mirror of
https://github.com/d0zingcat/nextjs-notion-starter-kit.git
synced 2026-05-13 15:09:47 +00:00
feat: update deps; fix eslint issues; fix bug with collections
This commit is contained in:
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -1,40 +1,29 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on: [push, pull_request]
|
on: [push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
name: Test Node.js ${{ matrix.node-version }}
|
name: Test Node.js ${{ matrix.node-version }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: true
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
node-version:
|
node-version:
|
||||||
- 18
|
- 20
|
||||||
- 22
|
- 22
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v4
|
- uses: pnpm/action-setup@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
- name: Install pnpm
|
|
||||||
uses: pnpm/action-setup@v3
|
|
||||||
id: pnpm-install
|
|
||||||
with:
|
|
||||||
version: 9.12.2
|
|
||||||
run_install: false
|
|
||||||
|
|
||||||
- name: Install Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- run: pnpm install --frozen-lockfile --strict-peer-dependencies
|
||||||
run: pnpm install --frozen-lockfile --strict-peer-dependencies
|
- run: pnpm test
|
||||||
|
|
||||||
- name: Run test
|
|
||||||
run: pnpm test
|
|
||||||
|
|
||||||
# TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets
|
# TODO Enable those lines below if you use a Redis cache, you'll also need to configure GitHub Repository Secrets
|
||||||
# env:
|
# env:
|
||||||
|
|||||||
@@ -4,6 +4,3 @@ dist/
|
|||||||
node_modules/
|
node_modules/
|
||||||
.next/
|
.next/
|
||||||
.vercel/
|
.vercel/
|
||||||
|
|
||||||
.demo/
|
|
||||||
.renderer/
|
|
||||||
|
|||||||
11
.prettierrc
11
.prettierrc
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"singleQuote": true,
|
|
||||||
"jsxSingleQuote": true,
|
|
||||||
"semi": false,
|
|
||||||
"useTabs": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"bracketSpacing": true,
|
|
||||||
"bracketSameLine": false,
|
|
||||||
"arrowParens": "always",
|
|
||||||
"trailingComma": "none"
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,7 @@ export function FooterImpl() {
|
|||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
const onToggleDarkMode = React.useCallback(
|
const onToggleDarkMode = React.useCallback(
|
||||||
(e) => {
|
(e: any) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
toggleDarkMode()
|
toggleDarkMode()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,36 +39,67 @@ const Code = dynamic(() =>
|
|||||||
import('react-notion-x/build/third-party/code').then(async (m) => {
|
import('react-notion-x/build/third-party/code').then(async (m) => {
|
||||||
// add / remove any prism syntaxes here
|
// add / remove any prism syntaxes here
|
||||||
await Promise.allSettled([
|
await Promise.allSettled([
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-markup-templating.js'),
|
import('prismjs/components/prism-markup-templating.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-markup.js'),
|
import('prismjs/components/prism-markup.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-bash.js'),
|
import('prismjs/components/prism-bash.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-c.js'),
|
import('prismjs/components/prism-c.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-cpp.js'),
|
import('prismjs/components/prism-cpp.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-csharp.js'),
|
import('prismjs/components/prism-csharp.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-docker.js'),
|
import('prismjs/components/prism-docker.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-java.js'),
|
import('prismjs/components/prism-java.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-js-templates.js'),
|
import('prismjs/components/prism-js-templates.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-coffeescript.js'),
|
import('prismjs/components/prism-coffeescript.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-diff.js'),
|
import('prismjs/components/prism-diff.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-git.js'),
|
import('prismjs/components/prism-git.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-go.js'),
|
import('prismjs/components/prism-go.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-graphql.js'),
|
import('prismjs/components/prism-graphql.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-handlebars.js'),
|
import('prismjs/components/prism-handlebars.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-less.js'),
|
import('prismjs/components/prism-less.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-makefile.js'),
|
import('prismjs/components/prism-makefile.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-markdown.js'),
|
import('prismjs/components/prism-markdown.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-objectivec.js'),
|
import('prismjs/components/prism-objectivec.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-ocaml.js'),
|
import('prismjs/components/prism-ocaml.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-python.js'),
|
import('prismjs/components/prism-python.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-reason.js'),
|
import('prismjs/components/prism-reason.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-rust.js'),
|
import('prismjs/components/prism-rust.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-sass.js'),
|
import('prismjs/components/prism-sass.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-scss.js'),
|
import('prismjs/components/prism-scss.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-solidity.js'),
|
import('prismjs/components/prism-solidity.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-sql.js'),
|
import('prismjs/components/prism-sql.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-stylus.js'),
|
import('prismjs/components/prism-stylus.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-swift.js'),
|
import('prismjs/components/prism-swift.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-wasm.js'),
|
import('prismjs/components/prism-wasm.js'),
|
||||||
|
// @ts-expect-error Ignore prisma types
|
||||||
import('prismjs/components/prism-yaml.js')
|
import('prismjs/components/prism-yaml.js')
|
||||||
])
|
])
|
||||||
return m.Code
|
return m.Code
|
||||||
@@ -112,7 +143,7 @@ function Tweet({ id }: { id: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const propertyLastEditedTimeValue = (
|
const propertyLastEditedTimeValue = (
|
||||||
{ block, pageHeader },
|
{ block, pageHeader }: any,
|
||||||
defaultFn: () => React.ReactNode
|
defaultFn: () => React.ReactNode
|
||||||
) => {
|
) => {
|
||||||
if (pageHeader && block?.last_edited_time) {
|
if (pageHeader && block?.last_edited_time) {
|
||||||
@@ -125,7 +156,7 @@ const propertyLastEditedTimeValue = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const propertyDateValue = (
|
const propertyDateValue = (
|
||||||
{ data, schema, pageHeader },
|
{ data, schema, pageHeader }: any,
|
||||||
defaultFn: () => React.ReactNode
|
defaultFn: () => React.ReactNode
|
||||||
) => {
|
) => {
|
||||||
if (pageHeader && schema?.name?.toLowerCase() === 'published') {
|
if (pageHeader && schema?.name?.toLowerCase() === 'published') {
|
||||||
@@ -142,7 +173,7 @@ const propertyDateValue = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const propertyTextValue = (
|
const propertyTextValue = (
|
||||||
{ schema, pageHeader },
|
{ schema, pageHeader }: any,
|
||||||
defaultFn: () => React.ReactNode
|
defaultFn: () => React.ReactNode
|
||||||
) => {
|
) => {
|
||||||
if (pageHeader && schema?.name?.toLowerCase() === 'author') {
|
if (pageHeader && schema?.name?.toLowerCase() === 'author') {
|
||||||
@@ -189,11 +220,11 @@ export function NotionPage({
|
|||||||
if (lite) params.lite = lite
|
if (lite) params.lite = lite
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(params)
|
const searchParams = new URLSearchParams(params)
|
||||||
return mapPageUrl(site, recordMap, searchParams)
|
return site ? mapPageUrl(site, recordMap!, searchParams) : undefined
|
||||||
}, [site, recordMap, lite])
|
}, [site, recordMap, lite])
|
||||||
|
|
||||||
const keys = Object.keys(recordMap?.block || {})
|
const keys = Object.keys(recordMap?.block || {})
|
||||||
const block = recordMap?.block?.[keys[0]]?.value
|
const block = recordMap?.block?.[keys[0]!]?.value
|
||||||
|
|
||||||
// const isRootPage =
|
// const isRootPage =
|
||||||
// parsePageId(block?.id) === parsePageId(site?.rootNotionPageId)
|
// parsePageId(block?.id) === parsePageId(site?.rootNotionPageId)
|
||||||
@@ -205,7 +236,11 @@ export function NotionPage({
|
|||||||
|
|
||||||
const pageAside = React.useMemo(
|
const pageAside = React.useMemo(
|
||||||
() => (
|
() => (
|
||||||
<PageAside block={block} recordMap={recordMap} isBlogPost={isBlogPost} />
|
<PageAside
|
||||||
|
block={block!}
|
||||||
|
recordMap={recordMap!}
|
||||||
|
isBlogPost={isBlogPost}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
[block, recordMap, isBlogPost]
|
[block, recordMap, isBlogPost]
|
||||||
)
|
)
|
||||||
@@ -238,8 +273,9 @@ export function NotionPage({
|
|||||||
g.block = block
|
g.block = block
|
||||||
}
|
}
|
||||||
|
|
||||||
const canonicalPageUrl =
|
const canonicalPageUrl = config.isDev
|
||||||
!config.isDev && getCanonicalPageUrl(site, recordMap)(pageId)
|
? undefined
|
||||||
|
: getCanonicalPageUrl(site, recordMap)(pageId)
|
||||||
|
|
||||||
const socialImage = mapImageUrl(
|
const socialImage = mapImageUrl(
|
||||||
getPageProperty<string>('Social Image', block, recordMap) ||
|
getPageProperty<string>('Social Image', block, recordMap) ||
|
||||||
@@ -286,7 +322,7 @@ export function NotionPage({
|
|||||||
defaultPageCoverPosition={config.defaultPageCoverPosition}
|
defaultPageCoverPosition={config.defaultPageCoverPosition}
|
||||||
mapPageUrl={siteMapPageUrl}
|
mapPageUrl={siteMapPageUrl}
|
||||||
mapImageUrl={mapImageUrl}
|
mapImageUrl={mapImageUrl}
|
||||||
searchNotion={config.isSearchEnabled ? searchNotion : null}
|
searchNotion={config.isSearchEnabled ? searchNotion : undefined}
|
||||||
pageAside={pageAside}
|
pageAside={pageAside}
|
||||||
footer={footer}
|
footer={footer}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function NotionPageHeader({
|
|||||||
<div className='notion-nav-header-rhs breadcrumbs'>
|
<div className='notion-nav-header-rhs breadcrumbs'>
|
||||||
{navigationLinks
|
{navigationLinks
|
||||||
?.map((link, index) => {
|
?.map((link, index) => {
|
||||||
if (!link.pageId && !link.url) {
|
if (!link?.pageId && !link?.url) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: rgb(55, 53, 47);
|
color: rgb(55, 53, 47);
|
||||||
caret-color: rgb(55, 53, 47);
|
caret-color: rgb(55, 53, 47);
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica,
|
font-family:
|
||||||
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica,
|
||||||
'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol';
|
'Apple Color Emoji', Arial, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol';
|
||||||
background-color: var(--bg-color);
|
background-color: var(--bg-color);
|
||||||
}
|
}
|
||||||
|
|||||||
22
eslint.config.js
Normal file
22
eslint.config.js
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { config } from '@fisch0920/config/eslint'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...config,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts', '**/*.tsx'],
|
||||||
|
rules: {
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'unicorn/no-array-reduce': 'off',
|
||||||
|
'unicorn/filename-case': 'off',
|
||||||
|
'unicorn/prefer-global-this': 'off',
|
||||||
|
'no-process-env': 'off',
|
||||||
|
'array-callback-return': 'off',
|
||||||
|
'jsx-a11y/click-events-have-key-events': 'off',
|
||||||
|
'jsx-a11y/no-static-element-interactions': 'off',
|
||||||
|
'jsx-a11y/media-has-caption': 'off',
|
||||||
|
'jsx-a11y/interactive-supports-focus': 'off',
|
||||||
|
'jsx-a11y/anchor-is-valid': 'off',
|
||||||
|
'@typescript-eslint/naming-convention': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -4,7 +4,7 @@ export async function pageAcl({
|
|||||||
site,
|
site,
|
||||||
recordMap,
|
recordMap,
|
||||||
pageId
|
pageId
|
||||||
}: PageProps): Promise<PageProps> {
|
}: PageProps): Promise<PageProps | undefined> {
|
||||||
if (!site) {
|
if (!site) {
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
|
|||||||
@@ -7,7 +7,11 @@
|
|||||||
import { parsePageId } from 'notion-utils'
|
import { parsePageId } from 'notion-utils'
|
||||||
import { type PostHogConfig } from 'posthog-js'
|
import { type PostHogConfig } from 'posthog-js'
|
||||||
|
|
||||||
import { getEnv, getSiteConfig } from './get-config-value'
|
import {
|
||||||
|
getEnv,
|
||||||
|
getRequiredSiteConfig,
|
||||||
|
getSiteConfig
|
||||||
|
} from './get-config-value'
|
||||||
import { type NavigationLink } from './site-config'
|
import { type NavigationLink } from './site-config'
|
||||||
import {
|
import {
|
||||||
type NavigationStyle,
|
type NavigationStyle,
|
||||||
@@ -19,17 +23,15 @@ import {
|
|||||||
export const rootNotionPageId: string = parsePageId(
|
export const rootNotionPageId: string = parsePageId(
|
||||||
getSiteConfig('rootNotionPageId'),
|
getSiteConfig('rootNotionPageId'),
|
||||||
{ uuid: false }
|
{ uuid: false }
|
||||||
)
|
)!
|
||||||
|
|
||||||
if (!rootNotionPageId) {
|
if (!rootNotionPageId) {
|
||||||
throw new Error('Config error invalid "rootNotionPageId"')
|
throw new Error('Config error invalid "rootNotionPageId"')
|
||||||
}
|
}
|
||||||
|
|
||||||
// if you want to restrict pages to a single notion workspace (optional)
|
// if you want to restrict pages to a single notion workspace (optional)
|
||||||
export const rootNotionSpaceId: string | null = parsePageId(
|
export const rootNotionSpaceId: string | null =
|
||||||
getSiteConfig('rootNotionSpaceId', null),
|
parsePageId(getSiteConfig('rootNotionSpaceId'), { uuid: true }) ?? null
|
||||||
{ uuid: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
export const pageUrlOverrides = cleanPageUrlMap(
|
export const pageUrlOverrides = cleanPageUrlMap(
|
||||||
getSiteConfig('pageUrlOverrides', {}) || {},
|
getSiteConfig('pageUrlOverrides', {}) || {},
|
||||||
@@ -47,24 +49,24 @@ export const environment = process.env.NODE_ENV || 'development'
|
|||||||
export const isDev = environment === 'development'
|
export const isDev = environment === 'development'
|
||||||
|
|
||||||
// general site config
|
// general site config
|
||||||
export const name: string = getSiteConfig('name')
|
export const name: string = getRequiredSiteConfig('name')
|
||||||
export const author: string = getSiteConfig('author')
|
export const author: string = getRequiredSiteConfig('author')
|
||||||
export const domain: string = getSiteConfig('domain')
|
export const domain: string = getRequiredSiteConfig('domain')
|
||||||
export const description: string = getSiteConfig('description', 'Notion Blog')
|
export const description: string = getSiteConfig('description', 'Notion Blog')
|
||||||
export const language: string = getSiteConfig('language', 'en')
|
export const language: string = getSiteConfig('language', 'en')
|
||||||
|
|
||||||
// social accounts
|
// social accounts
|
||||||
export const twitter: string | null = getSiteConfig('twitter', null)
|
export const twitter: string | undefined = getSiteConfig('twitter')
|
||||||
export const mastodon: string | null = getSiteConfig('mastodon', null)
|
export const mastodon: string | undefined = getSiteConfig('mastodon')
|
||||||
export const github: string | null = getSiteConfig('github', null)
|
export const github: string | undefined = getSiteConfig('github')
|
||||||
export const youtube: string | null = getSiteConfig('youtube', null)
|
export const youtube: string | undefined = getSiteConfig('youtube')
|
||||||
export const linkedin: string | null = getSiteConfig('linkedin', null)
|
export const linkedin: string | undefined = getSiteConfig('linkedin')
|
||||||
export const newsletter: string | null = getSiteConfig('newsletter', null)
|
export const newsletter: string | undefined = getSiteConfig('newsletter')
|
||||||
export const zhihu: string | null = getSiteConfig('zhihu', null)
|
export const zhihu: string | undefined = getSiteConfig('zhihu')
|
||||||
|
|
||||||
export const getMastodonHandle = (): string | null => {
|
export const getMastodonHandle = (): string | undefined => {
|
||||||
if (!mastodon) {
|
if (!mastodon) {
|
||||||
return null
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since Mastodon is decentralized, handles include the instance domain name.
|
// Since Mastodon is decentralized, handles include the instance domain name.
|
||||||
@@ -74,14 +76,10 @@ export const getMastodonHandle = (): string | null => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// default notion values for site-wide consistency (optional; may be overridden on a per-page basis)
|
// default notion values for site-wide consistency (optional; may be overridden on a per-page basis)
|
||||||
export const defaultPageIcon: string | null = getSiteConfig(
|
export const defaultPageIcon: string | undefined =
|
||||||
'defaultPageIcon',
|
getSiteConfig('defaultPageIcon')
|
||||||
null
|
export const defaultPageCover: string | undefined =
|
||||||
)
|
getSiteConfig('defaultPageCover')
|
||||||
export const defaultPageCover: string | null = getSiteConfig(
|
|
||||||
'defaultPageCover',
|
|
||||||
null
|
|
||||||
)
|
|
||||||
export const defaultPageCoverPosition: number = getSiteConfig(
|
export const defaultPageCoverPosition: number = getSiteConfig(
|
||||||
'defaultPageCoverPosition',
|
'defaultPageCoverPosition',
|
||||||
0.5
|
0.5
|
||||||
@@ -104,7 +102,7 @@ export const navigationStyle: NavigationStyle = getSiteConfig(
|
|||||||
'default'
|
'default'
|
||||||
)
|
)
|
||||||
|
|
||||||
export const navigationLinks: Array<NavigationLink | null> = getSiteConfig(
|
export const navigationLinks: Array<NavigationLink | undefined> = getSiteConfig(
|
||||||
'navigationLinks',
|
'navigationLinks',
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
@@ -115,19 +113,18 @@ export const isSearchEnabled: boolean = getSiteConfig('isSearchEnabled', true)
|
|||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Optional redis instance for persisting preview images
|
// Optional redis instance for persisting preview images
|
||||||
export const isRedisEnabled: boolean =
|
export const isRedisEnabled: boolean = getSiteConfig('isRedisEnabled', false)
|
||||||
getSiteConfig('isRedisEnabled', false) || !!getEnv('REDIS_ENABLED', null)
|
|
||||||
|
|
||||||
// (if you want to enable redis, only REDIS_HOST and REDIS_PASSWORD are required)
|
// (if you want to enable redis, only REDIS_HOST and REDIS_PASSWORD are required)
|
||||||
// we recommend that you store these in a local `.env` file
|
// we recommend that you store these in a local `.env` file
|
||||||
export const redisHost: string | null = getEnv('REDIS_HOST', null)
|
export const redisHost: string | undefined = getEnv('REDIS_HOST')
|
||||||
export const redisPassword: string | null = getEnv('REDIS_PASSWORD', null)
|
export const redisPassword: string | undefined = getEnv('REDIS_PASSWORD')
|
||||||
export const redisUser: string = getEnv('REDIS_USER', 'default')
|
export const redisUser: string | undefined = getEnv('REDIS_USER', 'default')
|
||||||
export const redisUrl = getEnv(
|
export const redisUrl = getEnv(
|
||||||
'REDIS_URL',
|
'REDIS_URL',
|
||||||
`redis://${redisUser}:${redisPassword}@${redisHost}`
|
`redis://${redisUser}:${redisPassword}@${redisHost}`
|
||||||
)
|
)
|
||||||
export const redisNamespace: string | null = getEnv(
|
export const redisNamespace: string | undefined = getEnv(
|
||||||
'REDIS_NAMESPACE',
|
'REDIS_NAMESPACE',
|
||||||
'preview-images'
|
'preview-images'
|
||||||
)
|
)
|
||||||
@@ -160,7 +157,7 @@ export const site: Site = {
|
|||||||
description
|
description
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fathomId = isDev ? null : process.env.NEXT_PUBLIC_FATHOM_ID
|
export const fathomId = isDev ? undefined : process.env.NEXT_PUBLIC_FATHOM_ID
|
||||||
export const fathomConfig = fathomId
|
export const fathomConfig = fathomId
|
||||||
? {
|
? {
|
||||||
excludedDomains: ['localhost', 'localhost:3000']
|
excludedDomains: ['localhost', 'localhost:3000']
|
||||||
@@ -211,7 +208,7 @@ function invertPageUrlOverrides(
|
|||||||
pageUrlOverrides: PageUrlOverridesMap
|
pageUrlOverrides: PageUrlOverridesMap
|
||||||
): PageUrlOverridesInverseMap {
|
): PageUrlOverridesInverseMap {
|
||||||
return Object.keys(pageUrlOverrides).reduce((acc, uri) => {
|
return Object.keys(pageUrlOverrides).reduce((acc, uri) => {
|
||||||
const pageId = pageUrlOverrides[uri]
|
const pageId = pageUrlOverrides[uri]!
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { isRedisEnabled, redisNamespace, redisUrl } from './config'
|
|||||||
|
|
||||||
let db: Keyv
|
let db: Keyv
|
||||||
if (isRedisEnabled) {
|
if (isRedisEnabled) {
|
||||||
const keyvRedis = new KeyvRedis(redisUrl)
|
const keyvRedis = new KeyvRedis(redisUrl!)
|
||||||
db = new Keyv({ store: keyvRedis, namespace: redisNamespace || undefined })
|
db = new Keyv({ store: keyvRedis, namespace: redisNamespace || undefined })
|
||||||
} else {
|
} else {
|
||||||
db = new Keyv()
|
db = new Keyv()
|
||||||
|
|||||||
@@ -10,18 +10,20 @@ export function getCanonicalPageId(
|
|||||||
pageId: string,
|
pageId: string,
|
||||||
recordMap: ExtendedRecordMap,
|
recordMap: ExtendedRecordMap,
|
||||||
{ uuid = true }: { uuid?: boolean } = {}
|
{ uuid = true }: { uuid?: boolean } = {}
|
||||||
): string | null {
|
): string | undefined {
|
||||||
const cleanPageId = parsePageId(pageId, { uuid: false })
|
const cleanPageId = parsePageId(pageId, { uuid: false })
|
||||||
if (!cleanPageId) {
|
if (!cleanPageId) {
|
||||||
return null
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const override = inversePageUrlOverrides[cleanPageId]
|
const override = inversePageUrlOverrides[cleanPageId]
|
||||||
if (override) {
|
if (override) {
|
||||||
return override
|
return override
|
||||||
} else {
|
} else {
|
||||||
return getCanonicalPageIdImpl(pageId, recordMap, {
|
return (
|
||||||
uuid
|
getCanonicalPageIdImpl(pageId, recordMap, {
|
||||||
})
|
uuid
|
||||||
|
}) ?? undefined
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ if (!rawSiteConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allow environment variables to override site.config.ts
|
// allow environment variables to override site.config.ts
|
||||||
let siteConfigOverrides: SiteConfig
|
let siteConfigOverrides: SiteConfig | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (process.env.NEXT_PUBLIC_SITE_CONFIG) {
|
if (process.env.NEXT_PUBLIC_SITE_CONFIG) {
|
||||||
siteConfigOverrides = JSON.parse(process.env.NEXT_PUBLIC_SITE_CONFIG)
|
siteConfigOverrides = JSON.parse(
|
||||||
|
process.env.NEXT_PUBLIC_SITE_CONFIG
|
||||||
|
) as SiteConfig
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Invalid config "NEXT_PUBLIC_SITE_CONFIG" failed to parse')
|
console.error('Invalid config "NEXT_PUBLIC_SITE_CONFIG" failed to parse')
|
||||||
@@ -22,25 +24,36 @@ const siteConfig: SiteConfig = {
|
|||||||
...siteConfigOverrides
|
...siteConfigOverrides
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSiteConfig<T>(key: string, defaultValue?: T): T {
|
export function getSiteConfig<T, TDefault>(
|
||||||
const value = siteConfig[key]
|
key: string,
|
||||||
|
defaultValue?: TDefault
|
||||||
|
): TDefault extends undefined ? T | undefined : T {
|
||||||
|
const value = siteConfig[key as keyof SiteConfig]
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
return value
|
return value as T
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultValue !== undefined) {
|
return defaultValue as TDefault extends undefined ? T | undefined : T
|
||||||
return defaultValue
|
}
|
||||||
|
|
||||||
|
export function getRequiredSiteConfig<T>(key: string): T {
|
||||||
|
const value = siteConfig[key as keyof SiteConfig]
|
||||||
|
|
||||||
|
if (value !== undefined) {
|
||||||
|
return value as T
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Config error: missing required site config value "${key}"`)
|
throw new Error(`Config error: missing required site config value "${key}"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isServer = typeof window === 'undefined'
|
||||||
|
|
||||||
export function getEnv(
|
export function getEnv(
|
||||||
key: string,
|
key: string,
|
||||||
defaultValue?: string,
|
defaultValue?: string,
|
||||||
env = process.env
|
env = process.env
|
||||||
): string {
|
): string | undefined {
|
||||||
const value = env[key]
|
const value = env[key]
|
||||||
|
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
@@ -51,5 +64,7 @@ export function getEnv(
|
|||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Config error: missing required env variable "${key}"`)
|
if (isServer) {
|
||||||
|
throw new Error(`Config error: missing required env variable "${key}"`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const uuid = !!includeNotionIdInUrls
|
|||||||
export async function getSiteMap(): Promise<types.SiteMap> {
|
export async function getSiteMap(): Promise<types.SiteMap> {
|
||||||
const partialSiteMap = await getAllPages(
|
const partialSiteMap = await getAllPages(
|
||||||
config.rootNotionPageId,
|
config.rootNotionPageId,
|
||||||
config.rootNotionSpaceId
|
config.rootNotionSpaceId ?? undefined
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -25,14 +25,19 @@ const getAllPages = pMemoize(getAllPagesImpl, {
|
|||||||
cacheKey: (...args) => JSON.stringify(args)
|
cacheKey: (...args) => JSON.stringify(args)
|
||||||
})
|
})
|
||||||
|
|
||||||
const getPage = async (pageId: string, ...args) => {
|
const getPage = async (pageId: string, opts?: any) => {
|
||||||
console.log('\nnotion getPage', uuidToId(pageId))
|
console.log('\nnotion getPage', uuidToId(pageId))
|
||||||
return notion.getPage(pageId, ...args)
|
return notion.getPage(pageId, {
|
||||||
|
kyOptions: {
|
||||||
|
timeout: 30_000
|
||||||
|
},
|
||||||
|
...opts
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAllPagesImpl(
|
async function getAllPagesImpl(
|
||||||
rootNotionPageId: string,
|
rootNotionPageId: string,
|
||||||
rootNotionSpaceId: string
|
rootNotionSpaceId?: string
|
||||||
): Promise<Partial<types.SiteMap>> {
|
): Promise<Partial<types.SiteMap>> {
|
||||||
const pageMap = await getAllPagesInSpace(
|
const pageMap = await getAllPagesInSpace(
|
||||||
rootNotionPageId,
|
rootNotionPageId,
|
||||||
@@ -41,7 +46,7 @@ async function getAllPagesImpl(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const canonicalPageMap = Object.keys(pageMap).reduce(
|
const canonicalPageMap = Object.keys(pageMap).reduce(
|
||||||
(map, pageId: string) => {
|
(map: Record<string, string>, pageId: string) => {
|
||||||
const recordMap = pageMap[pageId]
|
const recordMap = pageMap[pageId]
|
||||||
if (!recordMap) {
|
if (!recordMap) {
|
||||||
throw new Error(`Error loading page "${pageId}"`)
|
throw new Error(`Error loading page "${pageId}"`)
|
||||||
@@ -49,14 +54,14 @@ async function getAllPagesImpl(
|
|||||||
|
|
||||||
const block = recordMap.block[pageId]?.value
|
const block = recordMap.block[pageId]?.value
|
||||||
if (
|
if (
|
||||||
!(getPageProperty<boolean | null>('Public', block, recordMap) ?? true)
|
!(getPageProperty<boolean | null>('Public', block!, recordMap) ?? true)
|
||||||
) {
|
) {
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
const canonicalPageId = getCanonicalPageId(pageId, recordMap, {
|
const canonicalPageId = getCanonicalPageId(pageId, recordMap, {
|
||||||
uuid
|
uuid
|
||||||
})
|
})!
|
||||||
|
|
||||||
if (map[canonicalPageId]) {
|
if (map[canonicalPageId]) {
|
||||||
// you can have multiple pages in different collections that have the same id
|
// you can have multiple pages in different collections that have the same id
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { api, host } from './config'
|
import { api, host } from './config'
|
||||||
|
|
||||||
export function getSocialImageUrl(pageId: string) {
|
export function getSocialImageUrl(pageId: string | undefined) {
|
||||||
try {
|
try {
|
||||||
const url = new URL(api.getSocialImage, host)
|
const url = new URL(api.getSocialImage, host)
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ export function getSocialImageUrl(pageId: string) {
|
|||||||
url.searchParams.set('id', pageId)
|
url.searchParams.set('id', pageId)
|
||||||
return url.toString()
|
return url.toString()
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.warn('error invalid social image url', pageId, err.message)
|
console.warn('error invalid social image url', pageId, err.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async function getTweetImpl(tweetId: string): Promise<any> {
|
|||||||
if (cachedTweet || cachedTweet === null) {
|
if (cachedTweet || cachedTweet === null) {
|
||||||
return cachedTweet
|
return cachedTweet
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error get "${cacheKey}"`, err.message)
|
console.warn(`redis error get "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ async function getTweetImpl(tweetId: string): Promise<any> {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await db.set(cacheKey, tweetData)
|
await db.set(cacheKey, tweetData)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error set "${cacheKey}"`, err.message)
|
console.warn(`redis error set "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const uuid = !!includeNotionIdInUrls
|
|||||||
export const mapPageUrl =
|
export const mapPageUrl =
|
||||||
(site: Site, recordMap: ExtendedRecordMap, searchParams: URLSearchParams) =>
|
(site: Site, recordMap: ExtendedRecordMap, searchParams: URLSearchParams) =>
|
||||||
(pageId = '') => {
|
(pageId = '') => {
|
||||||
const pageUuid = parsePageId(pageId, { uuid: true })
|
const pageUuid = parsePageId(pageId, { uuid: true })!
|
||||||
|
|
||||||
if (uuidToId(pageUuid) === site.rootNotionPageId) {
|
if (uuidToId(pageUuid) === site.rootNotionPageId) {
|
||||||
return createUrl('/', searchParams)
|
return createUrl('/', searchParams)
|
||||||
@@ -27,7 +27,7 @@ export const mapPageUrl =
|
|||||||
export const getCanonicalPageUrl =
|
export const getCanonicalPageUrl =
|
||||||
(site: Site, recordMap: ExtendedRecordMap) =>
|
(site: Site, recordMap: ExtendedRecordMap) =>
|
||||||
(pageId = '') => {
|
(pageId = '') => {
|
||||||
const pageUuid = parsePageId(pageId, { uuid: true })
|
const pageUuid = parsePageId(pageId, { uuid: true })!
|
||||||
|
|
||||||
if (uuidToId(pageId) === site.rootNotionPageId) {
|
if (uuidToId(pageId) === site.rootNotionPageId) {
|
||||||
return `https://${site.domain}`
|
return `https://${site.domain}`
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { getPreviewImageMap } from './preview-images'
|
|||||||
const getNavigationLinkPages = pMemoize(
|
const getNavigationLinkPages = pMemoize(
|
||||||
async (): Promise<ExtendedRecordMap[]> => {
|
async (): Promise<ExtendedRecordMap[]> => {
|
||||||
const navigationLinkPageIds = (navigationLinks || [])
|
const navigationLinkPageIds = (navigationLinks || [])
|
||||||
.map((link) => link.pageId)
|
.map((link) => link?.pageId)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
if (navigationStyle !== 'default' && navigationLinkPageIds.length) {
|
if (navigationStyle !== 'default' && navigationLinkPageIds.length) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const oembed = async ({
|
|||||||
dark?: boolean
|
dark?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: handle pages with no pageId via domain
|
// TODO: handle pages with no pageId via domain
|
||||||
const pageId = parsePageId(url)
|
const pageId = parsePageId(url)!
|
||||||
|
|
||||||
let title = config.name
|
let title = config.name
|
||||||
let authorName = config.author
|
let authorName = config.author
|
||||||
@@ -26,7 +26,7 @@ export const oembed = async ({
|
|||||||
const pageTitle = getPageTitle(page)
|
const pageTitle = getPageTitle(page)
|
||||||
if (pageTitle) title = pageTitle
|
if (pageTitle) title = pageTitle
|
||||||
|
|
||||||
const user = page.notion_user[Object.keys(page.notion_user)[0]]?.value
|
const user = page.notion_user[Object.keys(page.notion_user)[0]!]!.value
|
||||||
const name = [user.given_name, user.family_name]
|
const name = [user.given_name, user.family_name]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(' ')
|
.join(' ')
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export async function getPreviewImageMap(
|
|||||||
const urls: string[] = getPageImageUrls(recordMap, {
|
const urls: string[] = getPageImageUrls(recordMap, {
|
||||||
mapImageUrl
|
mapImageUrl
|
||||||
})
|
})
|
||||||
.concat([defaultPageIcon, defaultPageCover])
|
.concat([defaultPageIcon, defaultPageCover].filter(Boolean))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|
||||||
const previewImagesMap = Object.fromEntries(
|
const previewImagesMap = Object.fromEntries(
|
||||||
@@ -48,7 +48,7 @@ async function createPreviewImage(
|
|||||||
if (cachedPreviewImage) {
|
if (cachedPreviewImage) {
|
||||||
return cachedPreviewImage
|
return cachedPreviewImage
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error get "${cacheKey}"`, err.message)
|
console.warn(`redis error get "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
@@ -65,13 +65,13 @@ async function createPreviewImage(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await db.set(cacheKey, previewImage)
|
await db.set(cacheKey, previewImage)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error set "${cacheKey}"`, err.message)
|
console.warn(`redis error set "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return previewImage
|
return previewImage
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.warn('failed to create preview image', url, err.message)
|
console.warn('failed to create preview image', url, err.message)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
1
lib/reset.d.ts
vendored
Normal file
1
lib/reset.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
import '@fisch0920/config/ts-reset'
|
||||||
@@ -1,18 +1,22 @@
|
|||||||
import { type ExtendedRecordMap } from 'notion-types'
|
import { type ExtendedRecordMap } from 'notion-types'
|
||||||
import { parsePageId } from 'notion-utils'
|
import { parsePageId } from 'notion-utils'
|
||||||
|
|
||||||
|
import type { PageProps } from './types'
|
||||||
import * as acl from './acl'
|
import * as acl from './acl'
|
||||||
import { environment, pageUrlAdditions, pageUrlOverrides, site } from './config'
|
import { environment, pageUrlAdditions, pageUrlOverrides, site } from './config'
|
||||||
import { db } from './db'
|
import { db } from './db'
|
||||||
import { getSiteMap } from './get-site-map'
|
import { getSiteMap } from './get-site-map'
|
||||||
import { getPage } from './notion'
|
import { getPage } from './notion'
|
||||||
|
|
||||||
export async function resolveNotionPage(domain: string, rawPageId?: string) {
|
export async function resolveNotionPage(
|
||||||
let pageId: string
|
domain: string,
|
||||||
|
rawPageId?: string
|
||||||
|
): Promise<PageProps> {
|
||||||
|
let pageId: string | undefined
|
||||||
let recordMap: ExtendedRecordMap
|
let recordMap: ExtendedRecordMap
|
||||||
|
|
||||||
if (rawPageId && rawPageId !== 'index') {
|
if (rawPageId && rawPageId !== 'index') {
|
||||||
pageId = parsePageId(rawPageId)
|
pageId = parsePageId(rawPageId)!
|
||||||
|
|
||||||
if (!pageId) {
|
if (!pageId) {
|
||||||
// check if the site configuration provides an override or a fallback for
|
// check if the site configuration provides an override or a fallback for
|
||||||
@@ -21,7 +25,7 @@ export async function resolveNotionPage(domain: string, rawPageId?: string) {
|
|||||||
pageUrlOverrides[rawPageId] || pageUrlAdditions[rawPageId]
|
pageUrlOverrides[rawPageId] || pageUrlAdditions[rawPageId]
|
||||||
|
|
||||||
if (override) {
|
if (override) {
|
||||||
pageId = parsePageId(override)
|
pageId = parsePageId(override)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,7 +41,7 @@ export async function resolveNotionPage(domain: string, rawPageId?: string) {
|
|||||||
pageId = await db.get(cacheKey)
|
pageId = await db.get(cacheKey)
|
||||||
|
|
||||||
// console.log(`redis get "${cacheKey}"`, pageId)
|
// console.log(`redis get "${cacheKey}"`, pageId)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error get "${cacheKey}"`, err.message)
|
console.warn(`redis error get "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
@@ -64,7 +68,7 @@ export async function resolveNotionPage(domain: string, rawPageId?: string) {
|
|||||||
await db.set(cacheKey, pageId, cacheTTL)
|
await db.set(cacheKey, pageId, cacheTTL)
|
||||||
|
|
||||||
// console.log(`redis set "${cacheKey}"`, pageId, { cacheTTL })
|
// console.log(`redis set "${cacheKey}"`, pageId, { cacheTTL })
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
// ignore redis errors
|
// ignore redis errors
|
||||||
console.warn(`redis error set "${cacheKey}"`, err.message)
|
console.warn(`redis error set "${cacheKey}"`, err.message)
|
||||||
}
|
}
|
||||||
@@ -86,6 +90,6 @@ export async function resolveNotionPage(domain: string, rawPageId?: string) {
|
|||||||
recordMap = await getPage(pageId)
|
recordMap = await getPage(pageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = { site, recordMap, pageId }
|
const props: PageProps = { site, recordMap, pageId }
|
||||||
return { ...props, ...(await acl.pageAcl(props)) }
|
return { ...props, ...(await acl.pageAcl(props)) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ async function searchNotionImpl(
|
|||||||
error.response = res
|
error.response = res
|
||||||
throw error
|
throw error
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json() as Promise<types.SearchResults>)
|
||||||
|
|
||||||
// return ky
|
// return ky
|
||||||
// .post(api.searchNotion, {
|
// .post(api.searchNotion, {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import type * as types from './types'
|
|||||||
|
|
||||||
export interface SiteConfig {
|
export interface SiteConfig {
|
||||||
rootNotionPageId: string
|
rootNotionPageId: string
|
||||||
rootNotionSpaceId?: string
|
rootNotionSpaceId?: string | null
|
||||||
|
|
||||||
name: string
|
name: string
|
||||||
domain: string
|
domain: string
|
||||||
@@ -28,8 +28,8 @@ export interface SiteConfig {
|
|||||||
isSearchEnabled?: boolean
|
isSearchEnabled?: boolean
|
||||||
|
|
||||||
includeNotionIdInUrls?: boolean
|
includeNotionIdInUrls?: boolean
|
||||||
pageUrlOverrides?: types.PageUrlOverridesMap
|
pageUrlOverrides?: types.PageUrlOverridesMap | null
|
||||||
pageUrlAdditions?: types.PageUrlOverridesMap
|
pageUrlAdditions?: types.PageUrlOverridesMap | null
|
||||||
|
|
||||||
navigationStyle?: types.NavigationStyle
|
navigationStyle?: types.NavigationStyle
|
||||||
navigationLinks?: Array<NavigationLink>
|
navigationLinks?: Array<NavigationLink>
|
||||||
|
|||||||
12
lib/types.ts
12
lib/types.ts
@@ -31,7 +31,7 @@ export interface Site {
|
|||||||
domain: string
|
domain: string
|
||||||
|
|
||||||
rootNotionPageId: string
|
rootNotionPageId: string
|
||||||
rootNotionSpaceId: string
|
rootNotionSpaceId: string | null
|
||||||
|
|
||||||
// settings
|
// settings
|
||||||
html?: string
|
html?: string
|
||||||
@@ -69,9 +69,9 @@ export interface PageUrlOverridesInverseMap {
|
|||||||
export interface NotionPageInfo {
|
export interface NotionPageInfo {
|
||||||
pageId: string
|
pageId: string
|
||||||
title: string
|
title: string
|
||||||
image: string
|
image?: string
|
||||||
imageObjectPosition: string
|
imageObjectPosition?: string
|
||||||
author: string
|
author?: string
|
||||||
authorImage: string
|
authorImage?: string
|
||||||
detail: string
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
|||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.
|
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import bundleAnalyzer from '@next/bundle-analyzer'
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
import bundleAnalyzer from '@next/bundle-analyzer'
|
||||||
|
|
||||||
const withBundleAnalyzer = bundleAnalyzer({
|
const withBundleAnalyzer = bundleAnalyzer({
|
||||||
|
// eslint-disable-next-line no-process-env
|
||||||
enabled: process.env.ANALYZE === 'true'
|
enabled: process.env.ANALYZE === 'true'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ export default withBundleAnalyzer({
|
|||||||
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;"
|
contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;"
|
||||||
},
|
},
|
||||||
|
|
||||||
webpack: (config, _context) => {
|
webpack: (config) => {
|
||||||
// Workaround for ensuring that `react` and `react-dom` resolve correctly
|
// Workaround for ensuring that `react` and `react-dom` resolve correctly
|
||||||
// when using a locally-linked version of `react-notion-x`.
|
// when using a locally-linked version of `react-notion-x`.
|
||||||
// @see https://github.com/vercel/next.js/issues/50391
|
// @see https://github.com/vercel/next.js/issues/50391
|
||||||
|
|||||||
56
package.json
56
package.json
@@ -6,6 +6,7 @@
|
|||||||
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||||
"repository": "transitive-bullshit/nextjs-notion-starter-kit",
|
"repository": "transitive-bullshit/nextjs-notion-starter-kit",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"packageManager": "pnpm@10.11.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
@@ -37,38 +38,51 @@
|
|||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"expiry-map": "^2.0.0",
|
"expiry-map": "^2.0.0",
|
||||||
"fathom-client": "^3.4.1",
|
"fathom-client": "^3.4.1",
|
||||||
"ky": "^1.7.2",
|
"ky": "^1.8.1",
|
||||||
"lqip-modern": "^2.2.1",
|
"lqip-modern": "^2.2.1",
|
||||||
"next": "^15.0.3",
|
"next": "^15.3.3",
|
||||||
"notion-client": "^7.1.3",
|
"notion-client": "^7.4.0",
|
||||||
"notion-types": "^7.1.3",
|
"notion-types": "^7.4.0",
|
||||||
"notion-utils": "^7.1.3",
|
"notion-utils": "^7.4.0",
|
||||||
"p-map": "^7.0.2",
|
"p-map": "^7.0.3",
|
||||||
"p-memoize": "^7.1.1",
|
"p-memoize": "^7.1.1",
|
||||||
"posthog-js": "^1.181.0",
|
"posthog-js": "^1.249.4",
|
||||||
"prismjs": "^1.29.0",
|
"prismjs": "^1.30.0",
|
||||||
"react": "^18.2.0",
|
"react": "^19.1.0",
|
||||||
"react-body-classname": "^1.3.1",
|
"react-body-classname": "^1.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-notion-x": "^7.2.3",
|
"react-notion-x": "^7.4.0",
|
||||||
"react-tweet": "^3.2.1",
|
"react-tweet": "^3.2.2",
|
||||||
"react-use": "^17.4.2",
|
"react-use": "^17.6.0",
|
||||||
"rss": "^1.2.2"
|
"rss": "^1.2.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fisch0920/eslint-config": "^1.4.0",
|
"@fisch0920/config": "^1.1.2",
|
||||||
"@next/bundle-analyzer": "^15.0.2",
|
"@next/bundle-analyzer": "^15.3.3",
|
||||||
"@types/node": "^22.8.6",
|
"@types/node": "^22.15.30",
|
||||||
"@types/react": "^18.0.21",
|
"@types/prismjs": "^1.26.5",
|
||||||
|
"@types/react": "^19.1.6",
|
||||||
|
"@types/react-body-classname": "^1.1.10",
|
||||||
|
"@types/rss": "^0.0.32",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^9.28.0",
|
||||||
"npm-run-all2": "^7.0.1",
|
"npm-run-all2": "^8.0.4",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.5.3",
|
||||||
"typescript": "^5.6.3"
|
"typescript": "^5.8.3"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"cacheable-request": {
|
"cacheable-request": {
|
||||||
"keyv": "npm:@keyvhq/core@~1.6.6"
|
"keyv": "npm:@keyvhq/core@~1.6.6"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"prettier": "@fisch0920/config/prettier",
|
||||||
|
"simple-git-hooks": {
|
||||||
|
"pre-commit": "npx lint-staged"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{ts,tsx}": [
|
||||||
|
"prettier --ignore-unknown --write",
|
||||||
|
"eslint --fix"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { type PageProps, type Params } from '@/lib/types'
|
|||||||
export const getStaticProps: GetStaticProps<PageProps, Params> = async (
|
export const getStaticProps: GetStaticProps<PageProps, Params> = async (
|
||||||
context
|
context
|
||||||
) => {
|
) => {
|
||||||
const rawPageId = context.params.pageId as string
|
const rawPageId = context.params?.pageId as string
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const props = await resolveNotionPage(domain, rawPageId)
|
const props = await resolveNotionPage(domain, rawPageId)
|
||||||
@@ -48,6 +48,6 @@ export async function getStaticPaths() {
|
|||||||
return staticPaths
|
return staticPaths
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NotionDomainDynamicPage(props) {
|
export default function NotionDomainDynamicPage(props: PageProps) {
|
||||||
return <NotionPage {...props} />
|
return <NotionPage {...props} />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import 'styles/prism-theme.css'
|
|||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
import * as Fathom from 'fathom-client'
|
import * as Fathom from 'fathom-client'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import posthog from 'posthog-js'
|
import { posthog } from 'posthog-js'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
import { bootstrap } from '@/lib/bootstrap-client'
|
import { bootstrap } from '@/lib/bootstrap-client'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { IconContext } from '@react-icons/all-files'
|
|||||||
import Document, { Head, Html, Main, NextScript } from 'next/document'
|
import Document, { Head, Html, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
export default class MyDocument extends Document {
|
export default class MyDocument extends Document {
|
||||||
render() {
|
override render() {
|
||||||
return (
|
return (
|
||||||
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
<IconContext.Provider value={{ style: { verticalAlign: 'middle' } }}>
|
||||||
<Html lang='en'>
|
<Html lang='en'>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default async function OGImage(
|
|||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse
|
res: NextApiResponse
|
||||||
) {
|
) {
|
||||||
const { searchParams } = new URL(req.url)
|
const { searchParams } = new URL(req.url!)
|
||||||
const pageId = parsePageId(
|
const pageId = parsePageId(
|
||||||
searchParams.get('id') || libConfig.rootNotionPageId
|
searchParams.get('id') || libConfig.rootNotionPageId
|
||||||
)
|
)
|
||||||
@@ -178,7 +178,7 @@ export async function getNotionPageInfo({
|
|||||||
const recordMap = await notion.getPage(pageId)
|
const recordMap = await notion.getPage(pageId)
|
||||||
|
|
||||||
const keys = Object.keys(recordMap?.block || {})
|
const keys = Object.keys(recordMap?.block || {})
|
||||||
const block = recordMap?.block?.[keys[0]]?.value
|
const block = recordMap?.block?.[keys[0]!]?.value
|
||||||
|
|
||||||
if (!block) {
|
if (!block) {
|
||||||
throw new Error('Invalid recordMap for page')
|
throw new Error('Invalid recordMap for page')
|
||||||
@@ -209,7 +209,7 @@ export async function getNotionPageInfo({
|
|||||||
libConfig.defaultPageCoverPosition
|
libConfig.defaultPageCoverPosition
|
||||||
const imageObjectPosition = imageCoverPosition
|
const imageObjectPosition = imageCoverPosition
|
||||||
? `center ${(1 - imageCoverPosition) * 100}%`
|
? `center ${(1 - imageCoverPosition) * 100}%`
|
||||||
: null
|
: undefined
|
||||||
|
|
||||||
const imageBlockUrl = mapImageUrl(
|
const imageBlockUrl = mapImageUrl(
|
||||||
getPageProperty<string>('Social Image', block, recordMap) ||
|
getPageProperty<string>('Social Image', block, recordMap) ||
|
||||||
@@ -220,7 +220,7 @@ export async function getNotionPageInfo({
|
|||||||
|
|
||||||
const blockIcon = getBlockIcon(block, recordMap)
|
const blockIcon = getBlockIcon(block, recordMap)
|
||||||
const authorImageBlockUrl = mapImageUrl(
|
const authorImageBlockUrl = mapImageUrl(
|
||||||
blockIcon && isUrl(blockIcon) ? blockIcon : null,
|
blockIcon && isUrl(blockIcon) ? blockIcon : undefined,
|
||||||
block
|
block
|
||||||
)
|
)
|
||||||
const authorImageFallbackUrl = mapImageUrl(libConfig.defaultPageIcon, block)
|
const authorImageFallbackUrl = mapImageUrl(libConfig.defaultPageIcon, block)
|
||||||
@@ -272,7 +272,9 @@ export async function getNotionPageInfo({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isUrlReachable(url: string | null): Promise<boolean> {
|
async function isUrlReachable(
|
||||||
|
url: string | undefined | null
|
||||||
|
): Promise<boolean> {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -286,9 +288,9 @@ async function isUrlReachable(url: string | null): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getCompatibleImageUrl(
|
async function getCompatibleImageUrl(
|
||||||
url: string | null,
|
url: string | undefined | null,
|
||||||
fallbackUrl: string | null
|
fallbackUrl: string | undefined | null
|
||||||
): Promise<string | null> {
|
): Promise<string | undefined> {
|
||||||
const image = (await isUrlReachable(url)) ? url : fallbackUrl
|
const image = (await isUrlReachable(url)) ? url : fallbackUrl
|
||||||
|
|
||||||
if (image) {
|
if (image) {
|
||||||
@@ -303,5 +305,5 @@ async function getCompatibleImageUrl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return image
|
return image ?? undefined
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,12 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const pagePath of Object.keys(siteMap.canonicalPageMap)) {
|
for (const pagePath of Object.keys(siteMap.canonicalPageMap)) {
|
||||||
const pageId = siteMap.canonicalPageMap[pagePath]
|
const pageId = siteMap.canonicalPageMap[pagePath]!
|
||||||
const recordMap = siteMap.pageMap[pageId] as ExtendedRecordMap
|
const recordMap = siteMap.pageMap[pageId] as ExtendedRecordMap
|
||||||
if (!recordMap) continue
|
if (!recordMap) continue
|
||||||
|
|
||||||
const keys = Object.keys(recordMap?.block || {})
|
const keys = Object.keys(recordMap?.block || {})
|
||||||
const block = recordMap?.block?.[keys[0]]?.value
|
const block = recordMap?.block?.[keys[0]!]?.value
|
||||||
if (!block) continue
|
if (!block) continue
|
||||||
|
|
||||||
const parentPage = getBlockParentPage(block, recordMap)
|
const parentPage = getBlockParentPage(block, recordMap)
|
||||||
@@ -67,7 +67,7 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
|
|||||||
? new Date(lastUpdatedTime)
|
? new Date(lastUpdatedTime)
|
||||||
: publishedTime
|
: publishedTime
|
||||||
? new Date(publishedTime)
|
? new Date(publishedTime)
|
||||||
: undefined
|
: new Date()
|
||||||
const socialImageUrl = getSocialImageUrl(pageId)
|
const socialImageUrl = getSocialImageUrl(pageId)
|
||||||
|
|
||||||
feed.item({
|
feed.item({
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { PageProps } from '@/lib/types'
|
||||||
import { NotionPage } from '@/components/NotionPage'
|
import { NotionPage } from '@/components/NotionPage'
|
||||||
import { domain } from '@/lib/config'
|
import { domain } from '@/lib/config'
|
||||||
import { resolveNotionPage } from '@/lib/resolve-notion-page'
|
import { resolveNotionPage } from '@/lib/resolve-notion-page'
|
||||||
@@ -16,6 +17,6 @@ export const getStaticProps = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NotionDomainPage(props) {
|
export default function NotionDomainPage(props: PageProps) {
|
||||||
return <NotionPage {...props} />
|
return <NotionPage {...props} />
|
||||||
}
|
}
|
||||||
|
|||||||
3779
pnpm-lock.yaml
generated
3779
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "@fisch0920/config/tsconfig-react",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2017",
|
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
|
||||||
"allowJs": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": false,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"typeRoots": ["./node_modules/@types"],
|
|
||||||
"incremental": true,
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/components/*": ["components/*"],
|
"@/components/*": ["components/*"],
|
||||||
"@/lib/*": ["lib/*"],
|
"@/lib/*": ["lib/*"],
|
||||||
"@/styles/*": ["styles/*"]
|
"@/styles/*": ["styles/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "*.config.ts"],
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "site.config.ts"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user