Skip to content

Commit 3811cc3

Browse files
author
Sergei Orlov ||↓
authored
✨ Merge pull request orlowdev#12 from LandonSchropp/main
Refactor notionBlockToMarkdown function
2 parents 10d8c9f + 1ad9481 commit 3811cc3

File tree

1 file changed

+136
-150
lines changed

1 file changed

+136
-150
lines changed

Diff for: src/transformers/notion-block-to-markdown.js

+136-150
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,139 @@
11
const { blockToString } = require("../block-to-string")
22

33
const EOL_MD = "\n"
4-
5-
exports.notionBlockToMarkdown = (block, lowerTitleLevel, depth = 0) =>
6-
block.children.reduce((acc, childBlock) => {
7-
let childBlocksString = ""
8-
9-
if (childBlock.has_children) {
10-
childBlocksString = " "
11-
.repeat(depth)
12-
.concat(childBlocksString)
13-
.concat(this.notionBlockToMarkdown(childBlock, lowerTitleLevel, depth + 2))
14-
.concat(EOL_MD)
15-
}
16-
17-
if (childBlock.type == "paragraph") {
18-
const p = blockToString(childBlock.paragraph.text)
19-
20-
const isTableRow = p.startsWith("|") && p.endsWith("|")
21-
22-
return acc
23-
.concat(p)
24-
.concat(isTableRow ? EOL_MD : EOL_MD.concat(EOL_MD))
25-
.concat(childBlocksString)
26-
}
27-
28-
if (childBlock.type.startsWith("heading_")) {
29-
const headingLevel = Number(childBlock.type.split("_")[1])
30-
31-
return acc
32-
.concat(EOL_MD)
33-
.concat(lowerTitleLevel ? "#" : "")
34-
.concat("#".repeat(headingLevel))
35-
.concat(" ")
36-
.concat(blockToString(childBlock[childBlock.type].text))
37-
.concat(EOL_MD)
38-
.concat(childBlocksString)
39-
}
40-
41-
if (childBlock.type == "to_do") {
42-
return acc
43-
.concat(`- [${childBlock.to_do.checked ? "x" : " "}] `)
44-
.concat(blockToString(childBlock.to_do.text))
45-
.concat(EOL_MD)
46-
.concat(childBlocksString)
47-
}
48-
49-
if (childBlock.type == "bulleted_list_item") {
50-
return acc
51-
.concat("* ")
52-
.concat(blockToString(childBlock.bulleted_list_item.text))
53-
.concat(EOL_MD)
54-
.concat(childBlocksString)
55-
}
56-
57-
if (childBlock.type == "numbered_list_item") {
58-
return acc
59-
.concat("1. ")
60-
.concat(blockToString(childBlock.numbered_list_item.text))
61-
.concat(EOL_MD)
62-
.concat(childBlocksString)
63-
}
64-
65-
if (childBlock.type == "toggle") {
66-
return acc
67-
.concat("<details><summary>")
68-
.concat(blockToString(childBlock.toggle.text))
69-
.concat("</summary>")
70-
.concat(childBlocksString)
71-
.concat("</details>")
72-
}
73-
74-
if (childBlock.type == "code") {
75-
return acc
76-
.concat(EOL_MD)
77-
.concat("```", childBlock.code.language, EOL_MD)
78-
.concat(blockToString(childBlock.code.text))
79-
.concat(EOL_MD)
80-
.concat("```")
81-
.concat(childBlocksString)
82-
.concat(EOL_MD)
83-
}
84-
85-
if (childBlock.type == "image") {
86-
const imageUrl =
87-
childBlock.image.type == "external" ? childBlock.image.external.url : childBlock.image.file.url
88-
89-
return acc
90-
.concat("![")
91-
.concat(blockToString(childBlock.image.caption))
92-
.concat("](")
93-
.concat(imageUrl)
94-
.concat(")")
95-
.concat(EOL_MD)
96-
}
97-
98-
if (childBlock.type == "audio") {
99-
const audioUrl =
100-
childBlock.audio.type == "external" ? childBlock.audio.external.url : childBlock.audio.file.url
101-
102-
return acc
103-
.concat("<audio controls>")
104-
.concat(EOL_MD)
105-
.concat(`<source src="${audioUrl}" />`)
106-
.concat(EOL_MD)
107-
.concat("</audio>")
108-
.concat(EOL_MD)
109-
}
110-
111-
if (childBlock.type == "video" && childBlock.video.type == "external") {
112-
const videoUrl = childBlock.video.external.url
113-
114-
return acc.concat(videoUrl).concat(EOL_MD)
115-
}
116-
117-
if (childBlock.type == "embed") {
118-
return acc.concat(childBlock.embed.url).concat(EOL_MD)
119-
}
120-
121-
if (childBlock.type == "quote") {
122-
return acc.concat("> ").concat(blockToString(childBlock.quote.text)).concat(EOL_MD)
123-
}
124-
125-
// TODO: Add support for callouts, internal video, andd files
126-
127-
if (childBlock.type == "bookmark") {
128-
const bookmarkUrl = childBlock.bookmark.url
129-
130-
const bookmarkCaption = blockToString(childBlock.bookmark.caption) || bookmarkUrl
131-
132-
return acc
133-
.concat("[")
134-
.concat(bookmarkCaption)
135-
.concat("](")
136-
.concat(bookmarkUrl)
137-
.concat(")")
138-
.concat(EOL_MD)
139-
}
140-
141-
if (childBlock.type == "divider") {
142-
return acc.concat("---").concat(EOL_MD)
143-
}
144-
145-
if (childBlock.type == "unsupported") {
146-
return acc
147-
.concat(`<!-- This block is not supported by Notion API yet. -->`)
148-
.concat(EOL_MD)
149-
.concat(childBlocksString)
150-
}
151-
152-
return acc
153-
}, "")
4+
const DOUBLE_EOL_MD = EOL_MD.repeat(2)
5+
6+
// Inserts the string at the beginning of every line of the content. If the useSpaces flag is set to
7+
// true, the lines after the first will instead be prepended with two spaces.
8+
function prependToLines(content, string, useSpaces = true) {
9+
let [head, ...tail] = content.split("\n")
10+
11+
return [
12+
`${string} ${head}`,
13+
...tail.map((line) => {
14+
return `${useSpaces ? " " : string} ${line}`
15+
}),
16+
].join("\n")
17+
}
18+
19+
// Converts a notion block to a markdown string.
20+
exports.notionBlockToMarkdown = (block, lowerTitleLevel) => {
21+
// Get the child content of the block.
22+
let childMarkdown = (block.children ?? [])
23+
.map((block) => this.notionBlockToMarkdown(block, lowerTitleLevel))
24+
.join("")
25+
.trim()
26+
27+
// If the block is a page, return the child content.
28+
if (block.object === "page") {
29+
return childMarkdown
30+
}
31+
32+
// Extract the remaining content of the block and combine it with its children.
33+
let blockMarkdown = block[block.type]?.text ? blockToString(block[block.type]?.text).trim() : null
34+
let markdown = [blockMarkdown, childMarkdown].filter((text) => text).join(DOUBLE_EOL_MD)
35+
36+
// Table row
37+
// TODO: This should be moved to the new Notion type.
38+
if (block.type == "paragraph" && blockMarkdown.startsWith("|") && blockMarkdown.endsWith("|")) {
39+
return markdown.concat(EOL_MD)
40+
}
41+
42+
// Paragraph
43+
if (block.type == "paragraph") {
44+
return [EOL_MD, markdown, EOL_MD].join("")
45+
}
46+
47+
// Heading
48+
if (block.type.startsWith("heading_")) {
49+
const headingLevel = Number(block.type.split("_")[1])
50+
let symbol = (lowerTitleLevel ? "#" : "") + "#".repeat(headingLevel)
51+
return [EOL_MD, prependToLines(markdown, symbol), EOL_MD].join("")
52+
}
53+
54+
// To do list item
55+
if (block.type == "to_do") {
56+
let symbol = `- [${block.to_do.checked ? "x" : " "}] `
57+
return prependToLines(markdown, symbol).concat(EOL_MD)
58+
}
59+
60+
// Bulleted list item
61+
if (block.type == "bulleted_list_item") {
62+
return prependToLines(markdown, "*").concat(EOL_MD)
63+
}
64+
65+
// Numbered list item
66+
if (block.type == "numbered_list_item") {
67+
return prependToLines(markdown, "1.").concat(EOL_MD)
68+
}
69+
70+
// Toggle
71+
if (block.type == "toggle") {
72+
return [
73+
EOL_MD,
74+
"<details><summary>",
75+
blockMarkdown,
76+
"</summary>",
77+
childMarkdown,
78+
"</details>",
79+
EOL_MD,
80+
].join("")
81+
}
82+
83+
// Code
84+
if (block.type == "code") {
85+
return [
86+
EOL_MD,
87+
`\`\`\` ${block.code.language}${EOL_MD}`,
88+
blockMarkdown,
89+
EOL_MD,
90+
"```",
91+
EOL_MD,
92+
childMarkdown,
93+
EOL_MD,
94+
].join("")
95+
}
96+
97+
// Image
98+
if (block.type == "image") {
99+
const imageUrl = block.image.type == "external" ? block.image.external.url : block.image.file.url
100+
return `${EOL_MD}![${blockToString(block.image.caption)}](${imageUrl})${EOL_MD}`
101+
}
102+
103+
// Audio
104+
if (block.type == "audio") {
105+
const audioUrl = block.audio.type == "external" ? block.audio.external.url : block.audio.file.url
106+
return [EOL_MD, "<audio controls>", `<source src="${audioUrl}" />`, "</audio>", EOL_MD].join("")
107+
}
108+
109+
// Video
110+
if (block.type == "video" && block.video.type == "external") {
111+
return [EOL_MD, block.video.external.url, EOL_MD].join("")
112+
}
113+
114+
// Embed
115+
if (block.type == "embed") {
116+
return [EOL_MD, block.embed.url, EOL_MD].join("")
117+
}
118+
119+
// Quote
120+
if (block.type == "quote") {
121+
return [EOL_MD, prependToLines(markdown, ">", false), EOL_MD].join("")
122+
}
123+
124+
// Bookmark
125+
if (block.type == "bookmark") {
126+
const bookmarkUrl = block.bookmark.url
127+
const bookmarkCaption = blockToString(block.bookmark.caption) || bookmarkUrl
128+
return `${EOL_MD}[${bookmarkCaption}](${bookmarkUrl})${EOL_MD}`
129+
}
130+
131+
// Divider
132+
if (block.type == "divider") {
133+
return `${EOL_MD}---${EOL_MD}`
134+
}
135+
136+
// Unsupported types.
137+
// TODO: Add support for callouts, internal video, and files
138+
return [EOL_MD, `<!-- This block type '${block.type}' is not supported yet. -->`, EOL_MD].join("")
139+
}

0 commit comments

Comments
 (0)