feat: copying initial skeleton from Notion2Site

This commit is contained in:
Travis Fischer
2021-01-15 09:13:52 -05:00
parent 582a8f5e92
commit 253400fba9
28 changed files with 3810 additions and 247 deletions

7
.editorconfig Normal file
View File

@@ -0,0 +1,7 @@
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

34
.eslintrc Normal file
View File

@@ -0,0 +1,34 @@
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react",
"plugin:prettier/recommended",
"prettier/standard",
"prettier/react"
],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 2020,
"ecmaFeatures": {
"legacyDecorators": true,
"jsx": true
}
},
"settings": {
"react": {
"version": "16"
}
},
"rules": {
"space-before-function-paren": 0,
"react/prop-types": 0,
"react/jsx-handler-names": 0,
"react/jsx-fragments": 0,
"react/no-unused-prop-types": 0,
"import/export": 0,
"standard/no-callback-literal": 0
}
}

9
.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
.snapshots/
build/
dist/
node_modules/
.next/
.vercel/
.demo/
.renderer/

11
.prettierrc Normal file
View File

@@ -0,0 +1,11 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"useTabs": false,
"tabWidth": 2,
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "always",
"trailingComma": "none"
}

27
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,27 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "next dev",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/next",
"runtimeArgs": ["dev"],
"cwd": "${workspaceFolder}",
"port": 9229,
"smartStep": true,
"console": "integratedTerminal",
"skipFiles": ["<node_internals>/**"],
"env": {
"NODE_OPTIONS": "--inspect"
}
},
{
"type": "node",
"request": "attach",
"name": "Next.js App",
"skipFiles": ["<node_internals>/**"],
"port": 9229
}
]
}

36
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"files.exclude": {
"**/logs": true,
"**/*.log": true,
"**/npm-debug.log*": true,
"**/yarn-debug.log*": true,
"**/yarn-error.log*": true,
"**/pids": true,
"**/*.pid": true,
"**/*.seed": true,
"**/*.pid.lock": true,
"**/.dummy": true,
"**/lib-cov": true,
"**/coverage": true,
"**/.nyc_output": true,
"**/.grunt": true,
"**/.snapshots/": true,
"**/bower_components": true,
"**/.lock-wscript": true,
"build/Release": true,
"**/node_modules/": true,
"**/jspm_packages/": true,
"**/typings/": true,
"**/.npm": true,
"**/.eslintcache": true,
"**/.node_repl_history": true,
"**/*.tgz": true,
"**/.yarn-integrity": true,
"**/.next/": true,
"**/dist/": true,
"**/build/": true,
"**/.now/": true,
"**/.vercel/": true,
"**/.google.json": true
}
}

7
next.config.js Normal file
View File

@@ -0,0 +1,7 @@
// const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
})
module.exports = withBundleAnalyzer({})

View File

@@ -2,14 +2,61 @@
"name": "transitive-bullshit",
"version": "0.1.0",
"private": true,
"description": "Personal site of Travis Fischer using Notion as a CMS.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"repository": "transitive-bullshit/transitivebullsh.it",
"license": "MIT",
"engines": {
"node": ">=10"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
"start": "next start",
"deploy": "vercel --prod",
"deps": "run-s deps:*",
"deps:update": "[ -z $GITHUB_ACTIONS ] && yarn add notion-client notion-types notion-utils || echo 'Skipping deps:update on CI'",
"deps:link": "[ -z $GITHUB_ACTIONS ] && yarn link notion-client notion-types notion-utils || echo 'Skipping deps:link on CI'",
"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build",
"test": "run-s test:*",
"test:lint": "eslint .",
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
},
"dependencies": {
"@google-cloud/firestore": "^4.8.1",
"classnames": "^2.2.6",
"lqip-modern": "^1.1.3",
"next": "10.0.5",
"notion-client": "^2.4.1",
"notion-types": "^2.2.1",
"notion-utils": "^2.4.1",
"p-map": "^4.0.0",
"p-memoize": "^4.0.0",
"react": "17.0.1",
"react-dom": "17.0.1"
"react-dom": "17.0.1",
"react-use": "^15.3.3"
},
"devDependencies": {
"@next/bundle-analyzer": "^10.0.5",
"@types/classnames": "^2.2.10",
"@types/node": "^14.6.0",
"@types/react": "^17.0.0",
"babel-eslint": "^10.0.3",
"cross-env": "^7.0.2",
"eslint": "^7.2.0",
"eslint-config-prettier": "^7.1.0",
"eslint-config-standard": "^16.0.2",
"eslint-config-standard-react": "^11.0.1",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-react": "^7.18.0",
"next-svgr": "^0.0.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.5",
"typescript": "^4.0.2"
}
}

3
pages/404.tsx Normal file
View File

@@ -0,0 +1,3 @@
import { Page404 } from 'components'
export default Page404

View File

@@ -1,7 +0,0 @@
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp

38
pages/_app.tsx Normal file
View File

@@ -0,0 +1,38 @@
import 'styles/global.css'
import 'react-notion/styles.css'
import 'prismjs/themes/prism-tomorrow.css'
import 'rc-dropdown/assets/index.css'
import 'katex/dist/katex.min.css'
import React from 'react'
import { useEffect } from 'react'
import { useRouter } from 'next/router'
import { bootstrap } from 'lib/bootstrap-client'
import { fathomId, fathomConfig } from 'lib/config'
import * as Fathom from 'fathom-client'
if (typeof window !== 'undefined') {
bootstrap()
}
export default function App({ Component, pageProps }) {
const router = useRouter()
useEffect(() => {
if (fathomId) {
Fathom.load(fathomId, fathomConfig)
function onRouteChangeComplete() {
Fathom.trackPageview()
}
router.events.on('routeChangeComplete', onRouteChangeComplete)
return () => {
router.events.off('routeChangeComplete', onRouteChangeComplete)
}
}
}, [])
return <Component {...pageProps} />
}

17
pages/_document.tsx Normal file
View File

@@ -0,0 +1,17 @@
import * as React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
render() {
return (
<Html lang='en'>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}

3
pages/_error.tsx Normal file
View File

@@ -0,0 +1,3 @@
import { ErrorPage } from 'components'
export default ErrorPage

View File

@@ -0,0 +1,75 @@
import { NextApiRequest, NextApiResponse } from 'next'
import got from 'got'
import lqip from 'lqip-modern'
import * as types from '../../lib/types'
import * as db from '../../lib/db'
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}
const { url, id } = req.body
const result = await createPreviewImage(url, id)
res.setHeader(
'Cache-Control',
result.error
? 'public, s-maxage=60, max-age=60, stale-while-revalidate=60'
: 'public, immutable, s-maxage=31536000, max-age=31536000, stale-while-revalidate=60'
)
res.status(200).json(result)
}
export async function createPreviewImage(
url: string,
id: string
): Promise<types.PreviewImage> {
const doc = db.images.doc(id)
try {
const model = await doc.get()
if (model.exists) {
return model.data() as types.PreviewImage
}
const { body } = await got(url, { responseType: 'buffer' })
const result = await lqip(body)
console.log('lqip', result.metadata)
const image = {
url,
originalWidth: result.metadata.originalWidth,
originalHeight: result.metadata.originalHeight,
width: result.metadata.width,
height: result.metadata.height,
type: result.metadata.type,
dataURIBase64: result.metadata.dataURIBase64
}
await doc.create(image)
return image
} catch (err) {
console.error('lqip error', err)
try {
const error: any = {
url,
error: err.message || 'unknown error'
}
if (err?.response?.statusCode) {
error.statusCode = err?.response?.statusCode
}
await doc.create(error)
return error
} catch (err) {
// ignore errors
console.error(err)
}
}
}

View File

@@ -1,6 +0,0 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
export default (req, res) => {
res.statusCode = 200
res.json({ name: 'John Doe' })
}

46
pages/api/preload-site.ts Normal file
View File

@@ -0,0 +1,46 @@
import { NextApiRequest, NextApiResponse } from 'next'
import got from 'got'
import pMap from 'p-map'
import * as types from '../../lib/types'
import * as db from '../../lib/db'
import { getAllPages } from 'lib/get-all-pages'
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}
const { siteId } = req.body
const doc = db.sites.doc(siteId)
const site = await db.get<types.Site>(doc)
const pages = await getAllPages(site.rootNotionPageId, site.rootNotionSpaceId)
await pMap(
pages,
async (pageId) => {
try {
const url = `https://renderer.notionx.so/${site.domain}/${pageId}`
console.log(url)
await got(url)
} catch (err) {
console.error('page error', site.domain, pageId, err)
}
},
{
concurrency: 8
}
)
res.setHeader(
'Cache-Control',
'public, s-maxage=60, max-age=60, stale-while-revalidate=60'
)
res.status(200).json({
site,
numPages: pages.length
})
}

46
pages/api/resolve-site.ts Normal file
View File

@@ -0,0 +1,46 @@
import { NextApiRequest, NextApiResponse } from 'next'
import got from 'got'
import pMap from 'p-map'
import * as types from '../../lib/types'
import * as db from '../../lib/db'
import { getAllPages } from 'lib/get-all-pages'
export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}
const { siteId, concurrency = 8 } = req.body
const doc = db.sites.doc(siteId)
const site = await db.get<types.Site>(doc)
const pages = await getAllPages(site.rootNotionPageId, site.rootNotionSpaceId)
await pMap(
pages,
async (pageId) => {
try {
const url = `https://renderer.notionx.so/${site.domain}/${pageId}`
console.log('preload', url)
await got(url)
} catch (err) {
console.error('page preload error', site.domain, pageId, err)
}
},
{
concurrency
}
)
res.setHeader(
'Cache-Control',
'public, s-maxage=60, max-age=60, stale-while-revalidate=60'
)
res.status(200).json({
site,
numPages: pages.length
})
}

View File

@@ -1,65 +0,0 @@
import Head from 'next/head'
import styles from '../styles/Home.module.css'
export default function Home() {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
<p className={styles.description}>
Get started by editing{' '}
<code className={styles.code}>pages/index.js</code>
</p>
<div className={styles.grid}>
<a href="https://nextjs.org/docs" className={styles.card}>
<h3>Documentation &rarr;</h3>
<p>Find in-depth information about Next.js features and API.</p>
</a>
<a href="https://nextjs.org/learn" className={styles.card}>
<h3>Learn &rarr;</h3>
<p>Learn about Next.js in an interactive course with quizzes!</p>
</a>
<a
href="https://github.com/vercel/next.js/tree/master/examples"
className={styles.card}
>
<h3>Examples &rarr;</h3>
<p>Discover and deploy boilerplate example Next.js projects.</p>
</a>
<a
href="https://vercel.com/import?filter=next.js&utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
className={styles.card}
>
<h3>Deploy &rarr;</h3>
<p>
Instantly deploy your Next.js site to a public URL with Vercel.
</p>
</a>
</div>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<img src="/vercel.svg" alt="Vercel Logo" className={styles.logo} />
</a>
</footer>
</div>
)
}

60
pages/index.tsx Normal file
View File

@@ -0,0 +1,60 @@
import React from 'react'
import { isDemoMode, isDev } from 'lib/config'
import { getSiteMaps } from 'lib/get-site-maps'
import { resolveNotionPage } from 'lib/resolve-notion-page'
import { NotionPage } from 'components'
export const getStaticProps = async (context) => {
const domain = context.params.domain as string
const rawPageId = context.params.pageId as string
const isDemo = isDemoMode(domain)
try {
const props = await resolveNotionPage(domain, rawPageId)
return { props, unstable_revalidate: 10 }
} catch (err) {
console.error('page error', domain, rawPageId, err)
return {
props: {
error: {
statusCode: err.statusCode || 500,
message: err.message
},
isDemo
}
}
}
}
export async function getStaticPaths() {
if (isDev) {
return {
paths: [],
fallback: true
}
}
const siteMaps = await getSiteMaps()
const ret = {
paths: siteMaps.flatMap((siteMap) =>
siteMap.pageIds.map((pageId) => ({
params: {
domain: siteMap.site.domain,
pageId
}
}))
),
// paths: [],
fallback: true
}
console.log(ret.paths)
return ret
}
export default function NotionDomainDynamicPage(props) {
return <NotionPage {...props} />
}

BIN
public/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
public/error.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

2
public/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Allow: /

View File

@@ -1,122 +0,0 @@
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.footer {
width: 100%;
height: 100px;
border-top: 1px solid #eaeaea;
display: flex;
justify-content: center;
align-items: center;
}
.footer img {
margin-left: 0.5rem;
}
.footer a {
display: flex;
justify-content: center;
align-items: center;
}
.title a {
color: #0070f3;
text-decoration: none;
}
.title a:hover,
.title a:focus,
.title a:active {
text-decoration: underline;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
.title,
.description {
text-align: center;
}
.description {
line-height: 1.5;
font-size: 1.5rem;
}
.code {
background: #fafafa;
border-radius: 5px;
padding: 0.75rem;
font-size: 1.1rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}
.grid {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
max-width: 800px;
margin-top: 3rem;
}
.card {
margin: 1rem;
flex-basis: 45%;
padding: 1.5rem;
text-align: left;
color: inherit;
text-decoration: none;
border: 1px solid #eaeaea;
border-radius: 10px;
transition: color 0.15s ease, border-color 0.15s ease;
}
.card:hover,
.card:focus,
.card:active {
color: #0070f3;
border-color: #0070f3;
}
.card h3 {
margin: 0 0 1rem 0;
font-size: 1.5rem;
}
.card p {
margin: 0;
font-size: 1.25rem;
line-height: 1.5;
}
.logo {
height: 1em;
}
@media (max-width: 600px) {
.grid {
width: 100%;
flex-direction: column;
}
}

View File

@@ -1,9 +1,5 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
* {
box-sizing: border-box;
}
a {
@@ -11,6 +7,14 @@ a {
text-decoration: none;
}
* {
box-sizing: border-box;
body,
html {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
.notion-page {
margin: 0 12px;
}

22
tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2016",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": false,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"typeRoots": ["./node_modules/@types"]
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

8
vercel.json Normal file
View File

@@ -0,0 +1,8 @@
{
"version": 2,
"build": {
"env": {
"GOOGLE_APPLICATION_CREDENTIALS": "@notionx-api-google-application-credentials"
}
}
}

3335
yarn.lock

File diff suppressed because it is too large Load Diff