Skip to content

Commit

Permalink
Next 9.3 with preview mode (ijjk#22)
Browse files Browse the repository at this point in the history
* next 9.3 with preview mode

* Fix post preview redirect and remove un-used items

Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
wbunting and ijjk committed Mar 12, 2020
1 parent 1a86c78 commit d96d3f2
Show file tree
Hide file tree
Showing 11 changed files with 501 additions and 135 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ yarn-error.log*

.blog_index_data
.blog_index_data_previews

.now
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"@zeit/react-jsx-parser": "^2.0.0",
"github-slugger": "^1.2.1",
"next": "^9.2.2-canary.3",
"next": "^9.3.0",
"prismjs": "^1.17.1",
"react": "16.12.0",
"react-dom": "16.12.0"
Expand Down
4 changes: 2 additions & 2 deletions src/lib/blog-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ export const getDateStr = date => {
})
}

export const postIsReady = (post: any) => {
return process.env.NODE_ENV !== 'production' || post.Published === 'Yes'
export const postIsPublished = (post: any) => {
return post.Published === 'Yes'
}

export const normalizeSlug = slug => {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/build-rss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { renderToStaticMarkup } from 'react-dom/server'
import { textBlock } from './notion/renderers'
import getBlogIndex from './notion/getBlogIndex'
import getNotionUsers from './notion/getNotionUsers'
import { postIsReady, getBlogLink } from './blog-helpers'
import { postIsPublished, getBlogLink } from './blog-helpers'

// must use weird syntax to bypass auto replacing of NODE_ENV
process.env['NODE' + '_ENV'] = 'production'
Expand Down Expand Up @@ -77,7 +77,7 @@ async function main() {
const blogPosts = Object.keys(postsTable)
.map(slug => {
const post = postsTable[slug]
if (!postIsReady(post)) return
if (!postIsPublished(post)) return

post.authors = post.Authors || []

Expand Down
13 changes: 13 additions & 0 deletions src/pages/api/clear-preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NextApiRequest, NextApiResponse } from 'next'

export default (req: NextApiRequest, res: NextApiResponse) => {
if (req.query.slug) {
res.clearPreviewData()
res.writeHead(307, { Location: `/blog/${req.query.slug}` })
res.end()
} else {
res.clearPreviewData()
res.writeHead(307, { Location: `/blog` })
res.end()
}
}
39 changes: 39 additions & 0 deletions src/pages/api/preview-post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { NextApiRequest, NextApiResponse } from 'next'
import getPageData from '../../lib/notion/getPageData'
import getBlogIndex from '../../lib/notion/getBlogIndex'
import { getBlogLink } from '../../lib/blog-helpers'

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (typeof req.query.token !== 'string') {
return res.status(401).json({ message: 'invalid token' })
}
if (req.query.token !== process.env.NOTION_TOKEN) {
return res.status(404).json({ message: 'not authorized' })
}

if (typeof req.query.slug !== 'string') {
return res.status(401).json({ message: 'invalid slug' })
}
const postsTable = await getBlogIndex()
const post = postsTable[req.query.slug]

if (!post) {
console.log(`Failed to find post for slug: ${req.query.slug}`)
return {
props: {
redirect: '/blog',
},
revalidate: 5,
}
}

const postData = await getPageData(post.id)

if (!postData) {
return res.status(401).json({ message: 'Invalid slug' })
}

res.setPreviewData({})
res.writeHead(307, { Location: getBlogLink(post.Slug) })
res.end()
}
22 changes: 22 additions & 0 deletions src/pages/api/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextApiRequest, NextApiResponse } from 'next'
import getPageData from '../../lib/notion/getPageData'
import getBlogIndex from '../../lib/notion/getBlogIndex'

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (typeof req.query.token !== 'string') {
return res.status(401).json({ message: 'invalid token' })
}
if (req.query.token !== process.env.NOTION_TOKEN) {
return res.status(404).json({ message: 'not authorized' })
}

const postsTable = await getBlogIndex()

if (!postsTable) {
return res.status(401).json({ message: 'Failed to fetch posts' })
}

res.setPreviewData({})
res.writeHead(307, { Location: `/blog` })
res.end()
}
65 changes: 51 additions & 14 deletions src/pages/blog/[slug].tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Head from 'next/head'
import Link from 'next/link'
import fetch from 'node-fetch'
import { useRouter } from 'next/router'
import Header from '../../components/header'
import Heading from '../../components/heading'
import components from '../../components/dynamic'
Expand All @@ -13,16 +14,19 @@ import getNotionUsers from '../../lib/notion/getNotionUsers'
import { getBlogLink, getDateStr } from '../../lib/blog-helpers'

// Get the data for each blog post
export async function unstable_getStaticProps({ params: { slug } }) {
export async function getStaticProps({ params: { slug }, preview }) {
// load the postsTable so that we can get the page's ID
const postsTable = await getBlogIndex()
const post = postsTable[slug]

if (!post) {
// if we can't find the post or if it is unpublished and
// viewed without preview mode then we just redirect to /blog
if (!post || (post.Published !== 'Yes' && !preview)) {
console.log(`Failed to find post for slug: ${slug}`)
return {
props: {
redirect: '/blog',
preview: false,
},
revalidate: 5,
}
Expand Down Expand Up @@ -58,20 +62,30 @@ export async function unstable_getStaticProps({ params: { slug } }) {
return {
props: {
post,
preview: preview || false,
},
revalidate: 10,
}
}

// Return our list of blog posts to prerender
export async function unstable_getStaticPaths() {
export async function getStaticPaths() {
const postsTable = await getBlogIndex()
return Object.keys(postsTable).map(slug => getBlogLink(slug))
// we fallback for any unpublished posts to save build time
// for actually published ones
return {
paths: Object.keys(postsTable)
.filter(post => postsTable[post].Published === 'Yes')
.map(slug => getBlogLink(slug)),
fallback: true,
}
}

const listTypes = new Set(['bulleted_list', 'numbered_list'])

const RenderPost = ({ post, redirect }) => {
const RenderPost = ({ post, redirect, preview }) => {
const router = useRouter()

let listTagName: string | null = null
let listLastId: string | null = null
let listMap: {
Expand All @@ -87,7 +101,7 @@ const RenderPost = ({ post, redirect }) => {
const twitterSrc = 'https://platform.twitter.com/widgets.js'
// make sure to initialize any new widgets loading on
// client navigation
if (post.hasTweet) {
if (post && post.hasTweet) {
if ((window as any)?.twttr?.widgets) {
;(window as any).twttr.widgets.load()
} else if (!document.querySelector(`script[src="${twitterSrc}"]`)) {
Expand All @@ -98,21 +112,44 @@ const RenderPost = ({ post, redirect }) => {
}
}
}, [])
useEffect(() => {
if (redirect && !post) {
router.replace(redirect)
}
}, [redirect, post])

if (redirect) {
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}

// if you don't have a post at this point, and are not
// loading one from fallback then redirect back to the index
if (!post) {
return (
<>
<Head>
<meta name="robots" content="noindex" />
<meta httpEquiv="refresh" content={`0;url=${redirect}`} />
</Head>
</>
<div className={blogStyles.post}>
<p>
Woops! didn't find that post, redirecting you back to the blog index
</p>
</div>
)
}

return (
<>
<Header titlePre={post.Page} />
{preview && (
<div className={blogStyles.previewAlertContainer}>
<div className={blogStyles.previewAlert}>
<b>Note:</b>
{` `}Viewing in preview mode{' '}
<Link href={`/api/clear-preview?slug=${post.Slug}`}>
<button className={blogStyles.escapePreview}>Exit Preview</button>
</Link>
</div>
</div>
)}
<div className={blogStyles.post}>
<h1>{post.Page || ''}</h1>
{post.Authors.length > 0 && (
Expand Down
31 changes: 26 additions & 5 deletions src/pages/blog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,24 @@ import Header from '../../components/header'
import blogStyles from '../../styles/blog.module.css'
import sharedStyles from '../../styles/shared.module.css'

import { getBlogLink, getDateStr, postIsReady } from '../../lib/blog-helpers'
import {
getBlogLink,
getDateStr,
postIsPublished,
} from '../../lib/blog-helpers'
import { textBlock } from '../../lib/notion/renderers'
import getNotionUsers from '../../lib/notion/getNotionUsers'
import getBlogIndex from '../../lib/notion/getBlogIndex'

export async function unstable_getStaticProps() {
export async function getStaticProps({ preview }) {
const postsTable = await getBlogIndex()

const authorsToGet: Set<string> = new Set()
const posts: any[] = Object.keys(postsTable)
.map(slug => {
const post = postsTable[slug]
// remove draft posts in production
if (!postIsReady(post)) {
if (!preview && !postIsPublished(post)) {
return null
}
post.Authors = post.Authors || []
Expand All @@ -36,16 +40,28 @@ export async function unstable_getStaticProps() {

return {
props: {
preview: preview || false,
posts,
},
revalidate: 10,
}
}

export default ({ posts = [] }) => {
export default ({ posts = [], preview }) => {
return (
<>
<Header titlePre="Blog" />
{preview && (
<div className={blogStyles.previewAlertContainer}>
<div className={blogStyles.previewAlert}>
<b>Note:</b>
{` `}Viewing in preview mode{' '}
<Link href={`/api/clear-preview`}>
<button className={blogStyles.escapePreview}>Exit Preview</button>
</Link>
</div>
</div>
)}
<div className={`${sharedStyles.layout} ${blogStyles.blogIndex}`}>
<h1>My Notion Blog</h1>
{posts.length === 0 && (
Expand All @@ -56,7 +72,12 @@ export default ({ posts = [] }) => {
<div className={blogStyles.postPreview} key={post.Slug}>
<h3>
<Link href="/blog/[slug]" as={getBlogLink(post.Slug)}>
<a>{post.Page}</a>
<div className={blogStyles.titleContainer}>
{!post.Published && (
<span className={blogStyles.draftBadge}>Draft</span>
)}
<a>{post.Page}</a>
</div>
</Link>
</h3>
{post.Authors.length > 0 && (
Expand Down
52 changes: 52 additions & 0 deletions src/styles/blog.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,58 @@
margin-bottom: 50px;
}

.previewAlertContainer {
display: flex;
justify-content: center;
}

.previewAlert {
display: inline-flex;
align-items: center;
justify-content: space-between;
text-align: center;
border: 1px solid #eaeaea;
width: 400px;
padding: 16px;
border-radius: 5px;
}

.escapePreview {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: none;
background-color: black;
border: 1px solid black;
color: white;
padding: 10px;
height: 24px;
border-radius: 5px;
transition: all 0.2s ease 0s;
}
.escapePreview:hover {
background-color: white;
color: black;
border: 1px solid black;
cursor: pointer;
}

.titleContainer {
display: inline-flex;
align-items: center;
justify-content: flex-start;
}
.draftBadge {
border-radius: 16px;
background-color: black;
color: white;
font-size: 14px;
font-weight: 200;
padding: 2px 7px;
margin-right: 6px;
}

.noPosts {
text-align: center;
}
Expand Down
Loading

0 comments on commit d96d3f2

Please sign in to comment.