Skip to content

Commit 3eadb09

Browse files
feat: add support for explicitly controlling child indent levels
this allows rendering toggle block contents without additional indent levels
1 parent 673391e commit 3eadb09

File tree

3 files changed

+70
-64
lines changed

3 files changed

+70
-64
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ The following [Notion API block object types](https://developers.notion.com/refe
3232
| Bulleted List | ✅ Yes | |
3333
| Numbered List | ✅ Yes | |
3434
| To do | ✅ Yes | |
35-
| Toggle | ❌ Missing | |
35+
| Toggle | ✅ (Yes) | Toggle content is included, however the toggle header is not |
3636
| Code | ✅ Yes | |
3737
| Child Pages | ❌ not planned | avoid, they don't mix well with clear site navigation |
3838
| Child Databases | ✅ Yes | renders as table + including child pages, inline-only tables planned |

src/BlockRenderer.ts

Lines changed: 64 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
import {
2-
Block as PublicBlock,
3-
BlockBase,
4-
Emoji,
5-
ExternalFile,
6-
ExternalFileWithCaption,
7-
File,
8-
FileWithCaption,
9-
ImageBlock,
10-
RichText,
11-
} from "@notionhq/client/build/src/api-types";
12-
13-
import { AssetWriter } from "./AssetWriter";
14-
import { DeferredRenderer } from "./DeferredRenderer";
15-
import { logger } from "./logger";
16-
import { RichTextRenderer } from "./RichTextRenderer";
2+
Block as PublicBlock, BlockBase, Emoji, ExternalFile, ExternalFileWithCaption, File,
3+
FileWithCaption, ImageBlock, RichText
4+
} from '@notionhq/client/build/src/api-types';
5+
6+
import { AssetWriter } from './AssetWriter';
7+
import { DeferredRenderer } from './DeferredRenderer';
8+
import { logger } from './logger';
9+
import { RichTextRenderer } from './RichTextRenderer';
1710

1811
const debug = require("debug")("blocks");
1912

@@ -60,69 +53,82 @@ export type Block =
6053
| DividerBlock
6154
| ChildDatabaseBlock;
6255

56+
57+
export interface BlockRenderResult {
58+
lines: string;
59+
childIndent?: number;
60+
}
6361
export class BlockRenderer {
6462
constructor(
6563
private readonly richText: RichTextRenderer,
6664
private readonly deferredRenderer: DeferredRenderer
67-
) {}
65+
) { }
6866

69-
async renderBlock(block: Block, assets: AssetWriter): Promise<string> {
67+
async renderBlock(block: Block, assets: AssetWriter): Promise<BlockRenderResult> {
7068
switch (block.type) {
7169
case "paragraph":
72-
return await this.richText.renderMarkdown(block.paragraph.text);
70+
return {
71+
lines: await this.richText.renderMarkdown(block.paragraph.text)
72+
}
7373
// note: render headings +1 level, because h1 is reserved for page titles
7474
case "heading_1":
75-
return (
76-
"## " + (await this.richText.renderMarkdown(block.heading_1.text))
77-
);
75+
return {
76+
lines: "## " + (await this.richText.renderMarkdown(block.heading_1.text))
77+
};
7878
case "heading_2":
79-
return (
80-
"### " + (await this.richText.renderMarkdown(block.heading_2.text))
81-
);
79+
return {
80+
lines: "### " + (await this.richText.renderMarkdown(block.heading_2.text))
81+
};
8282
case "heading_3":
83-
return (
84-
"#### " + (await this.richText.renderMarkdown(block.heading_3.text))
85-
);
83+
return {
84+
lines: "#### " + (await this.richText.renderMarkdown(block.heading_3.text))
85+
};
8686
case "bulleted_list_item":
87-
return (
88-
"- " +
89-
(await this.richText.renderMarkdown(block.bulleted_list_item.text))
90-
);
87+
return {
88+
lines: "- " + await this.richText.renderMarkdown(block.bulleted_list_item.text),
89+
childIndent: 4
90+
};
9191
case "numbered_list_item":
92-
return (
93-
"1. " +
94-
(await this.richText.renderMarkdown(block.numbered_list_item.text))
95-
);
92+
return {
93+
lines: "1. " + await this.richText.renderMarkdown(block.numbered_list_item.text),
94+
childIndent: 4
95+
};
9696
case "to_do":
97-
return "[ ] " + (await this.richText.renderMarkdown(block.to_do.text));
97+
return {
98+
lines: "[ ] " + (await this.richText.renderMarkdown(block.to_do.text))
99+
};
98100
case "image":
99-
return await this.renderImage(block, assets);
101+
return {
102+
lines: await this.renderImage(block, assets)
103+
}
100104
case "quote":
101105
block as any;
102-
return (
103-
"> " + (await this.richText.renderMarkdown((block as any).quote.text))
104-
);
106+
return {
107+
lines: "> " + (await this.richText.renderMarkdown((block as any).quote.text))
108+
};
105109
case "code":
106-
return (
107-
"```" +
108-
block.code.language +
109-
"\n" +
110-
(await this.richText.renderMarkdown(block.code.text)) +
111-
"\n```"
112-
);
110+
return {
111+
lines:
112+
"```" +
113+
block.code.language +
114+
"\n" +
115+
(await this.richText.renderMarkdown(block.code.text)) +
116+
"\n```"
117+
};
113118
case "callout":
114-
return (
115-
"> " +
116-
this.renderIcon(block.callout.icon) +
117-
" " +
118-
(await this.richText.renderMarkdown(block.callout.text))
119-
);
119+
return {
120+
lines:
121+
"> " +
122+
this.renderIcon(block.callout.icon) +
123+
" " +
124+
(await this.richText.renderMarkdown(block.callout.text))
125+
};
120126
case "divider":
121-
return "---";
127+
return { lines: "---" };
122128
case "child_database":
123129
const msg = `<!-- included database ${block.id} -->\n`;
124130
const db = await this.deferredRenderer.renderChildDatabase(block.id);
125-
return msg + db.markdown;
131+
return { lines: msg + db.markdown };
126132
case "toggle":
127133
case "child_page":
128134
case "embed":
@@ -133,10 +139,9 @@ export class BlockRenderer {
133139
case "audio":
134140
case "unsupported":
135141
default:
136-
return this.renderUnsupported(
137-
`unsupported block type: ${block.type}`,
138-
block
139-
);
142+
return {
143+
lines: this.renderUnsupported(`unsupported block type: ${block.type}`, block)
144+
}
140145
}
141146
}
142147

src/RecursiveBodyRenderer.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class RecursiveBodyRenderer {
1010
constructor(
1111
readonly publicApi: Client,
1212
readonly blockRenderer: BlockRenderer
13-
) {}
13+
) { }
1414

1515
async renderBody(
1616
page: Page,
@@ -40,18 +40,19 @@ export class RecursiveBodyRenderer {
4040
assets: AssetWriter
4141
): Promise<string> {
4242
const parentBlock = await this.blockRenderer.renderBlock(block, assets);
43-
const parentLines = this.indent(parentBlock, indent);
43+
const parentLines = this.indent(parentBlock.lines, indent);
4444

4545
// due to the way the Notion API is built, we need to recurisvely retrieve child
4646
// blocks, see https://developers.notion.com/reference/retrieve-a-block
4747
// "If a block contains the key has_children: true, use the Retrieve block children endpoint to get the list of children"
4848
const children = block.has_children
4949
? (await this.publicApi.blocks.children.list({ block_id: block.id }))
50-
.results
50+
.results
5151
: [];
5252

53+
const childIndent = indent + " ".repeat(parentBlock.childIndent || 0);
5354
const renderChilds = children.map(
54-
async (x) => await this.renderBlock(x, indent + " ", assets)
55+
async (x) => await this.renderBlock(x, childIndent, assets)
5556
);
5657
const childLines = await Promise.all(renderChilds);
5758

0 commit comments

Comments
 (0)