From 4aaeb870ebf5dd2bdced59f71be3216298433dba Mon Sep 17 00:00:00 2001 From: Xennis Date: Wed, 29 May 2024 02:33:42 +0200 Subject: [PATCH] [render] Download internal images --- examples/nextjs/.gitignore | 3 +++ examples/nextjs/src/lib/fetchers.ts | 40 ++++++++++++++++++++++++++++- packages/render/src/fetch.ts | 32 ++++++++++++++++++----- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/examples/nextjs/.gitignore b/examples/nextjs/.gitignore index fd3dbb5..5600d9c 100644 --- a/examples/nextjs/.gitignore +++ b/examples/nextjs/.gitignore @@ -34,3 +34,6 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +# Images downloaded from CMS +public/cms/* diff --git a/examples/nextjs/src/lib/fetchers.ts b/examples/nextjs/src/lib/fetchers.ts index 644f2b1..8175bb4 100644 --- a/examples/nextjs/src/lib/fetchers.ts +++ b/examples/nextjs/src/lib/fetchers.ts @@ -1,6 +1,8 @@ import { unstable_cache } from "next/cache" import { fetchBlocksChildren } from "@react-notion-cms/render" import { Client } from "@notionhq/client" +import { statSync, writeFileSync } from "node:fs" +import nextConfig from "../../next.config.mjs" const notionClient = new Client({ auth: process.env.NOTION_ACCESS_TOKEN, @@ -8,7 +10,16 @@ const notionClient = new Client({ export const getCachedPageContent = unstable_cache( async (blockId: string) => { - return fetchBlocksChildren(notionClient, blockId) + return fetchBlocksChildren( + notionClient, + { + block_id: blockId, + page_size: 100, + }, + { + resolveImageFn: downloadImageToPublicDir, + }, + ) }, ["cms-page"], // { @@ -16,3 +27,30 @@ export const getCachedPageContent = unstable_cache( // tags: ["cms-page"], // }, ) + +const downloadImageToPublicDir = async (url: string, meta: { blockId: string; lastEditedTime: Date }) => { + const fileUrl = new URL(url) + const originalFileName = fileUrl.pathname.substring(fileUrl.pathname.lastIndexOf("/") + 1) + const newFileName = `public/cms/${meta.blockId}-${originalFileName}` + + let savedLastEditedTime: Date | null = null + try { + const stat = statSync(newFileName) + savedLastEditedTime = stat.mtime + } catch (error) { + if (error instanceof Error && "code" in error && error.code === "ENOENT") { + console.debug(`${newFileName} not found`) + } else { + console.warn(`${newFileName}: ${error}`) + } + } + // Avoid download the file again and again + if (savedLastEditedTime === null || meta.lastEditedTime > savedLastEditedTime) { + const resp = await fetch(fileUrl) + const blob = await resp.blob() + writeFileSync(newFileName, new DataView(await blob.arrayBuffer())) + console.info(`downloaded image ${newFileName} of block ${meta.blockId}`) + } + + return newFileName.replace("public", nextConfig.basePath ?? "") +} diff --git a/packages/render/src/fetch.ts b/packages/render/src/fetch.ts index 03fe9b6..ee85e3a 100644 --- a/packages/render/src/fetch.ts +++ b/packages/render/src/fetch.ts @@ -1,13 +1,24 @@ import { type Client, isFullBlock, iteratePaginatedAPI } from "@notionhq/client" import { BlockObjectResponseWithChildren } from "./types" +import { type ListBlockChildrenParameters } from "@notionhq/client/build/src/api-endpoints" -export const fetchBlocksChildren = async (client: Client, blockId: string) => { +type ResolveImageFn = (url: string, meta: { blockId: string; lastEditedTime: Date }) => Promise + +export const fetchBlocksChildren = async ( + client: Client, + firstPageArgs: ListBlockChildrenParameters, + options: { resolveImageFn?: ResolveImageFn }, +) => { const result: Array = [] - for await (const block of iteratePaginatedAPI(client.blocks.children.list, { - block_id: blockId, - page_size: 100, - })) { + for await (const block of iteratePaginatedAPI(client.blocks.children.list, firstPageArgs)) { if (isFullBlock(block)) { + if (options.resolveImageFn && block.type === "image" && block.image.type === "file") { + block.image.file.url = await options.resolveImageFn(block.image.file.url, { + blockId: block.id, + lastEditedTime: new Date(block.last_edited_time), + }) + } + if (!block.has_children) { result.push(block) continue @@ -19,10 +30,17 @@ export const fetchBlocksChildren = async (client: Client, blockId: string) => { result.push({ ...block, - _children: await fetchBlocksChildren(client, childId), + _children: await fetchBlocksChildren( + client, + { + ...firstPageArgs, + block_id: childId, + }, + options, + ), }) } } - console.info(`fetched children from notion block ${blockId}`) + console.info(`fetched children from notion block ${firstPageArgs.block_id}`) return result }