Skip to content

Commit ec796e8

Browse files
committed
Refactor code structure for improved readability and maintainability
1 parent 42fa48c commit ec796e8

File tree

7 files changed

+975
-51
lines changed

7 files changed

+975
-51
lines changed

src/app/atlas/AtlasClient.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { personaMap } from '@/data/personas'
1212
import type { Article } from '@/lib/markdown'
1313

1414
export default function AtlasClient({ articles }: { articles: Article[] }) {
15-
const [selectedChannels, setSelectedChannels] = useState<string[]>(['editorial', 'conversations', 'biology', 'physics', 'mathematics', 'ethics'])
15+
const [selectedChannels, setSelectedChannels] = useState<string[]>(['editorial', 'books', 'conversations', 'biology', 'physics', 'mathematics', 'ethics'])
1616
const [displayCount, setDisplayCount] = useState(3)
1717
const [loadingMore, setLoadingMore] = useState(false)
1818
const observerRef = useRef<IntersectionObserver | null>(null)
@@ -93,10 +93,15 @@ export default function AtlasClient({ articles }: { articles: Article[] }) {
9393
const authors = authorString.includes('&') ? authorString.split('&').map((a: string) => a.trim()) : [authorString]
9494
const mainAuthor = authors[0]
9595
const persona = personaMap[mainAuthor]
96-
const href = article.frontmatter.type === 'dialogue' ? `/atlas/dialogue/${article.slug}` : `/atlas/monologue/${article.slug}`
96+
const href = article.frontmatter.type === 'dialogue'
97+
? `/atlas/dialogue/${article.slug}`
98+
: article.frontmatter.type === 'book'
99+
? `/atlas/books/${article.slug}`
100+
: `/atlas/monologue/${article.slug}`
97101
const contentText = article.content.replace(/<[^>]*>/g, '')
98102
const firstParagraph = contentText.split('\n\n')[0] || contentText.slice(0, 200)
99-
const summary = firstParagraph.length > 200 ? firstParagraph.slice(0, 200) + '...' : firstParagraph
103+
const autoSummary = firstParagraph.length > 200 ? firstParagraph.slice(0, 200) + '...' : firstParagraph
104+
const summary = article.frontmatter.summary || autoSummary
100105
const articleChannel = getArticleChannel(article)
101106

102107
return (
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { getArticleBySlug, getAllArticles } from '@/lib/markdown'
2+
import type { Metadata } from 'next'
3+
import { AppSidebar } from '@/components/app-sidebar'
4+
import { TracingBeam } from '@/components/ui/tracing-beam'
5+
import { notFound } from 'next/navigation'
6+
import Link from 'next/link'
7+
import { ArrowLeft, Calendar, Clock, User } from 'lucide-react'
8+
import { Toc } from '@/components/ui/toc'
9+
// no persona badge here; author is rendered as text for books
10+
11+
interface PageProps {
12+
params: {
13+
slug: string
14+
}
15+
}
16+
17+
export async function generateMetadata({
18+
params
19+
}: PageProps): Promise<Metadata> {
20+
const { slug } = await params
21+
const article = await getArticleBySlug(slug)
22+
23+
if (!article) {
24+
return {}
25+
}
26+
27+
return {
28+
title: article.frontmatter.title,
29+
description: article.frontmatter.summary,
30+
openGraph: {
31+
title: article.frontmatter.title,
32+
description: article.frontmatter.summary,
33+
type: 'article',
34+
url: `/atlas/books/${article.slug}`
35+
},
36+
twitter: {
37+
card: 'summary_large_image',
38+
title: article.frontmatter.title,
39+
description: article.frontmatter.summary
40+
}
41+
}
42+
}
43+
44+
export default async function BookPage({ params }: PageProps) {
45+
const { slug } = await params
46+
const article = await getArticleBySlug(slug)
47+
48+
if (!article || article.frontmatter.type !== 'book') {
49+
notFound()
50+
}
51+
52+
const { title, date, author, tags } = article.frontmatter
53+
const readingTime = Math.ceil(article.content.replace(/<[^>]*>/g, '').split(' ').length / 200)
54+
55+
const jsonLd = {
56+
'@context': 'https://schema.org',
57+
'@type': 'BlogPosting',
58+
headline: title,
59+
datePublished: date,
60+
author: [
61+
{
62+
'@type': 'Person',
63+
name: author
64+
}
65+
]
66+
}
67+
68+
return (
69+
<div className="flex flex-col md:flex-row bg-gray-100 dark:bg-neutral-800 w-full flex-1 mx-auto border border-neutral-200 dark:border-neutral-700 overflow-visible md:overflow-hidden min-h-screen md:h-screen">
70+
<script
71+
type="application/ld+json"
72+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
73+
/>
74+
<AppSidebar />
75+
<div className="flex flex-1">
76+
<div id="scroll-container" className="p-2 md:p-10 rounded-tl-2xl border border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900 flex flex-col gap-2 flex-1 w-full md:h-full h-auto md:overflow-y-auto overflow-visible">
77+
<div className="flex gap-8 max-w-7xl mx-auto w-full">
78+
{/* Main Content */}
79+
<div className="flex-1 min-w-0">
80+
<TracingBeam className="px-6">
81+
<div className="max-w-2xl mx-auto antialiased pt-4 relative">
82+
{/* Back Button */}
83+
<Link
84+
href="/atlas"
85+
className="inline-flex items-center gap-2 text-neutral-600 dark:text-neutral-400 hover:text-neutral-900 dark:hover:text-neutral-100 mb-8 group"
86+
>
87+
<ArrowLeft className="w-4 h-4 group-hover:-translate-x-1 transition-transform" />
88+
Back to Atlas
89+
</Link>
90+
91+
{/* Article Header */}
92+
<header className="mb-12">
93+
<h1 className="text-4xl md:text-5xl font-bold text-neutral-900 dark:text-neutral-100 mb-6 leading-tight">
94+
{title}
95+
</h1>
96+
97+
{article.frontmatter.summary && (
98+
<p className="text-xl text-neutral-700 dark:text-neutral-300 leading-relaxed mb-8 font-medium">
99+
{article.frontmatter.summary}
100+
</p>
101+
)}
102+
103+
{/* Meta Information */}
104+
<div className="flex flex-wrap items-center gap-6 text-sm text-neutral-600 dark:text-neutral-400 mb-8">
105+
{author && (
106+
<div className="flex items-center gap-2">
107+
<User className="w-4 h-4" />
108+
<span>{author}</span>
109+
</div>
110+
)}
111+
112+
<div className="flex items-center gap-2">
113+
<Calendar className="w-4 h-4" />
114+
<time dateTime={date}>
115+
{new Date(date).toLocaleDateString('en-US', {
116+
year: 'numeric',
117+
month: 'long',
118+
day: 'numeric'
119+
})}
120+
</time>
121+
</div>
122+
123+
<div className="flex items-center gap-2">
124+
<Clock className="w-4 h-4" />
125+
<span>{readingTime} min read</span>
126+
</div>
127+
</div>
128+
129+
{/* Tags */}
130+
{tags && tags.length > 0 && (
131+
<div className="flex flex-wrap gap-2 mb-8">
132+
{tags.map((tag) => (
133+
<span
134+
key={tag}
135+
className="inline-flex items-center px-3 py-1 bg-neutral-100 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 rounded-full text-sm"
136+
>
137+
{tag}
138+
</span>
139+
))}
140+
</div>
141+
)}
142+
</header>
143+
144+
{/* Book Content */}
145+
<div id="article-root">
146+
<article
147+
className="prose prose-neutral dark:prose-invert prose-lg max-w-prose prose-headings:scroll-mt-8 prose-headings:font-bold prose-h1:text-3xl prose-h1:mb-8 prose-h2:text-2xl prose-h2:mt-12 prose-h2:mb-6 prose-h3:text-xl prose-h3:mt-8 prose-h3:mb-4 prose-p:leading-relaxed prose-p:text-neutral-700 dark:prose-p:text-neutral-300 prose-blockquote:border-l-4 prose-blockquote:border-blue-500 prose-blockquote:bg-blue-50 dark:prose-blockquote:bg-blue-950/30 prose-blockquote:py-2 prose-blockquote:px-4 prose-strong:text-neutral-900 dark:prose-strong:text-neutral-100 prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline"
148+
dangerouslySetInnerHTML={{ __html: article.content }}
149+
/>
150+
</div>
151+
</div>
152+
</TracingBeam>
153+
</div>
154+
155+
{/* Right Sidebar - TOC */}
156+
<div className="hidden xl:block w-64 shrink-0">
157+
<Toc
158+
contentHtml={article.content}
159+
author={author}
160+
scrollContainerId="scroll-container"
161+
articleRootId="article-root"
162+
/>
163+
</div>
164+
</div>
165+
</div>
166+
</div>
167+
</div>
168+
)
169+
}
170+
171+
export async function generateStaticParams() {
172+
const articles = await getAllArticles()
173+
return articles
174+
.filter(article => article.frontmatter.type === 'book')
175+
.map(article => ({
176+
slug: article.slug
177+
}))
178+
}

src/components/channel-badge.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
import { cn } from "@/lib/utils";
2-
import {
3-
Atom,
4-
Brain,
5-
Calculator,
6-
Dna,
7-
Scale,
8-
Zap
9-
} from "lucide-react";
10-
11-
interface Channel {
12-
id: string;
13-
name: string;
14-
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>;
15-
color: string;
16-
}
172

183

194
// Centralized channel definitions (matches channels-sidebar.tsx)
205
const CHANNELS = {
216
editorial: { name: "Editorial", color: "#ec4899", icon: "⚡" },
7+
books: { name: "Books", color: "#8b5cf6", icon: "📚" },
228
biology: { name: "Biology", color: "#f59e0b", icon: "🧬" },
239
physics: { name: "Physics", color: "#3b82f6", icon: "⚛️" },
2410
mathematics: { name: "Mathematics", color: "#10b981", icon: "🧮" },

src/components/channels-sidebar.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Checkbox } from "@/components/ui/checkbox";
55
import type { Article } from "@/lib/markdown";
66
import {
77
Atom,
8+
BookOpen,
89
Calculator,
910
Dna,
1011
Scale,
@@ -22,6 +23,7 @@ interface Channel {
2223
// Base channel definitions (counts will be calculated dynamically)
2324
const baseChannels: Omit<Channel, 'count'>[] = [
2425
{ id: "editorial", name: "Editorial", icon: Zap, color: "#ec4899" },
26+
{ id: "books", name: "Books", icon: BookOpen, color: "#8b5cf6" },
2527
{ id: "biology", name: "Biology", icon: Dna, color: "#f59e0b" },
2628
{ id: "physics", name: "Physics", icon: Atom, color: "#3b82f6" },
2729
{ id: "mathematics", name: "Mathematics", icon: Calculator, color: "#10b981" },
@@ -57,6 +59,13 @@ export function ChannelsSidebar({
5759
const content = article.content.toLowerCase();
5860
const title = article.frontmatter.title.toLowerCase();
5961

62+
// Books (for book content and literary works)
63+
if (tags.some(tag => ['book', 'literature', 'manuscript', 'publication'].includes(tag.toLowerCase())) ||
64+
content.includes('chapter') && content.includes('bibliography') ||
65+
title.includes('book') || article.frontmatter.type === 'book') {
66+
return 'books';
67+
}
68+
6069
// Biology (high priority for evolution/life sciences)
6170
if (tags.some(tag => ['biology', 'evolution', 'life', 'natural-selection'].includes(tag.toLowerCase())) ||
6271
content.includes('evolution') || content.includes('darwin') || title.includes('evolution')) {
@@ -99,11 +108,12 @@ export function ChannelsSidebar({
99108
// Calculate automatic counts if articles are provided
100109
const channelCounts = articles.length > 0 ? (() => {
101110
const counts: Record<string, number> = {
111+
editorial: 0,
112+
books: 0,
102113
biology: 0,
103114
physics: 0,
104115
mathematics: 0,
105116
ethics: 0,
106-
editorial: 0,
107117
philosophy: 0
108118
};
109119

0 commit comments

Comments
 (0)