-
Notifications
You must be signed in to change notification settings - Fork 4
Initial setup, the flow is there. #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bcf5d39
cbb6cbb
5029296
43e75e6
350a857
a890278
b0173ba
2bea289
b316f1b
fa5eba3
c5c45cf
ba6cc3d
dbe44e8
400d134
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| import fs from "fs/promises"; | ||
| import path from "path"; | ||
| import { notFound } from "next/navigation"; | ||
| import { Metadata } from "next"; | ||
| import { getBlog } from "../readBlogs"; | ||
|
|
||
| type Params = { | ||
| params: Promise<{ | ||
| slug: string; | ||
| }>; | ||
| }; | ||
|
|
||
| export default async function BlogPost({ params }: Params) { | ||
| try { | ||
| const { slug } = await params; | ||
| const { data, contentHtml } = await getBlog(slug); | ||
|
|
||
| return ( | ||
| <div className="flex flex-1 flex-col items-center bg-gray-50 px-6 py-12"> | ||
| <div className="w-full max-w-4xl"> | ||
| <header className="mb-8 text-center"> | ||
| <h1 className="mb-4 text-5xl font-bold leading-tight text-primary"> | ||
| {data.title} | ||
| </h1> | ||
| <p className="text-sm italic text-gray-500"> | ||
| By {data.author} • {data.date} | ||
| </p> | ||
| </header> | ||
| <article | ||
| className="prose prose-lg lg:prose-xl prose-gray mx-auto leading-relaxed text-gray-700" | ||
| dangerouslySetInnerHTML={{ __html: contentHtml }} | ||
| /> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } catch (error) { | ||
| console.error("Error rendering blog post:", error); | ||
| return notFound(); | ||
| } | ||
| } | ||
|
|
||
| export async function generateStaticParams() { | ||
| try { | ||
| const blogPath = path.join(process.cwd(), "app/blog/posts"); | ||
| // 1) Check if the directory exists | ||
| const directoryExists = await fs | ||
| .stat(blogPath) | ||
| .then((stats) => stats.isDirectory()) | ||
| .catch(() => false); | ||
|
|
||
| // 2) If it's missing, return empty | ||
| if (!directoryExists) { | ||
| console.log( | ||
| "No app/blog/posts directory found. Returning empty params...", | ||
| ); | ||
| return []; | ||
| } | ||
|
|
||
| // 3) If it exists, read it | ||
| const files = await fs.readdir(blogPath); | ||
|
|
||
| // 4) Filter .md files to build the slug array | ||
| return files | ||
| .filter((filename) => filename.endsWith(".md")) | ||
| .map((filename) => ({ | ||
| slug: filename.replace(/\.md$/, ""), | ||
| })); | ||
| } catch (error) { | ||
| console.error("Error generating static params:", error); | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| export async function generateMetadata({ params }: Params): Promise<Metadata> { | ||
| try { | ||
| const { slug } = await params; | ||
| const { data } = await getBlog(slug); | ||
|
|
||
| return { | ||
| title: data.title, | ||
| authors: [{ name: data.author }], | ||
| }; | ||
| } catch (error) { | ||
| console.error("Error generating metadata:", error); | ||
| return { | ||
| title: "Blog Post", | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import Link from "next/link"; | ||
| import { getAllBlogs } from "./readBlogs"; | ||
|
|
||
| export default async function BlogIndex() { | ||
| const blogs = await getAllBlogs(); | ||
| return ( | ||
| <div className="flex-1 bg-gray-50"> | ||
| <div className="mx-auto max-w-6xl space-y-12 px-6 py-12"> | ||
| <div className="rounded-xl bg-white p-8 shadow-md"> | ||
| <div className="mb-8"> | ||
| <h1 className="text-4xl font-bold text-primary">All Updates</h1> | ||
| </div> | ||
| <div> | ||
| <ul className="space-y-6"> | ||
| {blogs.map((blog) => ( | ||
sid597 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <li | ||
| key={blog.slug} | ||
| className="flex items-start justify-between border-b border-gray-200 pb-4 last:border-b-0" | ||
| > | ||
| <div className="w-4/5"> | ||
| <Link | ||
| href={`/blog/${blog.slug}`} | ||
| className="block text-2xl font-semibold text-blue-600 hover:underline" | ||
| > | ||
| {blog.title} | ||
| </Link> | ||
| <p className="mt-2 text-sm italic text-gray-500"> | ||
| {blog.date} | ||
| </p> | ||
| </div> | ||
| <div className="w-1/5 text-right text-gray-600"> | ||
| by {blog.author} | ||
| </div> | ||
| </li> | ||
| ))} | ||
| </ul> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| --- | ||
| title: "Example Post - How to Create Blog Posts" | ||
| date: "2024-01-01" | ||
| author: "Devs" | ||
| published: false # Set to true to make this post visible | ||
| --- | ||
|
|
||
| # How to Create Blog Posts | ||
|
|
||
| This is a template post that shows how to create new blog posts. Keep this file as a reference and create new posts by following these instructions. | ||
|
|
||
| ### Creating Blog Posts | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea to put the instructions here 🔥 |
||
|
|
||
| 1. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) and [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) the repository. | ||
| 2. [Create a new branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches) for your update. | ||
| 3. Copy this file (`EXAMPLE.md`) to create a new post | ||
| 4. Rename the file to your desired URL slug (e.g., `my-new-post.md`) | ||
| 5. Update the frontmatter and content | ||
| 6. Set `published: true` when ready | ||
| 7. Push your branch and open a pull request. 🚀 | ||
|
|
||
| ### Required Frontmatter | ||
|
|
||
| Every post must start with frontmatter in YAML format: | ||
|
|
||
| ```yaml | ||
| --- | ||
| title: "Your Post Title" | ||
| date: "YYYY-MM-DD" | ||
| author: "Author's name" | ||
| published: true # Set to true to make the post visible | ||
| --- | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| import { remark } from "remark"; | ||
| import html from "remark-html"; | ||
| import { notFound } from "next/navigation"; | ||
| import path from "path"; | ||
| import fs from "fs/promises"; | ||
| import matter from "gray-matter"; | ||
| import { BlogSchema, type Blog, BlogFrontmatter } from "./schema"; | ||
|
|
||
| const BLOG_DIRECTORY = path.join(process.cwd(), "app/blog/posts"); | ||
|
|
||
| async function validateBlogDirectory(): Promise<boolean> { | ||
| try { | ||
| const stats = await fs.stat(BLOG_DIRECTORY); | ||
| return stats.isDirectory(); | ||
| } catch { | ||
| console.log("No app/blog/posts directory found."); | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| async function processBlogFile(filename: string): Promise<Blog | null> { | ||
| try { | ||
| const filePath = path.join(BLOG_DIRECTORY, filename); | ||
| const fileContent = await fs.readFile(filePath, "utf-8"); | ||
| const { data } = matter(fileContent); | ||
| const validatedData = BlogSchema.parse(data); | ||
|
|
||
| return { | ||
| slug: filename.replace(/\.md$/, ""), | ||
| ...validatedData, | ||
| }; | ||
| } catch (error) { | ||
| console.error(`Error processing blog file ${filename}:`, error); | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| async function getMarkdownContent(content: string): Promise<string> { | ||
| const processedContent = await remark().use(html).process(content); | ||
| return processedContent.toString(); | ||
| } | ||
|
|
||
| export async function getAllBlogs(): Promise<Blog[]> { | ||
| try { | ||
| const directoryExists = await validateBlogDirectory(); | ||
| if (!directoryExists) return []; | ||
|
|
||
| const files = await fs.readdir(BLOG_DIRECTORY); | ||
| const blogs = await Promise.all( | ||
| files.filter((filename) => filename.endsWith(".md")).map(processBlogFile), | ||
| ); | ||
| const validBlogs = blogs.filter(Boolean) as Blog[]; | ||
| return validBlogs.filter((blog) => blog.published); | ||
| } catch (error) { | ||
| console.error("Error reading blog directory:", error); | ||
| return []; | ||
| } | ||
| } | ||
|
|
||
| export async function getBlog( | ||
| slug: string, | ||
| ): Promise<{ data: BlogFrontmatter; contentHtml: string }> { | ||
| try { | ||
| const filePath = path.join(BLOG_DIRECTORY, `${slug}.md`); | ||
| await fs.access(filePath); | ||
|
|
||
| const fileContent = await fs.readFile(filePath, "utf-8"); | ||
| const { data: rawData, content } = matter(fileContent); | ||
| const data = BlogSchema.parse(rawData); | ||
|
|
||
| if (!data.published) { | ||
| console.log(`Post ${slug} is not published`); | ||
| return notFound(); | ||
| } | ||
|
|
||
| const contentHtml = await getMarkdownContent(content); | ||
|
|
||
| return { data, contentHtml }; | ||
| } catch (error) { | ||
| console.error("Error loading blog post:", error); | ||
| return notFound(); | ||
| } | ||
| } | ||
|
|
||
| export async function getLatestBlogs(): Promise<Blog[]> { | ||
| const blogs = await getAllBlogs(); | ||
| return blogs | ||
| .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) | ||
| .slice(0, 3); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { z } from "zod"; | ||
|
|
||
| export const BlogSchema = z.object({ | ||
| title: z.string(), | ||
| date: z.string(), | ||
| author: z.string(), | ||
| published: z.boolean().default(false), | ||
| }); | ||
|
|
||
| export type BlogFrontmatter = z.infer<typeof BlogSchema>; | ||
|
|
||
| export type Blog = BlogFrontmatter & { | ||
| slug: string; | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import type { Metadata } from "next"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import "./globals.css"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { PostHogProvider } from "./providers"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Image from "next/image"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Inter } from "next/font/google"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export const metadata: Metadata = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Discourse Graphs | A Tool for Collaborative Knowledge Synthesis", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -18,6 +21,8 @@ export const metadata: Metadata = { | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const inter = Inter({ subsets: ["latin"] }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export default function RootLayout({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| children, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }: Readonly<{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -26,7 +31,49 @@ export default function RootLayout({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <html lang="en"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <body className={`antialiased`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PostHogProvider>{children}</PostHogProvider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <PostHogProvider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className={`flex min-h-screen flex-col bg-neutral-light ${inter.className}`} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <header className="flex flex-col items-center justify-between space-y-4 px-6 py-4 md:flex-row md:space-y-0"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Link href="/" className="flex items-center space-x-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Image | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| src="/logo-screenshot-48.png" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| alt="Discourse Graphs Logo" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width={48} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height={48} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-3xl font-bold text-neutral-dark"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Discourse Graphs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+38
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add aria-label to improve logo link accessibility The logo link should have an aria-label for better accessibility. - <Link href="/" className="flex items-center space-x-2">
+ <Link href="/" className="flex items-center space-x-2" aria-label="Home">📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <nav className="w-full md:w-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ul className="flex flex-wrap justify-center space-x-4 md:flex-nowrap md:space-x-8"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {[ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "About", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Resources", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Events", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Updates", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Talks", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Supporters", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Contact", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ].map((item) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <li key={item}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Link | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| href={`/#${item.toLowerCase()}`} // Ensures absolute path with root `/` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Hash-based navigation implementation needs adjustments The verification revealed both positive and concerning aspects:
Recommendations:
🔗 Analysis chainVerify hash-based navigation implementation The current implementation uses hash-based navigation (
🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Check if sections with corresponding IDs exist
rg -l "id=[\"\'](?:about|resources|events|updates|talks|supporters|contact)[\"\']" --type html --type tsx
Length of output: 255 Script: #!/bin/bash
# Check if sections with corresponding IDs exist, fixing the regex
rg -l "id=['\"](?:about|resources|events|updates|talks|supporters|contact)['\"]" --type html --type tsx
# Check for scroll behavior implementation
rg "scroll" --type css --type tsx -A 2
# Check for any scroll offset or header-related adjustments
rg "header.*fixed|position:\s*fixed" --type css --type tsx -A 2
Length of output: 298 Script: #!/bin/bash
# Check if sections with corresponding IDs exist
rg -l "id=['\"](?:about|resources|events|updates|talks|supporters|contact)['\"]"
# Check for scroll behavior implementation
rg "scroll" -A 2
# Check for any scroll offset or header-related adjustments
rg "header.*fixed|position:\s*fixed" -A 2
# Look for any TypeScript/React files that might contain the sections
fd "\.(tsx|ts|jsx|js)$"
Length of output: 12941 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| className="text-neutral-dark hover:text-neutral-dark/60" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {item} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </li> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </nav> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </header> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {children} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </PostHogProvider> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </body> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </html> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.