This commit is contained in:
Travis Fischer
2022-04-07 14:53:08 -04:00
parent 679131ad22
commit 45b20d9622
10 changed files with 196 additions and 86 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react'
import useDarkMode from '@fisch0920/use-dark-mode'
import { FaTwitter } from '@react-icons/all-files/fa/FaTwitter'
import { FaZhihu } from '@react-icons/all-files/fa/FaZhihu'
import { FaGithub } from '@react-icons/all-files/fa/FaGithub'
@@ -11,39 +12,31 @@ import styles from './styles.module.css'
// TODO: merge the data and icons from PageSocial with the social links in Footer
export const Footer: React.FC<{
isDarkMode: boolean
toggleDarkMode: () => void
}> = ({ isDarkMode, toggleDarkMode }) => {
const [hasMounted, setHasMounted] = React.useState(false)
const toggleDarkModeCb = React.useCallback(
export const FooterImpl: React.FC = () => {
const darkMode = useDarkMode(false, { classNameDark: 'dark-mode' })
const onToggleDarkMode = React.useCallback(
(e) => {
e.preventDefault()
toggleDarkMode()
darkMode.toggle()
},
[toggleDarkMode]
[darkMode]
)
React.useEffect(() => {
setHasMounted(true)
}, [])
return (
<footer className={styles.footer}>
<div className={styles.copyright}>Copyright 2022 {config.author}</div>
{hasMounted ? (
<div className={styles.settings}>
<a
className={styles.toggleDarkMode}
href='#'
onClick={toggleDarkModeCb}
title='Toggle dark mode'
>
{isDarkMode ? <IoMoonSharp /> : <IoSunnyOutline />}
</a>
</div>
) : null}
<div className={styles.settings}>
<a
className={styles.toggleDarkMode}
href='#'
role='button'
onClick={onToggleDarkMode}
>
{darkMode.value ? <IoMoonSharp /> : <IoSunnyOutline />}
</a>
</div>
<div className={styles.social}>
{config.twitter && (
@@ -97,3 +90,5 @@ export const Footer: React.FC<{
</footer>
)
}
export const Footer = React.memo(FooterImpl)

View File

@@ -58,7 +58,11 @@ const Pdf = dynamic(
}
)
const Modal = dynamic(
() => import('react-notion-x/build/third-party/modal').then((m) => m.Modal),
() =>
import('react-notion-x/build/third-party/modal').then((m) => {
m.Modal.setAppElement('.notion-viewport')
return m.Modal
}),
{
ssr: false
}
@@ -73,15 +77,48 @@ export const NotionPage: React.FC<types.PageProps> = ({
const router = useRouter()
const lite = useSearchParam('lite')
const params: any = {}
if (lite) params.lite = lite
const components = React.useMemo(
() => ({
nextImage: Image,
nextLink: Link,
Code,
Collection,
Equation,
Pdf,
Modal,
Tweet,
Header: NotionPageHeader
}),
[]
)
const twitterContextValue = React.useMemo(() => {
if (!recordMap) {
return null
}
return {
tweetAstMap: (recordMap as any).tweetAstMap || {},
swrOptions: {
fetcher: (id: string) =>
fetch(`/api/get-tweet-ast/${id}`).then((r) => r.json())
}
}
}, [recordMap])
// lite mode is for oembed
const isLiteMode = lite === 'true'
const searchParams = new URLSearchParams(params)
const darkMode = useDarkMode(false, { classNameDark: 'dark-mode' })
const siteMapPageUrl = React.useMemo(() => {
const params: any = {}
if (lite) params.lite = lite
const searchParams = new URLSearchParams(params)
return mapPageUrl(site, recordMap, searchParams)
}, [site, recordMap, lite])
if (router.isFallback) {
return <Loading />
}
@@ -111,8 +148,6 @@ export const NotionPage: React.FC<types.PageProps> = ({
g.block = block
}
const siteMapPageUrl = mapPageUrl(site, recordMap, searchParams)
const canonicalPageUrl =
!config.isDev && getCanonicalPageUrl(site, recordMap)(pageId)
@@ -147,15 +182,7 @@ export const NotionPage: React.FC<types.PageProps> = ({
}
return (
<TwitterContextProvider
value={{
tweetAstMap: (recordMap as any).tweetAstMap || {},
swrOptions: {
fetcher: (id) =>
fetch(`/api/get-tweet-ast/${id}`).then((r) => r.json())
}
}}
>
<TwitterContextProvider value={twitterContextValue}>
<PageHead
pageId={pageId}
site={site}
@@ -174,17 +201,7 @@ export const NotionPage: React.FC<types.PageProps> = ({
styles.notion,
pageId === site.rootNotionPageId && 'index-page'
)}
components={{
nextImage: Image,
nextLink: Link,
Code,
Collection,
Equation,
Pdf,
Modal,
Tweet,
Header: NotionPageHeader
}}
components={components}
recordMap={recordMap}
rootPageId={site.rootNotionPageId}
rootDomain={site.domain}
@@ -199,14 +216,9 @@ export const NotionPage: React.FC<types.PageProps> = ({
defaultPageCoverPosition={config.defaultPageCoverPosition}
mapPageUrl={siteMapPageUrl}
mapImageUrl={mapImageUrl}
searchNotion={searchNotion}
searchNotion={config.isSearchEnabled ? searchNotion : null}
pageAside={pageAside}
footer={
<Footer
isDarkMode={darkMode.value}
toggleDarkMode={darkMode.toggle}
/>
}
footer={<Footer />}
/>
<GitHubShareButton />

View File

@@ -1,16 +1,27 @@
import React from 'react'
// import useDarkMode from '@fisch0920/use-dark-mode'
import cs from 'classnames'
import useDarkMode from '@fisch0920/use-dark-mode'
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline'
import { IoMoonSharp } from '@react-icons/all-files/io5/IoMoonSharp'
import { Header, Breadcrumbs, Search } from 'react-notion-x'
import { Header, Breadcrumbs, Search, useNotionContext } from 'react-notion-x'
import * as types from 'lib/types'
import { navigationStyle } from 'lib/config'
import { navigationStyle, navigationLinks, isSearchEnabled } from 'lib/config'
// import styles from './styles.module.css'
import styles from './styles.module.css'
export const NotionPageHeader: React.FC<{
block: types.CollectionViewPageBlock | types.PageBlock
}> = ({ block }) => {
const darkMode = useDarkMode(false, { classNameDark: 'dark-mode' })
const [hasMounted, setHasMounted] = React.useState(false)
const { components, mapPageUrl } = useNotionContext()
React.useEffect(() => {
setHasMounted(true)
}, [])
if (navigationStyle === 'default') {
return <Header block={block} />
}
@@ -19,7 +30,52 @@ export const NotionPageHeader: React.FC<{
<header className='notion-header'>
<div className='notion-nav-header'>
<Breadcrumbs block={block} rootOnly={true} />
<Search block={block} />
<div className='notion-nav-header-rhs breadcrumbs'>
{navigationLinks
?.map((link, index) => {
if (!link.pageId && !link.url) {
return null
}
if (link.pageId) {
return (
<components.PageLink
href={mapPageUrl(link.pageId)}
key={index}
className={cs(styles.navLink, 'breadcrumb', 'button')}
>
{link.title}
</components.PageLink>
)
} else {
return (
<components.Link
href={link.url}
key={index}
className={cs(styles.navLink, 'breadcrumb', 'button')}
>
{link.title}
</components.Link>
)
}
})
.filter(Boolean)}
<div
className={cs('breadcrumb', 'button')}
role='button'
onClick={darkMode.toggle}
>
{hasMounted && darkMode.value ? (
<IoMoonSharp />
) : (
<IoSunnyOutline />
)}
</div>
{isSearchEnabled && <Search block={block} title={null} />}
</div>
</div>
</header>
)

View File

@@ -8,6 +8,7 @@
import { parsePageId } from 'notion-utils'
import posthog from 'posthog-js'
import { getEnv, getSiteConfig } from './get-config-value'
import { NavigationLink } from './site-config'
import {
PageUrlOverridesInverseMap,
PageUrlOverridesMap,
@@ -93,6 +94,14 @@ export const navigationStyle: NavigationStyle = getSiteConfig(
'default'
)
export const navigationLinks: Array<NavigationLink | null> = getSiteConfig(
'navigationLinks',
null
)
// Optional site search
export const isSearchEnabled: boolean = getSiteConfig('isSearchEnabled', true)
// ----------------------------------------------------------------------------
// Optional redis instance for persisting preview images

View File

@@ -12,11 +12,13 @@ const uuid = !!includeNotionIdInUrls
export const mapPageUrl =
(site: Site, recordMap: ExtendedRecordMap, searchParams: URLSearchParams) =>
(pageId = '') => {
if (uuidToId(pageId) === site.rootNotionPageId) {
const pageUuid = parsePageId(pageId, { uuid: true })
if (uuidToId(pageUuid) === site.rootNotionPageId) {
return createUrl('/', searchParams)
} else {
return createUrl(
`/${getCanonicalPageId(pageId, recordMap, { uuid })}`,
`/${getCanonicalPageId(pageUuid, recordMap, { uuid })}`,
searchParams
)
}

View File

@@ -1,15 +1,46 @@
import pMap from 'p-map'
import { ExtendedRecordMap, SearchParams, SearchResults } from 'notion-types'
import { mergeRecordMaps } from 'notion-utils'
import { notion } from './notion-api'
import { getPreviewImageMap } from './preview-images'
import { getTweetAstMap } from './tweet-embeds'
import {
isPreviewImageSupportEnabled,
isTweetEmbedSupportEnabled
isTweetEmbedSupportEnabled,
navigationStyle,
navigationLinks
} from './config'
export async function getPage(pageId: string): Promise<ExtendedRecordMap> {
const recordMap = await notion.getPage(pageId)
let recordMap = await notion.getPage(pageId)
if (navigationStyle !== 'default') {
const navigationLinkPageIds = (navigationLinks || [])
.map((link) => link.pageId)
.filter(Boolean)
if (navigationLinkPageIds.length) {
const navigationLinkRecordMaps: ExtendedRecordMap[] = await pMap(
navigationLinkPageIds,
async (navigationLinkPageId) =>
notion.getPage(navigationLinkPageId, {
fetchMissingBlocks: false,
fetchCollections: false,
signFileUrls: false
}),
{
concurrency: 4
}
)
recordMap = navigationLinkRecordMaps.reduce(
(map, navigationLinkRecordMap) =>
mergeRecordMaps(map, navigationLinkRecordMap),
recordMap
)
}
}
if (isPreviewImageSupportEnabled) {
const previewImageMap = await getPreviewImageMap(recordMap)

View File

@@ -21,12 +21,20 @@ export interface SiteConfig {
isPreviewImageSupportEnabled?: boolean
isTweetEmbedSupportEnabled?: boolean
isRedisEnabled?: boolean
isSearchEnabled?: boolean
includeNotionIdInUrls?: boolean
pageUrlOverrides?: types.PageUrlOverridesMap
pageUrlAdditions?: types.PageUrlOverridesMap
navigationStyle?: types.NavigationStyle
navigationLinks?: Array<NavigationLink>
}
export interface NavigationLink {
title: string
pageId?: string
url?: string
}
export const siteConfig = (config: SiteConfig): SiteConfig => {

View File

@@ -67,20 +67,3 @@ export interface PageUrlOverridesInverseMap {
// (this overrides the built-in URL path generation for these pages)
[pageId: string]: string
}
export interface PreviewImage {
url: string
originalWidth: number
originalHeight: number
width: number
height: number
type: string
dataURIBase64: string
error?: string
statusCode?: number
}
export interface PreviewImageMap {
[url: string]: PreviewImage
}

View File

@@ -27,8 +27,6 @@ export default siteConfig({
defaultPageCover: null,
defaultPageCoverPosition: 0.5,
navigationStyle: 'custom',
// whether or not to enable support for LQIP preview images (optional)
isPreviewImageSupportEnabled: true,
@@ -45,5 +43,17 @@ export default siteConfig({
// '/foo': '067dd719a912471ea9a3ac10710e7fdf',
// '/bar': '0be6efce9daf42688f65c76b89f8eb27'
// }
pageUrlOverrides: null
pageUrlOverrides: null,
navigationStyle: 'custom',
navigationLinks: [
{
title: 'About',
pageId: 'f1199d37579b41cbabfc0b5174f4256a'
},
{
title: 'Contact',
pageId: '6a29ebcb935a4f0689fe661ab5f3b8d1'
}
]
})

View File

@@ -44,6 +44,10 @@
margin: 0 auto;
}
.notion-nav-header-rhs {
gap: 0.5rem;
}
.notion-gallery-grid {
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-gap: 6vmin;