Skip to content

Better handling of callouts (BL-11548) #12

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

Merged
merged 1 commit into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
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
126 changes: 126 additions & 0 deletions src/CalloutTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { NotionToMarkdown } from "notion-to-md";
import { ListBlockChildrenResponseResult } from "notion-to-md/build/types";
import { Client } from "@notionhq/client";
import { getBlockChildren } from "./CustomTranformers";

export async function notionCalloutToAdmonition(
notionToMarkdown: NotionToMarkdown,
notionClient: Client,
block: ListBlockChildrenResponseResult
): Promise<string> {
// In this case typescript is not able to index the types properly, hence ignoring the error
// @ts-ignore
const blockContent = block.callout.text || block.callout.rich_text || [];
// @ts-ignore
const icon = block.callout.icon;
let parsedData = "";
blockContent.map((content: Text) => {
const annotations = content.annotations;
let plain_text = content.plain_text;

plain_text = notionToMarkdown.annotatePlainText(plain_text, annotations);

// if (content["href"])
// plain_text = md.link(plain_text, content["href"]);

parsedData += plain_text;
});

let callout_string = "";
const { id, has_children } = block as any;
if (!has_children) {
const result1 = callout(parsedData, icon);
return result1;
}

const callout_children_object = await getBlockChildren(notionClient, id, 100);

// // parse children blocks to md object
const callout_children = await notionToMarkdown.blocksToMarkdown(
callout_children_object
);

callout_string += `${parsedData}\n`;
callout_children.map(child => {
callout_string += `${child.parent}\n\n`;
});

const result = callout(callout_string.trim(), icon);
return result;
}

// types copied from notion-to-md to allow compilation of copied code.
type TextRequest = string;

type Annotations = {
bold: boolean;
italic: boolean;
strikethrough: boolean;
underline: boolean;
code: boolean;
color:
| "default"
| "gray"
| "brown"
| "orange"
| "yellow"
| "green"
| "blue"
| "purple"
| "pink"
| "red"
| "gray_background"
| "brown_background"
| "orange_background"
| "yellow_background"
| "green_background"
| "blue_background"
| "purple_background"
| "pink_background"
| "red_background";
};
type Text = {
type: "text";
text: {
content: string;
link: {
url: TextRequest;
} | null;
};
annotations: Annotations;
plain_text: string;
href: string | null;
};

type CalloutIcon =
| { type: "emoji"; emoji?: string }
| { type: "external"; external?: { url: string } }
| { type: "file"; file: { url: string; expiry_time: string } }
| null;

const calloutsToAdmonitions = {
/* prettier-ignore */ "ℹ️": "note",
"💡": "tip",
"❗": "info",
"⚠️": "caution",
"🔥": "danger",
};

// This is the main change from the notion-to-md code.
function callout(text: string, icon?: CalloutIcon) {
let emoji: string | undefined;
if (icon?.type === "emoji") {
emoji = icon.emoji;
}
let docusaurusAdmonition = "note";
if (emoji) {
// the keyof typeof magic persuades typescript that it really is OK to use emoji as a key into calloutsToAdmonitions
docusaurusAdmonition =
calloutsToAdmonitions[emoji as keyof typeof calloutsToAdmonitions] ??
// For Notion callouts with other emojis, pass them through using hte emoji as the name.
// For this to work on a Docusaurus site, it will need to define that time on the remark-admonitions options in the docusaurus.config.js.
// See https://github.com/elviswolcott/remark-admonitions and https://docusaurus.io/docs/using-plugins#using-presets.
emoji;
}
return `:::${docusaurusAdmonition}\n\n${text}\n\n:::\n\n`;
}
18 changes: 16 additions & 2 deletions src/CustomTranformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ListBlockChildrenResponseResult,
ListBlockChildrenResponseResults,
} from "notion-to-md/build/types";
import { notionCalloutToAdmonition } from "./CalloutTransformer";

export function setupCustomTransformers(
notionToMarkdown: NotionToMarkdown,
Expand Down Expand Up @@ -76,6 +77,19 @@ export function setupCustomTransformers(
}
);

// In Notion, you can make a callout and change its emoji. We map 5 of these
// to the 5 Docusaurus admonition styles.
// This is mostly a copy of the callout code from notion-to-md. The change is to output docusaurus
// admonitions instead of emulating a callout with markdown > syntax.
// Note: I haven't yet tested this with any emoji except "💡"/"tip", nor the case where the
// callout has-children. Not even sure what that would mean, since the document I was testing
// with has quite complex markup inside the callout, but still takes the no-children branch.
notionToMarkdown.setCustomTransformer(
"callout",
(block: ListBlockChildrenResponseResult) =>
notionCalloutToAdmonition(notionToMarkdown, notionClient, block)
);

// Note: Pull.ts also adds an image transformer, but has to do that for each
// page so we don't do it here.
}
Expand Down Expand Up @@ -127,11 +141,11 @@ async function notionColumnToMarkdown(
)}\n\n</div>`;
}

async function getBlockChildren(
export async function getBlockChildren(
notionClient: Client,
block_id: string,
totalPage: number | null
) {
): Promise<ListBlockChildrenResponseResults> {
try {
const result: ListBlockChildrenResponseResults = [];
let pageCount = 0;
Expand Down
39 changes: 1 addition & 38 deletions src/DocusaurusTweaks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ export function tweakForDocusaurus(input: string): {
body: string;
imports: string;
} {
const output = notionCalloutsToAdmonitions(input);
const { body, imports } = notionEmbedsToMDX(output);
const { body, imports } = notionEmbedsToMDX(input);
return { body, imports };
}
// In Notion, you can embed videos & such. To show these
Expand Down Expand Up @@ -101,39 +100,3 @@ function notionEmbedsToMDX(input: string): {

return { body, imports: [...imports].join("\n") };
}

// In Notion, you can make a callout and change its emoji. We map 5 of these
// to the 5 Docusaurus admonition styles.
function notionCalloutsToAdmonitions(input: string): string {
const notionCalloutPattern = />\s(ℹ️|⚠️|💡|❗|🔥|.)\s(.*)\n/gmu;
const calloutsToAdmonitions = {
/* prettier-ignore */ "ℹ️": "note",
"💡": "tip",
"❗": "info",
"⚠️": "caution",
"🔥": "danger",
};
let output = input;
let match;
while ((match = notionCalloutPattern.exec(input)) !== null) {
const string = match[0];
const emoji = match[1] as keyof typeof calloutsToAdmonitions;
const content = match[2];

const docusaurusAdmonition = calloutsToAdmonitions[emoji];
if (docusaurusAdmonition) {
output = output.replace(
string,
`:::${docusaurusAdmonition}\n\n${content}\n\n:::\n\n`
);
}
// For Notion callouts with other emojis, pass them through using hte emoji as the name.
// For this to work on a Docusaurus site, it will need to define that time on the remark-admonitions options in the docusaurus.config.js.
// See https://github.com/elviswolcott/remark-admonitions and https://docusaurus.io/docs/using-plugins#using-presets.
else {
output = output.replace(string, `:::${emoji}\n\n${content}\n\n:::\n\n`);
}
}

return output;
}