diff --git a/libs/scully-plugin-notion/README.md b/libs/scully-plugin-notion/README.md index c3cd429..254e54e 100644 --- a/libs/scully-plugin-notion/README.md +++ b/libs/scully-plugin-notion/README.md @@ -77,7 +77,8 @@ import { ScullyConfig, setPluginConfig } from '@scullyio/scully'; import { NotionDom, NotionDomRouter, - NotionPluginOptions, + NotionDomPluginOptions, + NotionDomRouterPluginOptions } from '@notion-stuff/scully-plugin-notion'; setPluginConfig(NotionDom, { @@ -86,7 +87,7 @@ setPluginConfig(NotionDom, { ... customer the parser ... */ }, -} as NotionPluginOptions); +} as NotionDomPluginOptions); export const config: ScullyConfig = { projectRoot: './src', @@ -101,7 +102,7 @@ export const config: ScullyConfig = { basePath: '/blog', // optional, should match your route here titleSuffix: '', // optional slugKey: 'slug', // optional - }, + } as NotionDomRouterPluginOptions, }, }; ``` diff --git a/libs/scully-plugin-notion/src/lib/plugin-options.ts b/libs/scully-plugin-notion/src/lib/plugin-options.ts index 9a5c1e8..fbbf389 100644 --- a/libs/scully-plugin-notion/src/lib/plugin-options.ts +++ b/libs/scully-plugin-notion/src/lib/plugin-options.ts @@ -1,5 +1,55 @@ import type { NotionBlocksHtmlParserOptions } from '@notion-stuff/blocks-html-parser'; +import { PostResult, PropertyValue } from '@notion-stuff/v4-types'; +import type { RouteConfig } from '@scullyio/scully'; -export interface NotionPluginOptions { + +export interface NotionDomRouterPluginOptions extends RouteConfig { + /** + * @requires string Notion DatabaseID + */ + databaseId: string; + + /** + * Notion API Key. This should be provided via NOTION_API_KEY environment variable instead + */ + notionApiKey?: string; + + /** + * A custom function that will process the {PostResult} from Notion. You take over the Frontmatter with this function + */ + postResultProcessor?>(post: PostResult, options: NotionDomRouterPluginOptions, propertyValueParser: (propertyValue: PropertyValue) => any): TObject; + + /** + * A custom function that will resolve the `published` flag that Scully needs. + */ + isPublished?>(frontmatter: TObject): boolean; + + /** + * Default icon for your post + */ + defaultPostIcon?: string; + + /** + * The key that you use as your "Slug" in your Notion table. + * @default "slug" (requires you to have a "Slug" property in your table) + */ + slugKey?: string; + + /** + * The base path of the route when setup in Scully config + * @example + * { + * "/basePath/:slug": { + * basePath: "/basePath" // needs to match /basePath + * } + * } + * + * @default "/blog" + */ + basePath?: string; + titleSuffix?: string; +} + +export interface NotionDomPluginOptions { notionBlocksHtmlParserOptions?: NotionBlocksHtmlParserOptions; } diff --git a/libs/scully-plugin-notion/src/lib/plugin.ts b/libs/scully-plugin-notion/src/lib/plugin.ts index f6246e5..1d82561 100644 --- a/libs/scully-plugin-notion/src/lib/plugin.ts +++ b/libs/scully-plugin-notion/src/lib/plugin.ts @@ -1,20 +1,21 @@ import { NotionBlocksHtmlParser } from '@notion-stuff/blocks-html-parser'; import { Client } from '@notionhq/client/build/src'; import type { HandledRoute, RouteConfig } from '@scullyio/scully'; -import { - getPluginConfig, - log, - red, - registerPlugin, - yellow, -} from '@scullyio/scully'; +import { getPluginConfig, log, red, registerPlugin, yellow } from '@scullyio/scully'; import { injectHtml } from '@scullyio/scully/src/lib/renderPlugins/content-render-utils/injectHtml'; -import { pagePropertiesToFrontmatter } from './page-properties-to-frontmatter'; -import type { NotionPluginOptions } from './plugin-options'; +import type { NotionDomPluginOptions, NotionDomRouterPluginOptions } from './plugin-options'; +import { processPageProperties } from './process-page-properties'; export const NotionDom = 'notionDom'; export const NotionDomRouter = 'notionDomRouter'; +const pluginOptions: NotionDomPluginOptions = + getPluginConfig(NotionDom, 'postProcessByDom') || {}; + +const notionBlocksHtmlParser = NotionBlocksHtmlParser.getInstance( + pluginOptions.notionBlocksHtmlParserOptions +); + let notion: Client; try { @@ -29,12 +30,12 @@ try { async function notionDomRouterPlugin( route: string | undefined, - config: RouteConfig + config: NotionDomRouterPluginOptions ) { const mergedConfig = { - ...{ slugKey: 'slug', basePath: '/blog', titleSuffix: '' }, - ...(config || {}), - } as RouteConfig; + ...{ slugKey: 'slug', basePath: '/blog', titleSuffix: '', defaultPostIcon: '' }, + ...config + }; try { if (!notion) { @@ -47,17 +48,36 @@ async function notionDomRouterPlugin( } notion = new Client({ - auth: mergedConfig.notionApiKey, + auth: mergedConfig.notionApiKey }); } const posts = await notion.databases.query({ - database_id: mergedConfig.databaseId, + database_id: mergedConfig.databaseId }); return Promise.resolve( posts.results.map((postResult) => { - const frontmatter = pagePropertiesToFrontmatter(postResult.properties); + const frontmatter = processPageProperties(postResult, mergedConfig); + + const { url: cover } = + NotionBlocksHtmlParser.getMarkdownParser().parseFile( + postResult.cover + ); + + let icon = mergedConfig.defaultPostIcon; + switch (postResult.icon.type) { + case 'emoji': + icon = postResult.icon.emoji; + break; + case 'external': + case 'file': + icon = NotionBlocksHtmlParser.getMarkdownParser().parseFile( + postResult.icon + ).url; + break; + } + return { type: mergedConfig.type, route: `${mergedConfig.basePath}/${ @@ -70,8 +90,9 @@ async function notionDomRouterPlugin( ...frontmatter, id: postResult.id, notionUrl: postResult.url, - cover: postResult.cover, - }, + cover, + icon + } } as HandledRoute; }) ); @@ -107,19 +128,13 @@ async function notionDomPlugin(dom: any, route: HandledRoute | undefined) { try { const blocks = await notion.blocks.children.list({ - block_id: postId, + block_id: postId }); if (!blocks || !blocks.results.length) { log(yellow(`Post does not have any blocks. Skipping ${route.route}`)); return Promise.resolve(dom); } - const pluginOptions: NotionPluginOptions = - getPluginConfig(NotionDom, 'postProcessByDom') || {}; - const notionBlocksHtmlParser = NotionBlocksHtmlParser.getInstance( - pluginOptions.notionBlocksHtmlParserOptions - ); - return injectHtml(dom, notionBlocksHtmlParser.parse(blocks.results), route); } catch (e) { log(red(`Something went wrong. ${e}`)); diff --git a/libs/scully-plugin-notion/src/lib/page-properties-to-frontmatter.ts b/libs/scully-plugin-notion/src/lib/process-page-properties.ts similarity index 83% rename from libs/scully-plugin-notion/src/lib/page-properties-to-frontmatter.ts rename to libs/scully-plugin-notion/src/lib/process-page-properties.ts index d4ca151..04770b5 100644 --- a/libs/scully-plugin-notion/src/lib/page-properties-to-frontmatter.ts +++ b/libs/scully-plugin-notion/src/lib/process-page-properties.ts @@ -1,27 +1,36 @@ import type { + PostResult, PropertyValue, PropertyValueFormula, - PropertyValueMap, PropertyValueRollup, PropertyValueSelect, - PropertyValueUser, + PropertyValueUser } from '@notion-stuff/v4-types'; +import type { NotionDomRouterPluginOptions } from './plugin-options'; import { camelize } from './utils'; -export function pagePropertiesToFrontmatter(properties: PropertyValueMap) { +export function processPageProperties(post: PostResult, options: NotionDomRouterPluginOptions) { + if (options.postResultProcessor) { + return options.postResultProcessor(post, options, parsePropertyValue); + } + const frontmatter: Record = { published: false }; - for (const [propertyKey, propertyValue] of Object.entries(properties)) { + for (const [propertyKey, propertyValue] of Object.entries(post.properties)) { const camelizedKey = camelize(propertyKey); frontmatter[camelizedKey] = parsePropertyValue(propertyValue); - if (propertyKey.toLowerCase() === 'status') { + if (!options.isPublished && propertyKey.toLowerCase() === 'status') { frontmatter.published = (propertyValue as PropertyValueSelect).select?.name.toLowerCase() === 'published'; } } + if (options.isPublished) { + frontmatter.published = options.isPublished(frontmatter); + } + return frontmatter; } @@ -44,7 +53,7 @@ function parsePropertyValue(propertyValue: PropertyValue) { if (propertyValue.date.end) { return [ new Date(propertyValue.date.start), - new Date(propertyValue.date.end), + new Date(propertyValue.date.end) ]; } return new Date(propertyValue.date.start); @@ -92,7 +101,7 @@ function formularize(formulaValue: PropertyValueFormula) { if (formulaValue.formula.date?.end) { value = [ formulaValue.formula.date?.start, - formulaValue.formula.date?.end, + formulaValue.formula.date?.end ]; } else { value = formulaValue.formula.date?.start; @@ -129,6 +138,6 @@ function rollup(rollupValue: PropertyValueRollup) { function personify(user: PropertyValueUser) { return { name: user.name, - avatar: user.avatar_url, + avatar: user.avatar_url }; }