1
- import {
2
- Block as PublicBlock , BlockBase , Emoji , ExternalFile , ExternalFileWithCaption , File ,
3
- FileWithCaption , ImageBlock , RichText
4
- } from '@notionhq/client/build/src/api-types' ;
5
-
6
1
import { AssetWriter } from './AssetWriter' ;
2
+ import {
3
+ Block , Emoji , ExternalFile , ExternalFileWithCaption , File , FileWithCaption , ImageBlock
4
+ } from './Blocks' ;
7
5
import { DeferredRenderer } from './DeferredRenderer' ;
8
6
import { logger } from './logger' ;
9
7
import { RichTextRenderer } from './RichTextRenderer' ;
10
8
11
9
const debug = require ( "debug" ) ( "blocks" ) ;
12
10
13
- export interface CodeBlock extends BlockBase {
14
- type : "code" ;
15
- code : {
16
- text : RichText [ ] ;
17
- language : string ;
18
- } ;
19
- }
20
-
21
- export interface QuoteBlock extends BlockBase {
22
- type : "quote" ;
23
- code : {
24
- text : RichText [ ] ;
25
- language : string ;
26
- } ;
27
- }
28
-
29
- export interface CalloutBlock extends BlockBase {
30
- type : "callout" ;
31
- callout : {
32
- text : RichText [ ] ;
33
- icon : File | ExternalFile | Emoji ;
34
- } ;
35
- }
36
-
37
- export interface DividerBlock extends BlockBase {
38
- type : "divider" ;
39
- }
40
-
41
- export interface ChildDatabaseBlock extends BlockBase {
42
- type : "child_database" ;
43
- }
44
-
45
- // these are blocks that the notion API client code does not have proper typings for
46
- // for unknown reasons they removed types alltogether in v0.4 of the client
47
- // https://github.com/makenotion/notion-sdk-js/pulls?q=is%3Apr+is%3Aclosed#issuecomment-927781781
48
- export type Block =
49
- | PublicBlock
50
- | CodeBlock
51
- | QuoteBlock
52
- | CalloutBlock
53
- | DividerBlock
54
- | ChildDatabaseBlock ;
55
-
56
-
57
11
export interface BlockRenderResult {
58
12
lines : string ;
59
13
childIndent ?: number ;
@@ -62,9 +16,12 @@ export class BlockRenderer {
62
16
constructor (
63
17
private readonly richText : RichTextRenderer ,
64
18
private readonly deferredRenderer : DeferredRenderer
65
- ) { }
19
+ ) { }
66
20
67
- async renderBlock ( block : Block , assets : AssetWriter ) : Promise < BlockRenderResult > {
21
+ async renderBlock (
22
+ block : Block ,
23
+ assets : AssetWriter
24
+ ) : Promise < BlockRenderResult > {
68
25
switch ( block . type ) {
69
26
case "paragraph" :
70
27
return {
@@ -73,58 +30,70 @@ export class BlockRenderer {
73
30
// note: render headings +1 level, because h1 is reserved for page titles
74
31
case "heading_1" :
75
32
return {
76
- lines : "## " + ( await this . richText . renderMarkdown ( block . heading_1 . text ) )
33
+ lines :
34
+ "## " + ( await this . richText . renderMarkdown ( block . heading_1 . text ) ) ,
77
35
} ;
78
36
case "heading_2" :
79
37
return {
80
- lines : "### " + ( await this . richText . renderMarkdown ( block . heading_2 . text ) )
38
+ lines :
39
+ "### " + ( await this . richText . renderMarkdown ( block . heading_2 . text ) ) ,
81
40
} ;
82
41
case "heading_3" :
83
42
return {
84
- lines : "#### " + ( await this . richText . renderMarkdown ( block . heading_3 . text ) )
43
+ lines :
44
+ "#### " +
45
+ ( await this . richText . renderMarkdown ( block . heading_3 . text ) ) ,
85
46
} ;
86
47
case "bulleted_list_item" :
87
48
return {
88
- lines : "- " + await this . richText . renderMarkdown ( block . bulleted_list_item . text ) ,
89
- childIndent : 4
49
+ lines :
50
+ "- " +
51
+ ( await this . richText . renderMarkdown ( block . bulleted_list_item . text ) ) ,
52
+ childIndent : 4 ,
90
53
} ;
91
54
case "numbered_list_item" :
92
55
return {
93
- lines : "1. " + await this . richText . renderMarkdown ( block . numbered_list_item . text ) ,
94
- childIndent : 4
56
+ lines :
57
+ "1. " +
58
+ ( await this . richText . renderMarkdown ( block . numbered_list_item . text ) ) ,
59
+ childIndent : 4 ,
95
60
} ;
96
61
case "to_do" :
97
62
return {
98
- lines : "[ ] " + ( await this . richText . renderMarkdown ( block . to_do . text ) )
63
+ lines :
64
+ "[ ] " + ( await this . richText . renderMarkdown ( block . to_do . text ) ) ,
99
65
} ;
100
66
case "image" :
101
67
return {
102
- lines : await this . renderImage ( block , assets )
103
- }
104
- case "quote" :
105
- block as any ;
106
- return {
107
- lines : "> " + ( await this . richText . renderMarkdown ( ( block as any ) . quote . text ) )
68
+ lines : await this . renderImage ( block , assets ) ,
108
69
} ;
109
- case "code" :
70
+ case "quote" : {
71
+ // it's legal for a notion block to be cmoposed of multiple lines
72
+ // each of them must be prefixed with "> " to be part of the same quote block
73
+ const content = await this . richText . renderMarkdown ( block . quote . text ) ;
74
+
75
+ return { lines : this . formatAsQuoteBlock ( content ) } ;
76
+ }
77
+ case "code" : {
110
78
const code = await this . richText . renderPlainText ( block . code . text ) ;
111
79
if ( code . startsWith ( "<!--notion-markdown-cms:raw-->" ) ) {
112
80
return { lines : code } ;
113
81
}
114
82
115
83
return {
116
- lines :
117
- "```" +
118
- block . code . language +
119
- "\n" + code +
120
- "\n```"
84
+ lines : "```" + block . code . language + "\n" + code + "\n```" ,
121
85
} ;
122
- case "callout" :
86
+ }
87
+ case "callout" : {
88
+ // render emoji as bold, this enables css to target it as `blockquote > strong:first-child`
89
+ const content =
90
+ `**${ this . renderIcon ( block . callout . icon ) } ** ` +
91
+ ( await this . richText . renderMarkdown ( block . callout . text ) ) ;
92
+
123
93
return {
124
- lines :
125
- `> **${ this . renderIcon ( block . callout . icon ) } ** ` + // render emoji as bold, this enables css to target it as `blockquote > strong:first-child`
126
- ( await this . richText . renderMarkdown ( block . callout . text ) ) ,
94
+ lines : this . formatAsQuoteBlock ( content ) ,
127
95
} ;
96
+ }
128
97
case "divider" :
129
98
return { lines : "---" } ;
130
99
case "child_database" :
@@ -142,8 +111,11 @@ export class BlockRenderer {
142
111
case "unsupported" :
143
112
default :
144
113
return {
145
- lines : this . renderUnsupported ( `unsupported block type: ${ block . type } ` , block )
146
- }
114
+ lines : this . renderUnsupported (
115
+ `unsupported block type: ${ block . type } ` ,
116
+ block
117
+ ) ,
118
+ } ;
147
119
}
148
120
}
149
121
@@ -179,6 +151,13 @@ export class BlockRenderer {
179
151
}
180
152
}
181
153
154
+ private formatAsQuoteBlock ( content : string ) {
155
+ return content
156
+ . split ( "\n" )
157
+ . map ( ( x ) => "> " + x )
158
+ . join ( "\n" ) ;
159
+ }
160
+
182
161
private renderUnsupported ( msg : string , obj : any ) : string {
183
162
logger . warn ( msg ) ;
184
163
debug ( msg + "\n%O" , obj ) ;
0 commit comments