├── .env.example ├── public ├── favicon.ico ├── icons │ ├── favicon.ico │ ├── apple-touch-icon.png │ ├── maskable_icon_x48.png │ ├── maskable_icon_x72.png │ ├── maskable_icon_x96.png │ ├── maskable_icon_x128.png │ ├── maskable_icon_x192.png │ ├── maskable_icon_x384.png │ └── maskable_icon_x512.png ├── robots.txt ├── sitemap.xml.js ├── fallback-development.js └── manifest.json ├── .prettierrc.json ├── pages ├── 404.tsx ├── 500.tsx ├── _offline.tsx ├── _app.tsx ├── _document.tsx ├── index.tsx └── [slug].tsx ├── next-env.d.ts ├── postcss.config.js ├── .eslintrc.json ├── next.config.js ├── tsconfig.json ├── style └── global.css ├── .gitignore ├── webpack.config.js ├── lib └── client.ts ├── components ├── ScrollToTop.tsx ├── Footer.tsx ├── Template.tsx └── Layout.tsx ├── tailwind.config.js ├── package.json └── README.md /.env.example: -------------------------------------------------------------------------------- 1 | NOTION_TOKEN= 2 | NOTION_DATABASE_ID= -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/favicon.ico -------------------------------------------------------------------------------- /public/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x48.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x72.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x96.png -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "singleAttributePerLine": true 6 | } 7 | -------------------------------------------------------------------------------- /public/icons/maskable_icon_x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x128.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x192.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x384.png -------------------------------------------------------------------------------- /public/icons/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minime89-maker/next.js-notion-blog/HEAD/public/icons/maskable_icon_x512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # Block all crawlers for /accounts 2 | User-agent: * 3 | Disallow: /accounts 4 | 5 | # Allow all crawlers 6 | User-agent: * 7 | Allow: / -------------------------------------------------------------------------------- /pages/404.tsx: -------------------------------------------------------------------------------- 1 | const Custom404 = () => { 2 | return ( 3 | <> 4 |

404 - Page Not Found

5 | 6 | ) 7 | } 8 | 9 | export default Custom404 10 | -------------------------------------------------------------------------------- /pages/500.tsx: -------------------------------------------------------------------------------- 1 | const Custom500 = () => { 2 | return ( 3 | <> 4 |

500 - Internal Error

5 | 6 | ) 7 | } 8 | 9 | export default Custom500 10 | -------------------------------------------------------------------------------- /pages/_offline.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const Offline = () => { 4 | return ( 5 |
6 |

No Internet connection

7 |
8 | ) 9 | } 10 | 11 | export default Offline 12 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | // If you want to use other PostCSS plugins, see the following: 2 | // https://tailwindcss.com/docs/using-with-preprocessors 3 | module.exports = { 4 | plugins: { 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /public/sitemap.xml.js: -------------------------------------------------------------------------------- 1 | // manual sitemap for simple and static site 2 | ; 6 | 7 | 8 | https://notionblogs.vercel.app/ 9 | 2021-06-10 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["unused-imports", "@typescript-eslint"], 3 | "extends": [ 4 | "next/core-web-vitals", 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier" 7 | ], 8 | "rules": { 9 | "@typescript-eslint/no-unused-vars": "off", 10 | "unused-imports/no-unused-imports": "error", 11 | "@typescript-eslint/no-explicit-any": "off", 12 | "@typescript-eslint/ban-ts-comment": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | const withPWA = require('next-pwa') 2 | 3 | module.exports = withPWA({ 4 | images: { 5 | domains: [ 6 | 's3-us-west-2.amazonaws.com', 7 | 'images.unsplash.com', 8 | 'miro.medium.com', 9 | 's3.us-west-2.amazonaws.com', 10 | 'images.pexels.com', 11 | 'i.imgur.com', 12 | 'thumbs.gfycat.com', 13 | ], 14 | }, 15 | pwa: { 16 | dest: 'public', 17 | register: true, 18 | skipWaiting: true, 19 | disable: process.env.NODE_ENV === 'test', 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /style/global.css: -------------------------------------------------------------------------------- 1 | .post-link { 2 | @apply transition-shadow; 3 | box-shadow: inset 0 -0.05em 0 #2c7a7b; 4 | } 5 | .post-link:hover { 6 | box-shadow: inset 0 -0.5em 0 #2c7a7b; 7 | } 8 | .frame-container { 9 | position: relative; 10 | padding-bottom: 56.25%; 11 | height: 0; 12 | margin: 14px 0; 13 | } 14 | iframe { 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | } 21 | 22 | #nprogress .bar { 23 | background: #2c7a7b !important; 24 | } 25 | 26 | #nprogress .spinner { 27 | display: none; 28 | } 29 | 30 | #nprogress .spinner-icon { 31 | display: none; 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # PWA files 37 | **/public/sw.js 38 | **/public/workbox-*.js 39 | **/public/worker-*.js 40 | **/public/sw.js.map 41 | **/public/workbox-*.js.map 42 | **/public/worker-*.js.map -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src/index.ts', 3 | output: { 4 | filename: './dist/bundle.js', 5 | }, 6 | // Enable sourcemaps for debugging webpack's output. 7 | devtool: 'source-map', 8 | resolve: { 9 | // Add '.ts' and '.tsx' as resolvable extensions. 10 | extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'], 11 | }, 12 | module: { 13 | rules: [ 14 | // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'. 15 | { test: /\.tsx?$/, loader: 'ts-loader' }, 16 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 17 | { test: /\.js$/, loader: 'source-map-loader' }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /public/fallback-development.js: -------------------------------------------------------------------------------- 1 | /******/ (() => { // webpackBootstrap 2 | /******/ "use strict"; 3 | var __webpack_exports__ = {}; 4 | 5 | 6 | self.fallback = async request => { 7 | // https://developer.mozilla.org/en-US/docs/Web/API/RequestDestination 8 | switch (request.destination) { 9 | case 'document': 10 | if (true) return caches.match("/_offline", { 11 | ignoreSearch: true 12 | }); 13 | 14 | case 'image': 15 | if (false) {} 16 | 17 | case 'audio': 18 | if (false) {} 19 | 20 | case 'video': 21 | if (false) {} 22 | 23 | case 'font': 24 | if (false) {} 25 | 26 | case '': 27 | if (false) {} 28 | 29 | default: 30 | return Response.error(); 31 | } 32 | 33 | ; 34 | }; 35 | /******/ })() 36 | ; -------------------------------------------------------------------------------- /lib/client.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@notionhq/client' 2 | 3 | const client = new Client({ auth: process.env.NOTION_TOKEN }) 4 | 5 | export const getDatabase = async (databaseId: string) => { 6 | const response = await client.databases.query({ 7 | database_id: databaseId, 8 | sorts: [ 9 | { 10 | property: 'Date', 11 | direction: 'descending', 12 | }, 13 | ], 14 | }) 15 | return response.results 16 | } 17 | 18 | export const getPages = async (pageId: string) => { 19 | const response = await client.pages.retrieve({ 20 | page_id: pageId, 21 | }) 22 | return response 23 | } 24 | 25 | export const getBlocks = async (blockId: string) => { 26 | const response = await client.blocks.children.list({ 27 | block_id: blockId, 28 | }) 29 | return response.results 30 | } 31 | -------------------------------------------------------------------------------- /components/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useEffect, useState } from 'react' 2 | import { ArrowUpCircle } from 'react-feather' 3 | 4 | const ScrollToTop = () => { 5 | const [isScrolled, setIsScrolled] = useState(false) 6 | 7 | const handleScroll = useCallback(() => { 8 | window.scrollTo({ 9 | top: 0, 10 | behavior: 'smooth', 11 | }) 12 | }, []) 13 | 14 | useEffect(() => { 15 | window.addEventListener('scroll', () => { 16 | if (window.scrollY > 180) { 17 | setIsScrolled(true) 18 | } else if (window.scrollY < 180) { 19 | setIsScrolled(false) 20 | } 21 | }) 22 | }) 23 | 24 | return ( 25 | <> 26 | 35 | 36 | ) 37 | } 38 | 39 | export default ScrollToTop 40 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Blog", 3 | "name": "Notion's Blog", 4 | "description": "A simple Blog created with Notion", 5 | "icons": [ 6 | { 7 | "src": "./icons/maskable_icon_x48.png", 8 | "sizes": "48x48", 9 | "type": "image/png" 10 | }, 11 | { 12 | "src": "./icons/maskable_icon_x72.png", 13 | "sizes": "72x72", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "./icons/maskable_icon_x96.png", 18 | "sizes": "96x96", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "./icons/maskable_icon_x128.png", 23 | "sizes": "128x128", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "./icons/maskable_icon_x192.png", 28 | "sizes": "192x192", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "./icons/maskable_icon_x384.png", 33 | "sizes": "384x384", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "./icons/maskable_icon_x512.png", 38 | "sizes": "512x512", 39 | "type": "image/png" 40 | } 41 | ], 42 | "start_url": ".", 43 | "id": ".", 44 | "display": "standalone", 45 | "background_color": "#ffffff", 46 | "theme_color": "#222222" 47 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mode: 'jit', 3 | purge: [ 4 | './pages/**/*.{js,ts,jsx,tsx}', 5 | './components/**/*.{js,ts,jsx,tsx}', 6 | './public/**/*.html', 7 | ], 8 | darkMode: 'class', // or 'media' or 'class' 9 | theme: { 10 | fontFamily: { 11 | sans: ['IBM Plex Sans', 'Helvetica Neue', 'sans-serif'], 12 | serif: ['IBM Plex Serif', 'Georgia', 'serif'], 13 | mono: ['IBM Plex Mono', 'Menlo', 'monospace'], 14 | }, 15 | extend: { 16 | colors: { 17 | backgroundColor: '#F9FAFB', 18 | backgroundDark: '#202023', 19 | button: '#319795', 20 | buttonDark: '#81E6D9', 21 | social: '#2C7A7B', 22 | socialDark: '#81E6D9', 23 | callout: '#F4F4F5', 24 | calloutDark: '#3a3a40', 25 | }, 26 | textColor: { 27 | textPrimary: '#1A202C', 28 | textPrimaryDark: '#EEEEEE', 29 | textSecondary: '#262F40', 30 | textSecondaryDark: '#E0E0E0', 31 | textTertiary: '#6A6462', 32 | textTertiaryDark: '#ADADAD', 33 | textButton: '#fff', 34 | textLinks: '#81E6D9', 35 | }, 36 | }, 37 | }, 38 | variants: { 39 | extend: {}, 40 | }, 41 | plugins: [], 42 | } 43 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import 'tailwindcss/tailwind.css' 2 | import '../style/global.css' 3 | import { ThemeProvider } from 'next-themes' 4 | import 'prismjs/themes/prism-tomorrow.css' 5 | import Router from 'next/router' 6 | import { AppProps } from 'next/app' 7 | import NProgress from 'nprogress' 8 | import 'nprogress/nprogress.css' 9 | import { useEffect } from 'react' 10 | import ScrollToTop from '../components/ScrollToTop' 11 | 12 | Router.events.on('routeChangeStart', () => NProgress.start()) 13 | Router.events.on('routeChangeComplete', () => NProgress.done()) 14 | Router.events.on('routeChangeError', () => NProgress.done()) 15 | 16 | const MyApp = ({ Component, pageProps }: AppProps) => { 17 | // service worker 18 | useEffect(() => { 19 | if ('serviceWorker' in navigator) { 20 | window.addEventListener('load', function () { 21 | navigator.serviceWorker.register('/sw.js').then( 22 | function (registration) { 23 | registration 24 | }, 25 | function (err) { 26 | err.message 27 | }, 28 | ) 29 | }) 30 | } 31 | }, []) 32 | 33 | return ( 34 | 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | export default MyApp 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "start": "next start", 7 | "lint": "next lint" 8 | }, 9 | "dependencies": { 10 | "@carbon/type": "10.37.0", 11 | "@notionhq/client": "0.4.3", 12 | "@types/jest": "27.4.1", 13 | "@types/lodash": "4.14.182", 14 | "@types/node": "17.0.25", 15 | "@types/react": "18.0.6", 16 | "date-fns": "2.25.0", 17 | "eslint-config-prettier": "^8.5.0", 18 | "eslint-plugin-unused-imports": "^2.0.0", 19 | "next": "latest", 20 | "next-themes": "0.1.1", 21 | "nprogress": "0.2.0", 22 | "prismjs": "1.28.0", 23 | "react": "17.0.2", 24 | "react-dom": "17.0.2", 25 | "react-feather": "2.0.9", 26 | "sharp": "0.29.3", 27 | "source-map-loader": "3.0.1", 28 | "ts-loader": "9.2.8", 29 | "typescript": "4.6.3" 30 | }, 31 | "devDependencies": { 32 | "@types/nprogress": "0.2.0", 33 | "@types/prismjs": "1.26.0", 34 | "@typescript-eslint/eslint-plugin": "5.20.0", 35 | "@typescript-eslint/parser": "5.20.0", 36 | "autoprefixer": "10.2.6", 37 | "eslint": "8.14.0", 38 | "eslint-config-next": "12.0.3", 39 | "next-pwa": "5.4.0", 40 | "postcss": "8.3.5", 41 | "prettier": "2.6.2", 42 | "tailwindcss": "2.2.4" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notion Blog 2 | 3 | This is a blog built with [Next.js](https://nextjs.org) using [Notion's API](https://developers.notion.com/) and [TailwindCSS](https://tailwdindcss.com). 4 | 5 | ## How to use 6 | 7 | To obtain a token and the database id please visit the [Notion Getting Started Guide](https://developers.notion.com/docs/getting-started). 8 | 9 | Create a file called `.env.local` in the root of the project and add the following lines: 10 | 11 | ```bash 12 | NOTION_TOKEN= 13 | NOTION_DATABASE_ID= 14 | ``` 15 | 16 | Install dependencies 17 | 18 | ```bash 19 | npm install 20 | # or 21 | yarn 22 | ``` 23 | 24 | Start the server with 25 | 26 | ```bash 27 | npm run dev 28 | # or 29 | yarn dev 30 | ``` 31 | 32 | Go to [http://localhost:3000](http://localhost:3000). 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use [Vercel](https://vercel.com/). 37 | 38 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fminime89-maker%2Fnext.js-notion-blog&env=NOTION_TOKEN,NOTION_DATABASE_ID&envDescription=Notion%20Token%20and%20Notion%20Database%20ID%20is%20required&envLink=https%3A%2F%2Fwww.notion.so%2Fmy-integrations&demo-title=Blog&demo-description=A%20blog%20example%20using%20Next.js%20and%20Notion's%20api&demo-url=https%3A%2F%2Fnotionblogs.vercel.app%2F&demo-image=https%3A%2F%2Fuser-images.githubusercontent.com%2F77694499%2F139641140-b61b5d2a-cb9c-45ed-988f-f18eed400003.png) 39 | 40 | ## Credits 41 | 42 | Thanks to [leerob](https://github.com/leerob) and the [samuelkraft](https://github.com/samuelkraft) for insipration. 43 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | const MyDocument = () => { 4 | return ( 5 | 6 | 7 | 11 | 15 | 19 | 23 | 27 | 31 | 35 | 39 | 43 | 47 | 51 | 55 | 59 | 63 | 67 | 68 | 69 |
70 | 71 | 72 | 73 | ) 74 | } 75 | 76 | export default MyDocument 77 | -------------------------------------------------------------------------------- /components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Facebook, Twitter, Linkedin, GitHub } from 'react-feather' 2 | import Link from 'next/link' 3 | 4 | export type FooterProps = { 5 | facebook: string 6 | twitter: string 7 | linkedin: string 8 | github: string 9 | authorName?: string 10 | } 11 | 12 | const Footer = ({ 13 | facebook, 14 | twitter, 15 | linkedin, 16 | github, 17 | authorName, 18 | }: FooterProps) => ( 19 |
20 |
21 | {facebook && ( 22 | 23 | 28 | 32 | 33 | 34 | )} 35 | {twitter && ( 36 | 37 | 42 | 46 | 47 | 48 | )} 49 | {linkedin && ( 50 | 51 | 56 | 60 | 61 | 62 | )} 63 | {github && ( 64 | 65 | 70 | 74 | 75 | 76 | )} 77 |
78 |

79 | Copyright © 2022. {authorName}. 80 |

81 |
82 | ) 83 | 84 | export default Footer 85 | -------------------------------------------------------------------------------- /components/Template.tsx: -------------------------------------------------------------------------------- 1 | import Prism from 'prismjs' 2 | import React, { useEffect } from 'react' 3 | 4 | export type HeaderProps = { 5 | children: JSX.Element | JSX.Element[] 6 | } 7 | 8 | // @ts-ignore 9 | export const Text = ({ text }) => { 10 | { 11 | !text && null 12 | } 13 | return ( 14 | text && 15 | // @ts-ignore 16 | text.map((value, id) => { 17 | const { 18 | annotations: { bold, italic, underline, strikethrough, code, color }, 19 | text, 20 | } = value 21 | return ( 22 | 36 | {text.link ? ( 37 | 43 | {text.content} 44 | 45 | ) : ( 46 | text.content 47 | )} 48 | 49 | ) 50 | }) 51 | ) 52 | } 53 | 54 | // Header components 55 | export const Heading_1 = ({ children }: HeaderProps) => { 56 | return ( 57 |

58 |
59 | {children} 60 |

61 | ) 62 | } 63 | 64 | export const Heading_2 = ({ children }: HeaderProps) => { 65 | return ( 66 |

67 |
68 | {children} 69 |

70 | ) 71 | } 72 | 73 | export const Heading_3 = ({ children }: HeaderProps) => { 74 | return ( 75 |

76 |
77 | {children} 78 |

79 | ) 80 | } 81 | 82 | // Callout component 83 | export const Callout = ({ children }: HeaderProps) => { 84 | return ( 85 |
86 | {children} 87 |
88 | ) 89 | } 90 | 91 | // Divider component 92 | export const Divider = () => { 93 | return
94 | } 95 | 96 | // Code component 97 | export const Code = ({ 98 | children, 99 | language, 100 | }: { 101 | children: JSX.Element | JSX.Element[] 102 | language: string 103 | }) => { 104 | useEffect(() => { 105 | Prism.highlightAll() 106 | }, []) 107 | 108 | return ( 109 |
110 |       {children}
111 |     
112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Link from 'next/link' 3 | import { getDatabase } from '../lib/client' 4 | import { format } from 'date-fns' 5 | import Layout, { LayoutProps } from '../components/Layout' 6 | import { useTheme } from 'next-themes' 7 | 8 | export const databaseId = process.env.NOTION_DATABASE_ID as string 9 | 10 | export type HomeProps = { 11 | database: Array<{ [key: string]: any }> 12 | author: { [key: string]: any } 13 | } & LayoutProps 14 | 15 | const Home = ({ database, author }: HomeProps) => { 16 | const { theme } = useTheme() 17 | 18 | return ( 19 | 30 | 31 | {database[0].properties.Author.created_by.name} Blog 32 | 33 | 34 |
35 |
36 | {database && 37 | database.map((page) => { 38 | if (page.properties.published.checkbox) { 39 | return ( 40 | 44 | 45 |

46 | {page.properties.Name.title[0].plain_text} 47 |

48 |

49 | {page.properties.Description.rich_text[0].plain_text} 50 |

51 |
52 | 53 | 65 | 73 | 79 | 85 | 91 | 92 | {format( 93 | new Date(page.properties.Date.date.start), 94 | 'MMM dd, yyyy', 95 | )} 96 | 97 |
98 |
99 | 100 | ) 101 | } else { 102 | return null 103 | } 104 | })} 105 |
106 |
107 |
108 | ) 109 | } 110 | 111 | export default Home 112 | 113 | export const getStaticProps = async () => { 114 | const database = await getDatabase(databaseId) 115 | return { 116 | props: { 117 | author: database[0], 118 | database: database.slice(1), 119 | }, 120 | revalidate: 10, 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /pages/[slug].tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/ban-ts-comment */ 2 | 3 | import Image from 'next/image' 4 | import Head from 'next/head' 5 | import { GetStaticPaths, GetStaticProps } from 'next' 6 | import { 7 | Text, 8 | Heading_2, 9 | Code, 10 | Heading_1, 11 | Heading_3, 12 | Callout, 13 | Divider, 14 | } from '../components/Template' 15 | import { getDatabase, getPages, getBlocks } from '../lib/client' 16 | import { databaseId } from './index' 17 | import { format } from 'date-fns' 18 | import Layout from '../components/Layout' 19 | import { ParsedUrlQuery } from 'querystring' 20 | 21 | // @ts-ignore WIP 22 | const BlockPage = (block) => { 23 | const { type, id } = block 24 | const value = block[type] 25 | 26 | switch (type) { 27 | case 'paragraph': 28 | return ( 29 |

30 | 34 |

35 | ) 36 | case 'heading_1': 37 | return {value.text[0].plain_text} 38 | case 'heading_2': 39 | return {value.text[0].plain_text} 40 | case 'heading_3': 41 | return {value.text[0].plain_text} 42 | case 'callout': 43 | return ( 44 | 45 | {value.icon.emoji} {value.text[0]?.plain_text} 46 | 47 | ) 48 | case 'code': 49 | return ( 50 | 54 | {value.text[0].plain_text} 55 | 56 | ) 57 | case 'image': 58 | const src = 59 | value.type === 'external' ? value.external.url : value.file.url 60 | const caption = value.caption ? value.caption[0]?.plain_text : '' 61 | return ( 62 |
63 | {caption} 72 |
73 | ) 74 | case 'bulleted_list_item': 75 | case 'numbered_list_item': 76 | return ( 77 |
  • 78 | 79 |
  • 80 | ) 81 | case 'quote': 82 | return ( 83 |
    84 | 85 |
    86 | ) 87 | case 'video': 88 | return ( 89 |
    90 |