diff --git a/.env b/.env index 954b1e6c..407d8618 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ NEXTAUTH_URL=https://shunkakinoki.com +NOTION_BLOG_ID=e4ef762ca07f465e8f5cce906732140b diff --git a/package.json b/package.json index 63b193b8..0674336f 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "@heroicons/react": "^1.0.4", "@next-auth/prisma-adapter": "^0.5.4", "@next/bundle-analyzer": "^11.1.0", + "@notionhq/client": "^0.3.0", "@prisma/client": "^2.29.1", "autoprefixer": "^10.3.2", "chrome-aws-lambda": "^10.1.0", diff --git a/src/components/Blog/Blog.module.css b/src/components/Blog/Blog.module.css deleted file mode 100644 index 2408b131..00000000 --- a/src/components/Blog/Blog.module.css +++ /dev/null @@ -1,23 +0,0 @@ -.markdown { - @apply leading-relaxed; -} - -.markdown h1 { - @apply my-8 text-6xl font-semibold leading-none tracking-tight; -} - -.markdown h2 { - @apply my-6 text-5xl font-semibold leading-tight; -} - -.markdown h3 { - @apply my-5 text-4xl font-medium leading-snug; -} - -.markdown ul { - @apply mx-4 my-3 text-2xl; -} - -.markdown li { - @apply my-3 underline; -} diff --git a/src/components/Blog/Blog.tsx b/src/components/Blog/Blog.tsx index 8ea94d1f..f2a62012 100644 --- a/src/components/Blog/Blog.tsx +++ b/src/components/Blog/Blog.tsx @@ -1,48 +1,44 @@ -import { MDXRemote } from "next-mdx-remote"; -import type { MDXRemoteSerializeResult } from "next-mdx-remote"; +import type { Page } from "@notionhq/client/build/src/api-types"; import Link from "next/link"; - import type { FC } from "react"; -import s from "./Blog.module.css"; - -export interface Props { - frontMatter: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; - }; - source: MDXRemoteSerializeResult; - slug: string; -} - -interface BlogLinkProps { - children: string; - href: string; -} - -export const Blog: FC = ({ source, slug }) => { - const BlogLink = ({ children, href }: BlogLinkProps) => { - return ( - - {children} - - ); - }; - - const components = { - a: BlogLink, - }; +export type Props = { + database: Page[]; + locale?: string; +}; +export const Blog: FC = ({ database, locale }) => { return ( -
-
- +
+
+ {database.map(page => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const date = new Date(page.properties.Date.date.start).toLocaleString( + locale, + { + month: "short", + day: "2-digit", + year: "numeric", + }, + ); + return ( + + ); + })}
); diff --git a/src/components/Notion/Notion.module.css b/src/components/Notion/Notion.module.css new file mode 100644 index 00000000..1704fa2f --- /dev/null +++ b/src/components/Notion/Notion.module.css @@ -0,0 +1,67 @@ +.notion { + @apply leading-relaxed; +} + +.notion a { + @apply underline; +} + +.notion p { + @apply my-3; +} + +.notion h1 { + @apply my-8 text-6xl font-semibold tracking-tight; +} + +.notion h2 { + @apply my-6 text-4xl font-semibold leading-tight; +} + +.notion h3 { + @apply my-5 text-2xl font-semibold leading-snug; +} + +.notion h4 { + @apply my-4 text-2xl; +} + +.notion h5 { + @apply my-3 text-3xl font-light; +} + +.notion h6 { + @apply my-3 text-xl font-thin; +} + +.notion ul { + @apply mx-4 my-6 text-lg font-semibold list-disc sm:my-9; +} + +.notion li { + @apply my-1; +} + +.notion ol { + @apply mx-4 my-6 text-lg font-semibold list-decimal list-inside sm:my-9; +} + +.notion table { + @apply table-caption w-full max-w-screen-lg overflow-hidden table-auto; +} + +.notion th { + @apply relative font-serif text-2xl font-semibold leading-relaxed text-left border-collapse; +} + +.notion td { + @apply px-4 py-2 font-serif leading-relaxed; +} + +.notion blockquote { + @apply p-3 mx-16 my-auto font-serif text-xl italic; +} + +.notion blockquote > p { + @apply max-w-screen-md mx-0 my-auto text-2xl font-bold; +} diff --git a/src/components/Notion/Notion.tsx b/src/components/Notion/Notion.tsx new file mode 100644 index 00000000..4e872033 --- /dev/null +++ b/src/components/Notion/Notion.tsx @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ + +import type { PagesRetrieveResponse } from "@notionhq/client/build/src/api-endpoints"; +import type { Block, RichText } from "@notionhq/client/build/src/api-types"; +import clsx from "clsx"; +import { Fragment } from "react"; +import type { FC } from "react"; + +import s from "./Notion.module.css"; + +export type Props = { + blocks: Block[]; + content: PagesRetrieveResponse; +}; + +export type TextProps = { + text: RichText[]; +}; + +export const Text: FC = ({ text }) => { + return ( + <> + {text.map((value, id) => { + const { + annotations: { bold, code, italic, strikethrough, underline }, + plain_text, + href, + } = value; + return ( + + {href ? ( + + {plain_text} + + ) : ( + plain_text + )} + + ); + })} + + ); +}; + +const renderBlock = (block: Block) => { + switch (block.type) { + case "paragraph": + return ( +

+ +

+ ); + case "heading_1": + return ( +

+ +

+ ); + default: + } +}; + +export const Notion: FC = ({ blocks, content }) => { + return ( +
+
+

+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */} + {/* @ts-ignore */} + {content.properties.Name?.title[0]?.plain_text} +

+
+
+ {blocks.map(block => { + return {renderBlock(block)}; + })} +
+
+ ); +}; diff --git a/src/components/Notion/index.ts b/src/components/Notion/index.ts new file mode 100644 index 00000000..4e1a56f5 --- /dev/null +++ b/src/components/Notion/index.ts @@ -0,0 +1,2 @@ +export { Notion } from "./Notion"; +export type { Props } from "./Notion"; diff --git a/src/components/Seo/Seo.tsx b/src/components/Seo/Seo.tsx index efd38a37..39701442 100644 --- a/src/components/Seo/Seo.tsx +++ b/src/components/Seo/Seo.tsx @@ -11,9 +11,15 @@ export interface Props extends NextSeoProps { export const Seo: FC = ({ date, title, description, ...rest }) => { const imageUrl = date && title - ? `https://shunkakinoki.com/api/image?fileType=png&layoutName=Blog&Theme=Dark&Title=${title}&Date=${date}` + ? `https://shunkakinoki.com/api/image?fileType=png&layoutName=Blog&Theme=Dark&Title=${title?.replace( + /\s/g, + "%20", + )}&Date=${date?.replace(/\s/g, "%20")}` : title - ? `https://shunkakinoki.com/api/image?fileType=png&layoutName=Website&Theme=Dark&Title=${title}` + ? `https://shunkakinoki.com/api/image?fileType=png&layoutName=Website&Theme=Dark&Title=${title?.replace( + /\s/g, + "%20", + )}` : "https://shunkakinoki.com/api/image?fileType=png&layoutName=Shun&Title=shunkakinoki"; return ( diff --git a/src/lib/notion.ts b/src/lib/notion.ts new file mode 100644 index 00000000..6fc3951e --- /dev/null +++ b/src/lib/notion.ts @@ -0,0 +1,24 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ + +import { Client } from "@notionhq/client"; + +const notion = new Client({ + auth: process.env.NOTION_API_KEY, +}); + +export const getDatabase = async (databaseId: string) => { + const response = await notion.databases.query({ + database_id: databaseId, + }); + return response.results; +}; + +export const getPage = async (pageId: string) => { + const response = await notion.pages.retrieve({ page_id: pageId }); + return response; +}; + +export const getBlocks = async (blockId: string) => { + const response = await notion.blocks.children.list({ block_id: blockId }); + return response.results; +}; diff --git a/src/pages/[...slug].tsx b/src/pages/[...slug].tsx index aab8bcd0..e5bfdb94 100644 --- a/src/pages/[...slug].tsx +++ b/src/pages/[...slug].tsx @@ -1,3 +1,5 @@ +import type { PagesRetrieveResponse } from "@notionhq/client/build/src/api-endpoints"; +import type { Block } from "@notionhq/client/build/src/api-types"; import type { GetStaticProps, InferGetStaticPropsType, @@ -7,19 +9,19 @@ import type { import type { MDXRemoteSerializeResult } from "next-mdx-remote"; -import { getGithubContent, getGithubSummary } from "@/lib/github"; -import { BlogScreen } from "@/screens/BlogScreen"; +import { getGithubContent } from "@/lib/github"; +import { getPage, getBlocks } from "@/lib/notion"; import { ContentScreen } from "@/screens/ContentScreen"; +import { NotionScreen } from "@/screens/NotionScreen"; export interface Props { content: string; frontMatter?: string; + blocks?: string; + locale?: string; slug?: string; - type: "blog" | "content" | "collection" | "page"; } -const blogCollections = ["blog", "pioneer"]; - const coreCollections = ["cause", "mission", "values"]; // eslint-disable-next-line @typescript-eslint/require-await @@ -37,56 +39,8 @@ export const getStaticProps: GetStaticProps = async ({ GetStaticPropsContext) => { const slugs = params?.slug as string[]; - if (slugs?.length !== 1) { - try { - const result = await getGithubContent( - slugs[0], - slugs?.splice(0, 1).join("/"), - locale, - ); - if (result) { - const { frontMatter, source } = result; - return { - props: { - content: JSON.stringify(source), - frontMatter: JSON.stringify(frontMatter), - type: "content", - }, - revalidate: 30, - }; - } - throw new Error("No result"); - } catch (err) { - return { - notFound: true, - revalidate: 30, - }; - } - } - const pageId = slugs[0]; - if (blogCollections.includes(pageId)) { - const result = await getGithubSummary(pageId, locale); - if (result) { - const { frontMatter, source } = result; - return { - props: { - content: JSON.stringify(source), - frontMatter: JSON.stringify(frontMatter), - slug: JSON.stringify(pageId), - type: "blog", - }, - revalidate: 30, - }; - } else { - return { - notFound: true, - revalidate: 30, - }; - } - } - if (coreCollections.includes(pageId)) { const result = await getGithubContent(pageId, pageId.toUpperCase(), locale); if (result) { @@ -108,16 +62,39 @@ GetStaticPropsContext) => { } try { - const result = await getGithubContent("blog", pageId, locale); - if (result) { - const { frontMatter, source } = result; + const page = await getPage(pageId); + const blocks = await getBlocks(pageId); + const childBlocks = await Promise.all( + blocks + .filter(block => { + return block.has_children; + }) + .map(async block => { + return { + id: block.id, + children: await getBlocks(block.id), + }; + }), + ); + const blocksWithChildren = blocks.map(block => { + if (block.type === "paragraph") { + const typedBlock = block[block.type]; + if (block.has_children) { + typedBlock["children"] = childBlocks.find(x => { + return x.id === block.id; + })?.children; + } + } + return block; + }); + + if (page) { return { props: { - content: JSON.stringify(source), - frontMatter: JSON.stringify(frontMatter), - type: "content", + blocks: JSON.stringify(blocksWithChildren), + content: JSON.stringify(page), + locale: locale, }, - revalidate: 30, }; } throw new Error("No result"); @@ -131,32 +108,32 @@ GetStaticPropsContext) => { export const PageId = ({ content, + locale, frontMatter, + blocks, slug, - type, }: InferGetStaticPropsType): JSX.Element => { - if (content && frontMatter && slug && type === "blog") { + if (content && frontMatter) { return ( - ); } - if (content && frontMatter && type === "content") { + if (content && blocks) { return ( - ); } - return <>; + return <>{slug}; }; export default PageId; diff --git a/src/pages/blog.tsx b/src/pages/blog.tsx new file mode 100644 index 00000000..28d3edc8 --- /dev/null +++ b/src/pages/blog.tsx @@ -0,0 +1,46 @@ +import type { Page } from "@notionhq/client/build/src/api-types"; +import type { + InferGetStaticPropsType, + GetStaticProps, + GetStaticPropsContext, +} from "next"; + +import { getDatabase } from "@/lib/notion"; +import { BlogScreen } from "@/screens/BlogScreen"; + +export type Props = { + database: Page[]; + locale?: string; +}; + +export const getStaticProps: GetStaticProps = async ({ + locale, +}: GetStaticPropsContext) => { + if (!process.env.NOTION_BLOG_ID) { + throw new Error("process.NOTION_BLOG_ID is not defined"); + } + const database = await getDatabase(process.env.NOTION_BLOG_ID); + if (database) { + return { + props: { + database: database, + locale: locale, + }, + revalidate: 30, + }; + } else { + return { + notFound: true, + revalidate: 30, + }; + } +}; + +export const Blog = ({ + database, + locale, +}: InferGetStaticPropsType): JSX.Element => { + return ; +}; + +export default BlogScreen; diff --git a/src/screens/BlogScreen/BlogScreen.tsx b/src/screens/BlogScreen/BlogScreen.tsx index 6d7b5642..0d8c83b6 100644 --- a/src/screens/BlogScreen/BlogScreen.tsx +++ b/src/screens/BlogScreen/BlogScreen.tsx @@ -8,13 +8,13 @@ import { Seo } from "@/components/Seo"; export type Props = BlogProps; -export const BlogScreen: FC = ({ frontMatter, source, slug }) => { +export const BlogScreen: FC = ({ database }) => { return ( <> - +
- +