Merge pull request #651 from transitive-bullshit/fix/social-images

This commit is contained in:
Travis Fischer
2024-11-07 18:52:43 -06:00
committed by GitHub
9 changed files with 300 additions and 300 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
import { type Block } from 'notion-types'
import { defaultMapImageUrl } from 'react-notion-x'
import { defaultMapImageUrl } from 'notion-utils'
import { defaultPageCover, defaultPageIcon } from './config'

View File

@@ -15,7 +15,7 @@
"build": "next build",
"start": "next start",
"deploy": "vercel deploy",
"deps:update": "[ -z $GITHUB_ACTIONS ] && pnpm up -L notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:upgrade": "[ -z $GITHUB_ACTIONS ] && pnpm up -L notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link": "[ -z $GITHUB_ACTIONS ] && run-s deps:link:* || echo 'Skipping deps:update on CI'",
"deps:unlink": "[ -z $GITHUB_ACTIONS ] && pnpm add notion-client notion-types notion-utils react-notion-x || echo 'Skipping deps:update on CI'",
"deps:link:notion-types": "pnpm link ../react-notion-x/packages/notion-types",
@@ -36,23 +36,23 @@
"@react-icons/all-files": "^4.1.0",
"@vercel/og": "^0.6.3",
"classnames": "^2.5.1",
"date-fns": "^2.30.0",
"date-fns": "^4.1.0",
"expiry-map": "^2.0.0",
"fathom-client": "^3.4.1",
"ky": "^1.7.2",
"lqip-modern": "^2.1.0",
"next": "^15.0.2",
"notion-client": "^7.0.1",
"notion-types": "^7.0.1",
"notion-utils": "^7.0.1",
"next": "^15.0.3",
"notion-client": "^7.1.1",
"notion-types": "^7.1.1",
"notion-utils": "^7.1.1",
"p-map": "^7.0.2",
"p-memoize": "^7.1.1",
"posthog-js": "^1.20.2",
"posthog-js": "^1.181.0",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-body-classname": "^1.3.1",
"react-dom": "^18.2.0",
"react-notion-x": "^7.0.1",
"react-notion-x": "^7.2.1",
"react-tweet-embed": "^2.0.0",
"react-use": "^17.4.2",
"rss": "^1.2.2"

View File

@@ -1,156 +0,0 @@
import ky from 'ky'
import { type NextApiRequest, type NextApiResponse } from 'next'
import { type PageBlock } from 'notion-types'
import {
getBlockIcon,
getBlockTitle,
getPageProperty,
isUrl,
parsePageId
} from 'notion-utils'
import * as libConfig from '@/lib/config'
import { mapImageUrl } from '@/lib/map-image-url'
import { notion } from '@/lib/notion-api'
import { type NotionPageInfo } from '@/lib/types'
export default async function notionPageInfo(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).send({ error: 'method not allowed' })
}
const pageId: string = parsePageId(req.body.pageId)
if (!pageId) {
throw new Error('Invalid notion page id')
}
const recordMap = await notion.getPage(pageId)
const keys = Object.keys(recordMap?.block || {})
const block = recordMap?.block?.[keys[0]]?.value
if (!block) {
throw new Error('Invalid recordMap for page')
}
const blockSpaceId = block.space_id
if (
blockSpaceId &&
libConfig.rootNotionSpaceId &&
blockSpaceId !== libConfig.rootNotionSpaceId
) {
return res.status(400).send({
error: `Notion page "${pageId}" belongs to a different workspace.`
})
}
const isBlogPost =
block.type === 'page' && block.parent_table === 'collection'
const title = getBlockTitle(block, recordMap) || libConfig.name
const imageCoverPosition =
(block as PageBlock).format?.page_cover_position ??
libConfig.defaultPageCoverPosition
const imageObjectPosition = imageCoverPosition
? `center ${(1 - imageCoverPosition) * 100}%`
: null
const imageBlockUrl = mapImageUrl(
getPageProperty<string>('Social Image', block, recordMap) ||
(block as PageBlock).format?.page_cover,
block
)
const imageFallbackUrl = mapImageUrl(libConfig.defaultPageCover, block)
const blockIcon = getBlockIcon(block, recordMap)
const authorImageBlockUrl = mapImageUrl(
blockIcon && isUrl(blockIcon) ? blockIcon : null,
block
)
const authorImageFallbackUrl = mapImageUrl(libConfig.defaultPageIcon, block)
const [authorImage, image] = await Promise.all([
getCompatibleImageUrl(authorImageBlockUrl, authorImageFallbackUrl),
getCompatibleImageUrl(imageBlockUrl, imageFallbackUrl)
])
const author =
getPageProperty<string>('Author', block, recordMap) || libConfig.author
// const socialDescription =
// getPageProperty<string>('Description', block, recordMap) ||
// libConfig.description
// const lastUpdatedTime = getPageProperty<number>(
// 'Last Updated',
// block,
// recordMap
// )
const publishedTime = getPageProperty<number>('Published', block, recordMap)
const datePublished = publishedTime ? new Date(publishedTime) : undefined
// const dateUpdated = lastUpdatedTime
// ? new Date(lastUpdatedTime)
// : publishedTime
// ? new Date(publishedTime)
// : undefined
const date =
isBlogPost && datePublished
? `${datePublished.toLocaleString('en-US', {
month: 'long'
})} ${datePublished.getFullYear()}`
: undefined
const detail = date || author || libConfig.domain
const pageInfo: NotionPageInfo = {
pageId,
title,
image,
imageObjectPosition,
author,
authorImage,
detail
}
res.setHeader(
'Cache-Control',
'public, s-maxage=3600, max-age=3600, stale-while-revalidate=3600'
)
res.status(200).json(pageInfo)
}
async function isUrlReachable(url: string | null): Promise<boolean> {
if (!url) {
return false
}
try {
await ky.head(url)
return true
} catch {
return false
}
}
async function getCompatibleImageUrl(
url: string | null,
fallbackUrl: string | null
): Promise<string | null> {
const image = (await isUrlReachable(url)) ? url : fallbackUrl
if (image) {
const imageUrl = new URL(image)
if (imageUrl.host === 'images.unsplash.com') {
if (!imageUrl.searchParams.has('w')) {
imageUrl.searchParams.set('w', '1200')
imageUrl.searchParams.set('fit', 'max')
return imageUrl.toString()
}
}
}
return image
}

View File

@@ -1,47 +1,45 @@
import { ImageResponse } from '@vercel/og'
import ky from 'ky'
import { type NextRequest } from 'next/server'
import { type NextApiRequest, type NextApiResponse } from 'next'
import { ImageResponse } from 'next/og'
import { type PageBlock } from 'notion-types'
import {
getBlockIcon,
getBlockTitle,
getPageProperty,
isUrl,
parsePageId
} from 'notion-utils'
import { api, apiHost, rootNotionPageId } from '@/lib/config'
import { type NotionPageInfo } from '@/lib/types'
import * as libConfig from '@/lib/config'
import interRegularFont from '@/lib/fonts/inter-regular'
import interSemiBoldFont from '@/lib/fonts/inter-semibold'
import { mapImageUrl } from '@/lib/map-image-url'
import { notion } from '@/lib/notion-api'
import { type NotionPageInfo, type PageError } from '@/lib/types'
const interRegularFontP = ky(
new URL('../../public/fonts/Inter-Regular.ttf', import.meta.url)
).arrayBuffer()
export const runtime = 'edge'
const interBoldFontP = ky(
new URL('../../public/fonts/Inter-SemiBold.ttf', import.meta.url)
).arrayBuffer()
export const config = {
runtime: 'edge'
}
export default async function OGImage(req: NextRequest) {
export default async function OGImage(
req: NextApiRequest,
res: NextApiResponse
) {
const { searchParams } = new URL(req.url)
const pageId = searchParams.get('id') || rootNotionPageId
const pageId = parsePageId(
searchParams.get('id') || libConfig.rootNotionPageId
)
if (!pageId) {
return new Response('Invalid notion page id', { status: 400 })
}
const pageInfoRes = await fetch(`${apiHost}${api.getNotionPageInfo}`, {
method: 'POST',
body: JSON.stringify({ pageId }),
headers: {
'content-type': 'application/json'
}
})
if (!pageInfoRes.ok) {
return new Response(pageInfoRes.statusText, { status: pageInfoRes.status })
const pageInfoOrError = await getNotionPageInfo({ pageId })
if (pageInfoOrError.type === 'error') {
return res.status(pageInfoOrError.error.statusCode).send({
error: pageInfoOrError.error.message
})
}
const pageInfo: NotionPageInfo = await pageInfoRes.json()
const pageInfo = pageInfoOrError.data
console.log(pageInfo)
const [interRegularFont, interBoldFont] = await Promise.all([
interRegularFontP,
interBoldFontP
])
return new ImageResponse(
(
<div
@@ -168,7 +166,7 @@ export default async function OGImage(req: NextRequest) {
},
{
name: 'Inter',
data: interBoldFont,
data: interSemiBoldFont,
style: 'normal',
weight: 700
}
@@ -176,3 +174,142 @@ export default async function OGImage(req: NextRequest) {
}
)
}
export async function getNotionPageInfo({
pageId
}: {
pageId: string
}): Promise<
| { type: 'success'; data: NotionPageInfo }
| { type: 'error'; error: PageError }
> {
const recordMap = await notion.getPage(pageId)
const keys = Object.keys(recordMap?.block || {})
const block = recordMap?.block?.[keys[0]]?.value
if (!block) {
throw new Error('Invalid recordMap for page')
}
const blockSpaceId = block.space_id
if (
blockSpaceId &&
libConfig.rootNotionSpaceId &&
blockSpaceId !== libConfig.rootNotionSpaceId
) {
return {
type: 'error',
error: {
statusCode: 400,
message: `Notion page "${pageId}" belongs to a different workspace.`
}
}
}
const isBlogPost =
block.type === 'page' && block.parent_table === 'collection'
const title = getBlockTitle(block, recordMap) || libConfig.name
const imageCoverPosition =
(block as PageBlock).format?.page_cover_position ??
libConfig.defaultPageCoverPosition
const imageObjectPosition = imageCoverPosition
? `center ${(1 - imageCoverPosition) * 100}%`
: null
const imageBlockUrl = mapImageUrl(
getPageProperty<string>('Social Image', block, recordMap) ||
(block as PageBlock).format?.page_cover,
block
)
const imageFallbackUrl = mapImageUrl(libConfig.defaultPageCover, block)
const blockIcon = getBlockIcon(block, recordMap)
const authorImageBlockUrl = mapImageUrl(
blockIcon && isUrl(blockIcon) ? blockIcon : null,
block
)
const authorImageFallbackUrl = mapImageUrl(libConfig.defaultPageIcon, block)
const [authorImage, image] = await Promise.all([
getCompatibleImageUrl(authorImageBlockUrl, authorImageFallbackUrl),
getCompatibleImageUrl(imageBlockUrl, imageFallbackUrl)
])
const author =
getPageProperty<string>('Author', block, recordMap) || libConfig.author
// const socialDescription =
// getPageProperty<string>('Description', block, recordMap) ||
// libConfig.description
// const lastUpdatedTime = getPageProperty<number>(
// 'Last Updated',
// block,
// recordMap
// )
const publishedTime = getPageProperty<number>('Published', block, recordMap)
const datePublished = publishedTime ? new Date(publishedTime) : undefined
// const dateUpdated = lastUpdatedTime
// ? new Date(lastUpdatedTime)
// : publishedTime
// ? new Date(publishedTime)
// : undefined
const date =
isBlogPost && datePublished
? `${datePublished.toLocaleString('en-US', {
month: 'long'
})} ${datePublished.getFullYear()}`
: undefined
const detail = date || author || libConfig.domain
const pageInfo: NotionPageInfo = {
pageId,
title,
image,
imageObjectPosition,
author,
authorImage,
detail
}
return {
type: 'success',
data: pageInfo
}
}
async function isUrlReachable(url: string | null): Promise<boolean> {
if (!url) {
return false
}
try {
await ky.head(url)
return true
} catch {
return false
}
}
async function getCompatibleImageUrl(
url: string | null,
fallbackUrl: string | null
): Promise<string | null> {
const image = (await isUrlReachable(url)) ? url : fallbackUrl
if (image) {
const imageUrl = new URL(image)
if (imageUrl.host === 'images.unsplash.com') {
if (!imageUrl.searchParams.has('w')) {
imageUrl.searchParams.set('w', '1200')
imageUrl.searchParams.set('fit', 'max')
return imageUrl.toString()
}
}
}
return image
}

215
pnpm-lock.yaml generated
View File

@@ -27,8 +27,8 @@ importers:
specifier: ^2.5.1
version: 2.5.1
date-fns:
specifier: ^2.30.0
version: 2.30.0
specifier: ^4.1.0
version: 4.1.0
expiry-map:
specifier: ^2.0.0
version: 2.0.0
@@ -42,17 +42,17 @@ importers:
specifier: ^2.1.0
version: 2.1.0
next:
specifier: ^15.0.2
version: 15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^15.0.3
version: 15.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
notion-client:
specifier: ^7.0.1
version: 7.0.1
specifier: ^7.1.1
version: 7.1.1
notion-types:
specifier: ^7.0.1
version: 7.0.1
specifier: ^7.1.1
version: 7.1.1
notion-utils:
specifier: ^7.0.1
version: 7.0.1
specifier: ^7.1.1
version: 7.1.1
p-map:
specifier: ^7.0.2
version: 7.0.2
@@ -60,8 +60,8 @@ importers:
specifier: ^7.1.1
version: 7.1.1
posthog-js:
specifier: ^1.20.2
version: 1.180.0
specifier: ^1.181.0
version: 1.181.0
prismjs:
specifier: ^1.29.0
version: 1.29.0
@@ -75,8 +75,8 @@ importers:
specifier: ^18.2.0
version: 18.3.1(react@18.3.1)
react-notion-x:
specifier: ^7.0.1
version: 7.0.1(@babel/runtime@7.26.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^7.2.1
version: 7.2.1(@babel/runtime@7.26.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-tweet-embed:
specifier: ^2.0.0
version: 2.0.0(react@18.3.1)
@@ -318,53 +318,53 @@ packages:
'@next/bundle-analyzer@15.0.2':
resolution: {integrity: sha512-bV566k+rDsaqXSUgHBof0iMIDx5DWtLx/98jvYtqb9x85e+WJzv+8cpDvbjtxQMf7nFC/LUkPmpruj1cOKfz4A==}
'@next/env@15.0.2':
resolution: {integrity: sha512-c0Zr0ModK5OX7D4ZV8Jt/wqoXtitLNPwUfG9zElCZztdaZyNVnN40rDXVZ/+FGuR4CcNV5AEfM6N8f+Ener7Dg==}
'@next/env@15.0.3':
resolution: {integrity: sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==}
'@next/swc-darwin-arm64@15.0.2':
resolution: {integrity: sha512-GK+8w88z+AFlmt+ondytZo2xpwlfAR8U6CRwXancHImh6EdGfHMIrTSCcx5sOSBei00GyLVL0ioo1JLKTfprgg==}
'@next/swc-darwin-arm64@15.0.3':
resolution: {integrity: sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@15.0.2':
resolution: {integrity: sha512-KUpBVxIbjzFiUZhiLIpJiBoelqzQtVZbdNNsehhUn36e2YzKHphnK8eTUW1s/4aPy5kH/UTid8IuVbaOpedhpw==}
'@next/swc-darwin-x64@15.0.3':
resolution: {integrity: sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@15.0.2':
resolution: {integrity: sha512-9J7TPEcHNAZvwxXRzOtiUvwtTD+fmuY0l7RErf8Yyc7kMpE47MIQakl+3jecmkhOoIyi/Rp+ddq7j4wG6JDskQ==}
'@next/swc-linux-arm64-gnu@15.0.3':
resolution: {integrity: sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@15.0.2':
resolution: {integrity: sha512-BjH4ZSzJIoTTZRh6rG+a/Ry4SW0HlizcPorqNBixBWc3wtQtj4Sn9FnRZe22QqrPnzoaW0ctvSz4FaH4eGKMww==}
'@next/swc-linux-arm64-musl@15.0.3':
resolution: {integrity: sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@15.0.2':
resolution: {integrity: sha512-i3U2TcHgo26sIhcwX/Rshz6avM6nizrZPvrDVDY1bXcLH1ndjbO8zuC7RoHp0NSK7wjJMPYzm7NYL1ksSKFreA==}
'@next/swc-linux-x64-gnu@15.0.3':
resolution: {integrity: sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@15.0.2':
resolution: {integrity: sha512-AMfZfSVOIR8fa+TXlAooByEF4OB00wqnms1sJ1v+iu8ivwvtPvnkwdzzFMpsK5jA2S9oNeeQ04egIWVb4QWmtQ==}
'@next/swc-linux-x64-musl@15.0.3':
resolution: {integrity: sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@15.0.2':
resolution: {integrity: sha512-JkXysDT0/hEY47O+Hvs8PbZAeiCQVxKfGtr4GUpNAhlG2E0Mkjibuo8ryGD29Qb5a3IOnKYNoZlh/MyKd2Nbww==}
'@next/swc-win32-arm64-msvc@15.0.3':
resolution: {integrity: sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-x64-msvc@15.0.2':
resolution: {integrity: sha512-foaUL0NqJY/dX0Pi/UcZm5zsmSk5MtP/gxx3xOPyREkMFN+CTjctPfu3QaqrQHinaKdPnMWPJDKt4VjDfTBe/Q==}
'@next/swc-win32-x64-msvc@15.0.3':
resolution: {integrity: sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -655,6 +655,9 @@ packages:
caniuse-lite@1.0.30001676:
resolution: {integrity: sha512-Qz6zwGCiPghQXGJvgQAem79esjitvJ+CxSbSQkW9H/UX5hg8XM88d4lp2W+MEQ81j+Hip58Il+jGVdazk1z9cw==}
caniuse-lite@1.0.30001678:
resolution: {integrity: sha512-RR+4U/05gNtps58PEBDZcPWTgEO2MBeoPZ96aQcjmfkBWRIDfN451fW2qyDA9/+HohLLIL5GqiMwA+IB1pWarw==}
canvas@2.11.2:
resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==}
engines: {node: '>=6'}
@@ -781,9 +784,8 @@ packages:
resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==}
engines: {node: '>= 0.4'}
date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
debounce@1.2.1:
resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
@@ -1646,16 +1648,16 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
next@15.0.2:
resolution: {integrity: sha512-rxIWHcAu4gGSDmwsELXacqAPUk+j8dV/A9cDF5fsiCMpkBDYkO2AEaL1dfD+nNmDiU6QMCFN8Q30VEKapT9UHQ==}
engines: {node: '>=18.18.0'}
next@15.0.3:
resolution: {integrity: sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==}
engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@playwright/test': ^1.41.2
babel-plugin-react-compiler: '*'
react: ^18.2.0 || 19.0.0-rc-02c0e824-20241028
react-dom: ^18.2.0 || 19.0.0-rc-02c0e824-20241028
react: ^18.2.0 || 19.0.0-rc-66855b96-20241106
react-dom: ^18.2.0 || 19.0.0-rc-66855b96-20241106
sass: ^1.3.0
peerDependenciesMeta:
'@opentelemetry/api':
@@ -1691,16 +1693,16 @@ packages:
resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==}
engines: {node: '>=14.16'}
notion-client@7.0.1:
resolution: {integrity: sha512-nVJktjm6VOIrfpQXs8UhPFmEj4TpWsXMHwOTk6hbMry4IGemNpJ1pBnx2YY+2nCzvkJrvEiTLmKFuE+kkbGCfg==}
notion-client@7.1.1:
resolution: {integrity: sha512-mT/yEOIlbQzIAMsuIoWMX3XQrkoN07NMtSilDYIQQShxYJDdXMYfLuSoOgalEwFmwwsDnETHJqfrdkfe6sF9Rw==}
engines: {node: '>=18'}
notion-types@7.0.1:
resolution: {integrity: sha512-vtyEBDdBKULYFrnMSzQpvcbs8vQm6jPca6wMmjTADXih0dAJqw5QLrPVUgCeKdnPfFJwldHmAL4FuOYQ3HhNvQ==}
notion-types@7.1.1:
resolution: {integrity: sha512-wsj/mwTi0hZjldvfVoKgtrXrOaBRgEsXic2n1kh6S1Aj7snx9Xo8sA8KtHqmXqKhJ8BFEsavh0Dv3TlyMv8aWg==}
engines: {node: '>=18'}
notion-utils@7.0.1:
resolution: {integrity: sha512-c9pjCEKrNpocu62cRXn5A30rPIGNjPCCU5/ucoMve07E+xLXD6+JzJZvKRy6dteUtcSHdr/IQSbrAqCeHfGRcQ==}
notion-utils@7.1.1:
resolution: {integrity: sha512-Hks/sipBA7aDZ3TS90CRKuKvCsvs/cW+MXzZPFh/Q1uTjhEt9GynSUl8o11k+Nm4No3+deQJY/hsuv90gyCFjg==}
engines: {node: '>=18'}
npm-normalize-package-bin@4.0.0:
@@ -1848,8 +1850,8 @@ packages:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
path2d@0.2.1:
resolution: {integrity: sha512-Fl2z/BHvkTNvkuBzYTpTuirHZg6wW9z8+4SND/3mDTEcYbbNKWAy21dz9D3ePNNwrrK8pqZO5vLPZ1hLF6T7XA==}
path2d@0.2.2:
resolution: {integrity: sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==}
engines: {node: '>=6'}
pdfjs-dist@4.4.168:
@@ -1883,8 +1885,8 @@ packages:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
engines: {node: ^10 || ^12 || >=14}
posthog-js@1.180.0:
resolution: {integrity: sha512-a+LTbmDUaHuskdIlRlTWWV1YOgQdfhhJJ8sSoW8+sCa+UrE8miD2B4Q2PtCj7mTcyNENu/ZR1VqkbfzIWRNWmQ==}
posthog-js@1.181.0:
resolution: {integrity: sha512-bI+J+f4E8x4JwbGtG6LReQv1Xvss01F6cs7UDlvffHySpVhNq4ptkNjV88B92IVEsrCtNYhy/TjFnGxk6RN0Qw==}
preact@10.24.3:
resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==}
@@ -1923,8 +1925,8 @@ packages:
react-fast-compare@3.2.2:
resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==}
react-hotkeys-hook@4.5.1:
resolution: {integrity: sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==}
react-hotkeys-hook@4.6.1:
resolution: {integrity: sha512-XlZpbKUj9tkfgPgT9gA+1p7Ey6vFIZHttUjPqpTdyT5nqQ8mHL7elxvSbaC+dpSiHUSmr21Ya1mDxBZG3aje4Q==}
peerDependencies:
react: '>=16.8.1'
react-dom: '>=16.8.1'
@@ -1960,8 +1962,8 @@ packages:
react: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18
react-dom: ^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18
react-notion-x@7.0.1:
resolution: {integrity: sha512-/uvP1AGXXJgpbS7y9As2hk/v9pD0yC27fseLLkyhlbAHvWUb3TstHqI4CoJ8hn3jyuvyH6dKQ6wUX7jyb4S9Lg==}
react-notion-x@7.2.1:
resolution: {integrity: sha512-wy/MOp/+pOL/qKUcY3azMvTn/y6AkKqutFboOKn0OUouPe2OaOjUk/VIGFV081UL7+nwCKbWecHgRZ9MuwwHng==}
engines: {node: '>=18'}
peerDependencies:
react: '>=18'
@@ -2558,7 +2560,7 @@ snapshots:
'@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.6.3)
eslint-config-prettier: 9.1.0(eslint@8.57.1)
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-jest: 28.8.3(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
eslint-plugin-jest-dom: 5.4.0(eslint@8.57.1)
@@ -2716,30 +2718,30 @@ snapshots:
- bufferutil
- utf-8-validate
'@next/env@15.0.2': {}
'@next/env@15.0.3': {}
'@next/swc-darwin-arm64@15.0.2':
'@next/swc-darwin-arm64@15.0.3':
optional: true
'@next/swc-darwin-x64@15.0.2':
'@next/swc-darwin-x64@15.0.3':
optional: true
'@next/swc-linux-arm64-gnu@15.0.2':
'@next/swc-linux-arm64-gnu@15.0.3':
optional: true
'@next/swc-linux-arm64-musl@15.0.2':
'@next/swc-linux-arm64-musl@15.0.3':
optional: true
'@next/swc-linux-x64-gnu@15.0.2':
'@next/swc-linux-x64-gnu@15.0.3':
optional: true
'@next/swc-linux-x64-musl@15.0.2':
'@next/swc-linux-x64-musl@15.0.3':
optional: true
'@next/swc-win32-arm64-msvc@15.0.2':
'@next/swc-win32-arm64-msvc@15.0.3':
optional: true
'@next/swc-win32-x64-msvc@15.0.2':
'@next/swc-win32-x64-msvc@15.0.3':
optional: true
'@nodelib/fs.scandir@2.1.5':
@@ -3065,6 +3067,8 @@ snapshots:
caniuse-lite@1.0.30001676: {}
caniuse-lite@1.0.30001678: {}
canvas@2.11.2:
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
@@ -3095,7 +3099,8 @@ snapshots:
client-only@0.0.1: {}
clsx@2.1.1: {}
clsx@2.1.1:
optional: true
cluster-key-slot@1.1.2: {}
@@ -3190,9 +3195,7 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.1
date-fns@2.30.0:
dependencies:
'@babel/runtime': 7.26.0
date-fns@4.1.0: {}
debounce@1.2.1: {}
@@ -3228,7 +3231,8 @@ snapshots:
denque@2.1.0: {}
dequal@2.0.3: {}
dequal@2.0.3:
optional: true
detect-libc@2.0.3: {}
@@ -3380,13 +3384,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1):
eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 8.57.1
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
@@ -3399,14 +3403,14 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
@@ -3421,7 +3425,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -4064,14 +4068,16 @@ snapshots:
p-map: 4.0.0
sharp: 0.33.5
make-cancellable-promise@1.3.2: {}
make-cancellable-promise@1.3.2:
optional: true
make-dir@3.1.0:
dependencies:
semver: 6.3.1
optional: true
make-event-props@1.6.2: {}
make-event-props@1.6.2:
optional: true
map-age-cleaner@0.1.3:
dependencies:
@@ -4093,6 +4099,7 @@ snapshots:
merge-refs@1.3.0(@types/react@18.3.12):
optionalDependencies:
'@types/react': 18.3.12
optional: true
merge2@1.4.1: {}
@@ -4165,26 +4172,26 @@ snapshots:
natural-compare@1.4.0: {}
next@15.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next@15.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 15.0.2
'@next/env': 15.0.3
'@swc/counter': 0.1.3
'@swc/helpers': 0.5.13
busboy: 1.6.0
caniuse-lite: 1.0.30001676
caniuse-lite: 1.0.30001678
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.6(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 15.0.2
'@next/swc-darwin-x64': 15.0.2
'@next/swc-linux-arm64-gnu': 15.0.2
'@next/swc-linux-arm64-musl': 15.0.2
'@next/swc-linux-x64-gnu': 15.0.2
'@next/swc-linux-x64-musl': 15.0.2
'@next/swc-win32-arm64-msvc': 15.0.2
'@next/swc-win32-x64-msvc': 15.0.2
'@next/swc-darwin-arm64': 15.0.3
'@next/swc-darwin-x64': 15.0.3
'@next/swc-linux-arm64-gnu': 15.0.3
'@next/swc-linux-arm64-musl': 15.0.3
'@next/swc-linux-x64-gnu': 15.0.3
'@next/swc-linux-x64-musl': 15.0.3
'@next/swc-win32-arm64-msvc': 15.0.3
'@next/swc-win32-x64-msvc': 15.0.3
sharp: 0.33.5
transitivePeerDependencies:
- '@babel/core'
@@ -4211,21 +4218,21 @@ snapshots:
normalize-url@8.0.1: {}
notion-client@7.0.1:
notion-client@7.1.1:
dependencies:
ky: 1.7.2
notion-types: 7.0.1
notion-utils: 7.0.1
notion-types: 7.1.1
notion-utils: 7.1.1
p-map: 7.0.2
notion-types@7.0.1: {}
notion-types@7.1.1: {}
notion-utils@7.0.1:
notion-utils@7.1.1:
dependencies:
is-url-superb: 6.1.0
mem: 10.0.0
normalize-url: 8.0.1
notion-types: 7.0.1
notion-types: 7.1.1
p-queue: 8.0.1
npm-normalize-package-bin@4.0.0: {}
@@ -4378,16 +4385,17 @@ snapshots:
path-type@4.0.0: {}
path2d@0.2.1:
path2d@0.2.2:
optional: true
pdfjs-dist@4.4.168:
optionalDependencies:
canvas: 2.11.2
path2d: 0.2.1
path2d: 0.2.2
transitivePeerDependencies:
- encoding
- supports-color
optional: true
picocolors@1.1.1: {}
@@ -4407,7 +4415,7 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
posthog-js@1.180.0:
posthog-js@1.181.0:
dependencies:
core-js: 3.39.0
fflate: 0.4.8
@@ -4447,7 +4455,7 @@ snapshots:
react-fast-compare@3.2.2: {}
react-hotkeys-hook@4.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-hotkeys-hook@4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
@@ -4484,21 +4492,22 @@ snapshots:
react-lifecycles-compat: 3.0.4
warning: 4.0.3
react-notion-x@7.0.1(@babel/runtime@7.26.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
react-notion-x@7.2.1(@babel/runtime@7.26.0)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@fisch0920/medium-zoom': 1.0.7
'@matejmazur/react-katex': 3.1.3(katex@0.16.11)(react@18.3.1)
katex: 0.16.11
notion-types: 7.0.1
notion-utils: 7.0.1
notion-types: 7.1.1
notion-utils: 7.1.1
prismjs: 1.29.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-fast-compare: 3.2.2
react-hotkeys-hook: 4.5.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-hotkeys-hook: 4.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-image: 4.1.0(@babel/runtime@7.26.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-lazy-images: 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-modal: 3.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies:
react-pdf: 9.1.1(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
transitivePeerDependencies:
- '@babel/runtime'
@@ -4523,6 +4532,7 @@ snapshots:
transitivePeerDependencies:
- encoding
- supports-color
optional: true
react-side-effect@2.1.2(react@18.3.1):
dependencies:
@@ -4930,7 +4940,8 @@ snapshots:
tiny-inflate@1.0.3: {}
tiny-invariant@1.3.3: {}
tiny-invariant@1.3.3:
optional: true
to-regex-range@5.0.1:
dependencies:

Binary file not shown.

Binary file not shown.