diff --git a/src/config/default.docunotion.config.ts b/src/config/default.docunotion.config.ts index ee3dad4..33fa4ad 100644 --- a/src/config/default.docunotion.config.ts +++ b/src/config/default.docunotion.config.ts @@ -15,6 +15,7 @@ import { standardNumberedListTransformer } from "../plugins/NumberedListTransfor import { standardTableTransformer } from "../plugins/TableTransformer"; import { standardExternalLinkConversion } from "../plugins/externalLinks"; import { IDocuNotionConfig } from "./configuration"; +import { standardFrontmatterTransformer } from "../plugins/FrontMatterTransformer"; const defaultConfig: IDocuNotionConfig = { plugins: [ @@ -35,6 +36,9 @@ const defaultConfig: IDocuNotionConfig = { standardInternalLinkConversion, standardExternalLinkConversion, + // Frontmatter transformers, add information to the page frontMatter + standardFrontmatterTransformer, + // Regexps plus javascript `import`s that operate on the Markdown output imgurGifEmbed, gifEmbed, diff --git a/src/plugins/FrontMatterTransformer.spec.ts b/src/plugins/FrontMatterTransformer.spec.ts new file mode 100644 index 0000000..ce18cf8 --- /dev/null +++ b/src/plugins/FrontMatterTransformer.spec.ts @@ -0,0 +1,154 @@ +import { GetPageResponse } from "@notionhq/client/build/src/api-endpoints"; +import { NotionPage } from "../NotionPage"; +import { standardFrontmatterTransformer } from "./FrontMatterTransformer"; +import { IDocuNotionContext } from ".."; + +const getFrontMatter = standardFrontmatterTransformer.frontMatterGenerator + ?.getFrontMatter as (context: IDocuNotionContext, page: NotionPage) => string; + +const sampleMetadata: GetPageResponse = { + object: "page", + id: "6e6921b9-b1f5-4614-ab3c-bf1a73358a1f", + created_time: "2023-04-11T10:17:00.000Z", + last_edited_time: "2023-04-13T20:24:00.000Z", + created_by: { + object: "user", + id: "USERID", + }, + last_edited_by: { + object: "user", + id: "USERID", + }, + cover: null, + icon: { + type: "file", + file: { + url: "https:/dummy_URL", + expiry_time: "2023-04-15T11:50:20.461Z", + }, + }, + parent: { + type: "workspace", + workspace: true, + }, + archived: false, + properties: { + title: { + id: "title", + type: "title", + title: [ + { + type: "text", + text: { + content: "Foo", + link: null, + }, + annotations: { + bold: false, + italic: false, + strikethrough: false, + underline: false, + code: false, + color: "default", + }, + plain_text: "Foo", + href: null, + }, + { + type: "text", + text: { + content: "Bar", + link: null, + }, + annotations: { + bold: false, + italic: false, + strikethrough: false, + underline: false, + code: false, + color: "default", + }, + plain_text: "Bar", + href: null, + }, + ], + }, + Keywords: { + id: "keywords", + type: "rich_text", + rich_text: [ + { + type: "text", + text: { + content: "Foo, Bar", + link: null, + }, + annotations: { + bold: false, + italic: false, + strikethrough: false, + underline: false, + code: false, + color: "default", + }, + plain_text: "Foo, Bar", + href: null, + }, + ], + }, + date_property: { + id: "a%3Cql", + type: "date", + date: { + start: "2021-10-24", + end: "2021-10-28", + time_zone: null, + }, + }, + }, + url: "https://www.notion.so/Site-docu-notion-PAGEID", +}; + +describe("getFrontMatter", () => { + let page: NotionPage; + + beforeEach(() => { + page = new NotionPage({ + layoutContext: "Test Context", + pageId: "123", + order: 1, + metadata: JSON.parse(JSON.stringify(sampleMetadata)), + foundDirectlyInOutline: true, + }); + }); + + it("should generate frontMatter with all available properties", () => { + const expectedFrontmatter = `title: FooBar\nsidebar_position: 1\nslug: /123\nkeywords: [Foo, Bar]\n`; + (page.metadata as any).properties.Keywords.rich_text[0].plain_text = + "Foo, Bar"; + + const result = getFrontMatter({} as IDocuNotionContext, page); + + expect(result).toEqual(expectedFrontmatter); + }); + + // "title: Foo-Barsidebar_position: 1slug: keywords: [Foo, Bar]" + // "title: FooBar\nsidebar_position: 1\nslug: /123\n" + it("should generate frontMatter with no keywords", () => { + const expectedFrontmatter = `title: FooBar\nsidebar_position: 1\nslug: /123\n`; + (page.metadata as any).properties.Keywords = undefined; + + const result = getFrontMatter({} as IDocuNotionContext, page); + + expect(result).toEqual(expectedFrontmatter); + }); + + it("should replace colons with dashes in the title", () => { + const expectedFrontmatter = `title: FooBaz-\nsidebar_position: 1\nslug: /123\nkeywords: [Foo, Bar]\n`; + (page.metadata as any).properties.title.title[1].plain_text = "Baz:"; + + const result = getFrontMatter({} as IDocuNotionContext, page); + + expect(result).toEqual(expectedFrontmatter); + }); +}); diff --git a/src/plugins/FrontMatterTransformer.ts b/src/plugins/FrontMatterTransformer.ts new file mode 100644 index 0000000..7c36778 --- /dev/null +++ b/src/plugins/FrontMatterTransformer.ts @@ -0,0 +1,20 @@ +import { IDocuNotionContext, IPlugin } from "./pluginTypes"; +import { NotionPage } from "../NotionPage"; + +function getFrontmatter(context: IDocuNotionContext, page: NotionPage): string { + let frontMatter = ""; + frontMatter += `title: ${page.nameOrTitle.replaceAll(":", "-")}\n`; // I have not found a way to escape colons + frontMatter += `sidebar_position: ${page.order}\n`; + frontMatter += `slug: ${page.slug ?? ""}\n`; + if (page.keywords) frontMatter += `keywords: [${page.keywords}]\n`; + + return frontMatter; +} + +export const standardFrontmatterTransformer: IPlugin = { + name: "standardFrontmatterTransformer", + + frontMatterGenerator: { + getFrontMatter: getFrontmatter, + }, +}; diff --git a/src/plugins/pluginTestRun.ts b/src/plugins/pluginTestRun.ts index b19877d..d5a4a17 100644 --- a/src/plugins/pluginTestRun.ts +++ b/src/plugins/pluginTestRun.ts @@ -92,7 +92,7 @@ export async function blocksToMarkdown( return r; } -// This is used for things like testing links to other pages and frontmatter creation, +// This is used for things like testing links to other pages and frontMatter creation, // when just testing what happens to individual blocks is not enough. // after getting this, you can make changes to it, then pass it to blocksToMarkdown export function makeSamplePageObject(options: { diff --git a/src/plugins/pluginTypes.ts b/src/plugins/pluginTypes.ts index 7c3eb5c..86d67bd 100644 --- a/src/plugins/pluginTypes.ts +++ b/src/plugins/pluginTypes.ts @@ -35,6 +35,11 @@ export type IPlugin = { // simple regex replacements on the markdown output regexMarkdownModifications?: IRegexMarkdownModification[]; + // operations on pages to define the markdown's frontMatter + frontMatterGenerator?: { + getFrontMatter: (context: IDocuNotionContext, page: NotionPage) => string; + }; + // Allow a plugin to perform an async operation at the start of docu-notion. // Notice that the plugin itself is given, so you can add things to it. init?(plugin: IPlugin): Promise; diff --git a/src/transform.ts b/src/transform.ts index 7a8f232..bc99255 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -30,8 +30,8 @@ export async function getMarkdownForPage( logDebugFn("markdown from page", () => JSON.stringify(blocks, null, 2)); const body = await getMarkdownFromNotionBlocks(context, config, blocks); - const frontmatter = getFrontMatter(page); // todo should be a plugin - return `${frontmatter}\n${body}`; + const frontMatter = getMarkdownFrontMatter(context, config, page); + return `${frontMatter}\n${body}`; } // this is split off from getMarkdownForPage so that unit tests can provide the block contents @@ -252,14 +252,18 @@ function registerNotionToMarkdownCustomTransforms( }); } -// enhance:make this built-in plugin so that it can be overridden -function getFrontMatter(page: NotionPage): string { - let frontmatter = "---\n"; - frontmatter += `title: ${page.nameOrTitle.replaceAll(":", "-")}\n`; // I have not found a way to escape colons - frontmatter += `sidebar_position: ${page.order}\n`; - frontmatter += `slug: ${page.slug ?? ""}\n`; - if (page.keywords) frontmatter += `keywords: [${page.keywords}]\n`; - - frontmatter += "---\n"; - return frontmatter; +function getMarkdownFrontMatter( + context: IDocuNotionContext, + config: IDocuNotionConfig, + page: NotionPage +): string { + let frontMatter = "---\n"; + config.plugins.forEach(plugin => { + if (plugin.frontMatterGenerator) { + logDebug("transforming page with plugin", plugin.name); + frontMatter += plugin.frontMatterGenerator?.getFrontMatter(context, page); + } + }); + frontMatter += "---\n"; + return frontMatter; }