Skip to content

Created a plugin for frontmatter #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/config/default.docunotion.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/FronmatterTransformer";

const defaultConfig: IDocuNotionConfig = {
plugins: [
Expand All @@ -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,
Expand Down
153 changes: 153 additions & 0 deletions src/plugins/FronmatterTransformer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { GetPageResponse } from "@notionhq/client/build/src/api-endpoints";
import { NotionPage } from "../NotionPage";
import { standardFrontmatterTransformer } from "./FronmatterTransformer";

let getFrontMatter = standardFrontmatterTransformer.frontmatterTransform
?.build as (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(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(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(page);

expect(result).toEqual(expectedFrontmatter);
});
});
20 changes: 20 additions & 0 deletions src/plugins/FronmatterTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { IPlugin } from "./pluginTypes";
import { NotionPage } from "../NotionPage";

function getFrontmatter(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",

frontmatterTransform: {
build: getFrontmatter,
},
};
5 changes: 5 additions & 0 deletions src/plugins/pluginTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
frontmatterTransform?: {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not completely sold on the frontmatterTransform name. Can you think of a better one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about frontMatterGenerator? I'm camel casing "Matter" because front matter is normally written as two words.

build: (page: NotionPage) => string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about getFrontMatter() to be parallel with getStringFromBlock() and getReplacement().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recently I broke the plugin API when someone needed the context: IDocuNotionContext,. Although I don't know why it might be needed, let's just include it here, from the start. Please make it the 1st argument.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I definitely need the context in a lot of places. Due to the complexity of interconnected documents, providing the full context makes it easier to write custom plugins without the complexity of figuring out where to get the context you need.

};

// 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<void>;
Expand Down
19 changes: 11 additions & 8 deletions src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ 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
const frontmatter = getMarkdownFrontMatter(config, page);
return `${frontmatter}\n${body}`;
}

Expand Down Expand Up @@ -252,14 +252,17 @@ function registerNotionToMarkdownCustomTransforms(
});
}

// enhance:make this built-in plugin so that it can be overridden
function getFrontMatter(page: NotionPage): string {
function getMarkdownFrontMatter(
config: IDocuNotionConfig,
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`;

config.plugins.forEach(plugin => {
if (plugin.frontmatterTransform) {
logDebug("transforming page with plugin", plugin.name);
frontmatter += plugin.frontmatterTransform?.build(page);
}
});
frontmatter += "---\n";
return frontmatter;
}