Skip to content
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

SEKAI{V2_Migration} #50

Merged
merged 9 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 9 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
# visit https://giscus.app to get your Giscus ids
NEXT_PUBLIC_GISCUS_REPO=
NEXT_PUBLIC_GISCUS_REPOSITORY_ID=
NEXT_PUBLIC_GISCUS_CATEGORY=
NEXT_PUBLIC_GISCUS_CATEGORY_ID=
NEXT_PUBLIC_UTTERANCES_REPO=
NEXT_PUBLIC_DISQUS_SHORTNAME=


MAILCHIMP_API_KEY=
MAILCHIMP_API_SERVER=
MAILCHIMP_AUDIENCE_ID=

BUTTONDOWN_API_URL=https://api.buttondown.email/v1/
BUTTONDOWN_API_KEY=

CONVERTKIT_API_URL=https://api.convertkit.com/v3/
CONVERTKIT_API_KEY=
// curl https://api.convertkit.com/v3/forms?api_key=<your_public_api_key> to get your form ID
# curl https://api.convertkit.com/v3/forms?api_key=<your_public_api_key> to get your form ID
CONVERTKIT_FORM_ID=

KLAVIYO_API_KEY=
KLAVIYO_LIST_ID=
KLAVIYO_LIST_ID=

REVUE_API_KEY=

EMAILOCTOPUS_API_KEY=
EMAILOCTOPUS_LIST_ID=
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
.eslintrc.js
9 changes: 6 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,13 @@ module.exports = {
'next',
'next/core-web-vitals',
],
parserOptions: {
project: true,
tsconfigRootDir: __dirname,
},
rules: {
'prettier/prettier': 'error',
'prettier/prettier': 'warn',
'react/react-in-jsx-scope': 'off',
'@next/next/no-img-element': 'off',
'jsx-a11y/anchor-is-valid': [
'error',
{
Expand All @@ -30,7 +33,7 @@ module.exports = {
},
],
'react/prop-types': 0,
'no-unused-vars': 0,
'@typescript-eslint/no-unused-vars': 0,
'react/no-unescaped-entities': 0,
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
Expand Down
4 changes: 2 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
## Source: https://github.com/alexkaratarakis/gitattributes
## Modified * text=auto to * text eol=lf to force LF endings.
## Modified * text=auto to * text=auto eol=lf eol=lf to force LF endings.

## GITATTRIBUTES FOR WEB PROJECTS
#
Expand All @@ -16,7 +16,7 @@
## Force LF line endings automatically for files detected as
## text and leave all files detected as binary untouched.
## This will handle all files NOT defined below.
* text eol=lf
* text=auto eol=lf

# Source code
*.bash text eol=lf
Expand Down
12 changes: 11 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
/node_modules
/.pnp
.pnp.js
/.yarn/*
!/.yarn/releases
!/.yarn/plugins
!/.yarn/sdks

# testing
/coverage
Expand All @@ -13,14 +17,17 @@
/out/
public/sitemap.xml
.vercel
.next

# production
/build
*.xml

# rss feed
/public/feed.xml

# search
/public/search.json

# misc
.DS_Store

Expand All @@ -35,3 +42,6 @@ yarn-error.log*
.env.development.local
.env.test.local
.env.production.local

# Contentlayer
.contentlayer
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.mdx
1 change: 0 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
MIT License

Copyright (c) 2021 Timothy Lin
Copyright (c) 2021 Eana Hufwe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,20 @@ Website of Project SEKAI, a CTF team.

![Banner with logo](./public/static/images/twitter-card.png)

Based on [Tailwind Nextjs Starter Blog](https://github.com/timlrx/tailwind-nextjs-starter-blog).
Based on [timlrx/tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog).

## Installation

```bash
npm i
```

If you are using Windows, you may need to run:

```bash
set PWD="$(pwd)"
```

## Contributing

Please read [CONTRIBUTING.md](./CONTRIBUTING.md) for our editorial guidelines and writeup etiquette.
151 changes: 151 additions & 0 deletions app/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'css/prism.css'
import 'katex/dist/katex.css'

import PageTitle from '@/components/PageTitle'
import { components } from '@/components/MDXComponents'
import { MDXLayoutRenderer } from 'pliny/mdx-components'
import {
sortPosts,
coreContent,
allCoreContent,
} from 'pliny/utils/contentlayer'
import { allBlogs, allAuthors } from 'contentlayer/generated'
import type { Authors, Blog } from 'contentlayer/generated'
import PostSimple from '@/layouts/PostSimple'
import PostLayout from '@/layouts/PostLayout'
import PostBanner from '@/layouts/PostBanner'
import { Metadata } from 'next'
import siteMetadata from '@/data/siteMetadata'

const defaultLayout = 'PostLayout'
const layouts = {
PostSimple,
PostLayout,
PostBanner,
}

export async function generateMetadata({
params,
}: {
params: { slug: string[] }
}): Promise<Metadata | undefined> {
const slug = decodeURI(params.slug.join('/'))
const post = allBlogs.find((p) => p.slug === slug)
const authorList = post?.authors || ['default']
const authorDetails = authorList.map((author) => {
const authorResults = allAuthors.find((p) => p.slug === author)
return coreContent(authorResults as Authors)
})
if (!post) {
return
}

const publishedAt = new Date(post.date).toISOString()
const modifiedAt = new Date(post.lastmod || post.date).toISOString()
const authors = authorDetails.map((author) => author.name)
const authorNames = authors.join(', ')
const organizedParams = {
title: post.title,
desc: post.summary,
authors: authorNames,
} as Record<string, string>
const query = new URLSearchParams(organizedParams).toString()
const defaultSocialBanner = `https://og-vercel-ruby.vercel.app/api/sekai?${query}`
let imageList = [defaultSocialBanner]
if (post.images) {
imageList = typeof post.images === 'string' ? [post.images] : post.images
}
const ogImages = imageList.map((img) => {
return {
url: img.includes('http') ? img : siteMetadata.siteUrl + img,
}
})

return {
title: post.title,
description: post.summary,
openGraph: {
title: post.title,
description: post.summary,
siteName: siteMetadata.title,
locale: 'en_US',
type: 'article',
publishedTime: publishedAt,
modifiedTime: modifiedAt,
url: './',
images: ogImages,
authors: authors.length > 0 ? authors : [siteMetadata.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.summary,
images: imageList,
},
}
}

export const generateStaticParams = async () => {
const paths = allBlogs.map((p) => ({ slug: p.slug.split('/') }))

return paths
}

export default async function Page({ params }: { params: { slug: string[] } }) {
const slug = decodeURI(params.slug.join('/'))
// Filter out drafts in production
const sortedCoreContents = allCoreContent(sortPosts(allBlogs))
const postIndex = sortedCoreContents.findIndex((p) => p.slug === slug)
if (postIndex === -1) {
return (
<div className="mt-24 text-center">
<PageTitle>
Under Construction{' '}
<span role="img" aria-label="roadwork sign">
🚧
</span>
</PageTitle>
</div>
)
}

const prev = sortedCoreContents[postIndex + 1]
const next = sortedCoreContents[postIndex - 1]
const post = allBlogs.find((p) => p.slug === slug) as Blog
const authorList = post?.authors || ['default']
const authorDetails = authorList.map((author) => {
const authorResults = allAuthors.find((p) => p.slug === author)
return coreContent(authorResults as Authors)
})
const mainContent = coreContent(post)
const jsonLd = post.structuredData
jsonLd['author'] = authorDetails.map((author) => {
return {
'@type': 'Person',
name: author.name,
}
})

const Layout = layouts[post.layout || defaultLayout]

return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<Layout
content={mainContent}
authorDetails={authorDetails}
next={next}
prev={prev}
>
<MDXLayoutRenderer
code={post.body.code}
components={components}
toc={post.toc}
/>
</Layout>
</>
)
}
30 changes: 30 additions & 0 deletions app/blog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ListLayout from '@/layouts/ListLayout'
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
import { allBlogs } from 'contentlayer/generated'
import { genPageMetadata } from 'app/seo'

const POSTS_PER_PAGE = 5

export const metadata = genPageMetadata({ title: 'Blog' })

export default function BlogPage() {
const posts = allCoreContent(sortPosts(allBlogs))
const pageNumber = 1
const initialDisplayPosts = posts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(posts.length / POSTS_PER_PAGE),
}

return (
<ListLayout
posts={posts}
initialDisplayPosts={initialDisplayPosts}
pagination={pagination}
title="All Posts"
/>
)
}
36 changes: 36 additions & 0 deletions app/blog/page/[page]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ListLayout from '@/layouts/ListLayout'
import { allCoreContent, sortPosts } from 'pliny/utils/contentlayer'
import { allBlogs } from 'contentlayer/generated'

const POSTS_PER_PAGE = 5

export const generateStaticParams = async () => {
const totalPages = Math.ceil(allBlogs.length / POSTS_PER_PAGE)
const paths = Array.from({ length: totalPages }, (_, i) => ({
page: (i + 1).toString(),
}))

return paths
}

export default function Page({ params }: { params: { page: string } }) {
const posts = allCoreContent(sortPosts(allBlogs))
const pageNumber = parseInt(params.page as string)
const initialDisplayPosts = posts.slice(
POSTS_PER_PAGE * (pageNumber - 1),
POSTS_PER_PAGE * pageNumber
)
const pagination = {
currentPage: pageNumber,
totalPages: Math.ceil(posts.length / POSTS_PER_PAGE),
}

return (
<ListLayout
posts={posts}
initialDisplayPosts={initialDisplayPosts}
pagination={pagination}
title="All Posts"
/>
)
}