Skip to content

Commit 054f098

Browse files
committed
fix: better handling of callouts (BL-11548)
1 parent 40346d4 commit 054f098

File tree

3 files changed

+143
-40
lines changed

3 files changed

+143
-40
lines changed

src/CalloutTransformer.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { NotionToMarkdown } from "notion-to-md";
2+
import { ListBlockChildrenResponseResult } from "notion-to-md/build/types";
3+
import { Client } from "@notionhq/client";
4+
import { getBlockChildren } from "./CustomTranformers";
5+
6+
export async function notionCalloutToAdmonition(
7+
notionToMarkdown: NotionToMarkdown,
8+
notionClient: Client,
9+
block: ListBlockChildrenResponseResult
10+
): Promise<string> {
11+
// In this case typescript is not able to index the types properly, hence ignoring the error
12+
// @ts-ignore
13+
const blockContent = block.callout.text || block.callout.rich_text || [];
14+
// @ts-ignore
15+
const icon = block.callout.icon;
16+
let parsedData = "";
17+
blockContent.map((content: Text) => {
18+
const annotations = content.annotations;
19+
let plain_text = content.plain_text;
20+
21+
plain_text = notionToMarkdown.annotatePlainText(plain_text, annotations);
22+
23+
// if (content["href"])
24+
// plain_text = md.link(plain_text, content["href"]);
25+
26+
parsedData += plain_text;
27+
});
28+
29+
let callout_string = "";
30+
const { id, has_children } = block as any;
31+
if (!has_children) {
32+
const result1 = callout(parsedData, icon);
33+
return result1;
34+
}
35+
36+
const callout_children_object = await getBlockChildren(notionClient, id, 100);
37+
38+
// // parse children blocks to md object
39+
const callout_children = await notionToMarkdown.blocksToMarkdown(
40+
callout_children_object
41+
);
42+
43+
callout_string += `${parsedData}\n`;
44+
callout_children.map(child => {
45+
callout_string += `${child.parent}\n\n`;
46+
});
47+
48+
const result = callout(callout_string.trim(), icon);
49+
return result;
50+
}
51+
52+
// types copied from notion-to-md to allow compilation of copied code.
53+
type TextRequest = string;
54+
55+
type Annotations = {
56+
bold: boolean;
57+
italic: boolean;
58+
strikethrough: boolean;
59+
underline: boolean;
60+
code: boolean;
61+
color:
62+
| "default"
63+
| "gray"
64+
| "brown"
65+
| "orange"
66+
| "yellow"
67+
| "green"
68+
| "blue"
69+
| "purple"
70+
| "pink"
71+
| "red"
72+
| "gray_background"
73+
| "brown_background"
74+
| "orange_background"
75+
| "yellow_background"
76+
| "green_background"
77+
| "blue_background"
78+
| "purple_background"
79+
| "pink_background"
80+
| "red_background";
81+
};
82+
type Text = {
83+
type: "text";
84+
text: {
85+
content: string;
86+
link: {
87+
url: TextRequest;
88+
} | null;
89+
};
90+
annotations: Annotations;
91+
plain_text: string;
92+
href: string | null;
93+
};
94+
95+
type CalloutIcon =
96+
| { type: "emoji"; emoji?: string }
97+
| { type: "external"; external?: { url: string } }
98+
| { type: "file"; file: { url: string; expiry_time: string } }
99+
| null;
100+
101+
const calloutsToAdmonitions = {
102+
/* prettier-ignore */ "ℹ️": "note",
103+
"💡": "tip",
104+
"❗": "info",
105+
"⚠️": "caution",
106+
"🔥": "danger",
107+
};
108+
109+
// This is the main change from the notion-to-md code.
110+
function callout(text: string, icon?: CalloutIcon) {
111+
let emoji: string | undefined;
112+
if (icon?.type === "emoji") {
113+
emoji = icon.emoji;
114+
}
115+
let docusaurusAdmonition = "note";
116+
if (emoji) {
117+
// the keyof typeof magic persuades typescript that it really is OK to use emoji as a key into calloutsToAdmonitions
118+
docusaurusAdmonition =
119+
calloutsToAdmonitions[emoji as keyof typeof calloutsToAdmonitions] ??
120+
// For Notion callouts with other emojis, pass them through using hte emoji as the name.
121+
// 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.
122+
// See https://github.com/elviswolcott/remark-admonitions and https://docusaurus.io/docs/using-plugins#using-presets.
123+
emoji;
124+
}
125+
return `:::${docusaurusAdmonition}\n\n${text}\n\n:::\n\n`;
126+
}

src/CustomTranformers.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
ListBlockChildrenResponseResult,
77
ListBlockChildrenResponseResults,
88
} from "notion-to-md/build/types";
9+
import { notionCalloutToAdmonition } from "./CalloutTransformer";
910

1011
export function setupCustomTransformers(
1112
notionToMarkdown: NotionToMarkdown,
@@ -76,6 +77,19 @@ export function setupCustomTransformers(
7677
}
7778
);
7879

80+
// In Notion, you can make a callout and change its emoji. We map 5 of these
81+
// to the 5 Docusaurus admonition styles.
82+
// This is mostly a copy of the callout code from notion-to-md. The change is to output docusaurus
83+
// admonitions instead of emulating a callout with markdown > syntax.
84+
// Note: I haven't yet tested this with any emoji except "💡"/"tip", nor the case where the
85+
// callout has-children. Not even sure what that would mean, since the document I was testing
86+
// with has quite complex markup inside the callout, but still takes the no-children branch.
87+
notionToMarkdown.setCustomTransformer(
88+
"callout",
89+
(block: ListBlockChildrenResponseResult) =>
90+
notionCalloutToAdmonition(notionToMarkdown, notionClient, block)
91+
);
92+
7993
// Note: Pull.ts also adds an image transformer, but has to do that for each
8094
// page so we don't do it here.
8195
}
@@ -127,11 +141,11 @@ async function notionColumnToMarkdown(
127141
)}\n\n</div>`;
128142
}
129143

130-
async function getBlockChildren(
144+
export async function getBlockChildren(
131145
notionClient: Client,
132146
block_id: string,
133147
totalPage: number | null
134-
) {
148+
): Promise<ListBlockChildrenResponseResults> {
135149
try {
136150
const result: ListBlockChildrenResponseResults = [];
137151
let pageCount = 0;

src/DocusaurusTweaks.ts

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ export function tweakForDocusaurus(input: string): {
44
body: string;
55
imports: string;
66
} {
7-
const output = notionCalloutsToAdmonitions(input);
8-
const { body, imports } = notionEmbedsToMDX(output);
7+
const { body, imports } = notionEmbedsToMDX(input);
98
return { body, imports };
109
}
1110
// In Notion, you can embed videos & such. To show these
@@ -101,39 +100,3 @@ function notionEmbedsToMDX(input: string): {
101100

102101
return { body, imports: [...imports].join("\n") };
103102
}
104-
105-
// In Notion, you can make a callout and change its emoji. We map 5 of these
106-
// to the 5 Docusaurus admonition styles.
107-
function notionCalloutsToAdmonitions(input: string): string {
108-
const notionCalloutPattern = />\s(||💡||🔥|.)\s(.*)\n/gmu;
109-
const calloutsToAdmonitions = {
110-
/* prettier-ignore */ "ℹ️": "note",
111-
"💡": "tip",
112-
"❗": "info",
113-
"⚠️": "caution",
114-
"🔥": "danger",
115-
};
116-
let output = input;
117-
let match;
118-
while ((match = notionCalloutPattern.exec(input)) !== null) {
119-
const string = match[0];
120-
const emoji = match[1] as keyof typeof calloutsToAdmonitions;
121-
const content = match[2];
122-
123-
const docusaurusAdmonition = calloutsToAdmonitions[emoji];
124-
if (docusaurusAdmonition) {
125-
output = output.replace(
126-
string,
127-
`:::${docusaurusAdmonition}\n\n${content}\n\n:::\n\n`
128-
);
129-
}
130-
// For Notion callouts with other emojis, pass them through using hte emoji as the name.
131-
// 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.
132-
// See https://github.com/elviswolcott/remark-admonitions and https://docusaurus.io/docs/using-plugins#using-presets.
133-
else {
134-
output = output.replace(string, `:::${emoji}\n\n${content}\n\n:::\n\n`);
135-
}
136-
}
137-
138-
return output;
139-
}

0 commit comments

Comments
 (0)