notion-to-md 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +178 -0
- package/notion-to-md.js +98 -0
- package/package.json +28 -0
- package/utils/md.js +57 -0
- package/utils/notion.js +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<img src="https://imgur.com/WgXdz9r.png" />
|
|
2
|
+
|
|
3
|
+
# Notion to Markdown
|
|
4
|
+
|
|
5
|
+
Notion Markdown Exporter using **[notion-sdk-js](https://github.com/makenotion/notion-sdk-js)**
|
|
6
|
+
|
|
7
|
+
> **Note:** Before getting started, create [an integration and find the token](https://www.notion.so/my-integrations).
|
|
8
|
+
|
|
9
|
+
## Todo
|
|
10
|
+
|
|
11
|
+
- [x] heading
|
|
12
|
+
- [x] images
|
|
13
|
+
- [x] quotes
|
|
14
|
+
- [x] links
|
|
15
|
+
- [x] bullets
|
|
16
|
+
- [x] todo
|
|
17
|
+
- [x] inline code
|
|
18
|
+
- [x] code block
|
|
19
|
+
- [x] strikethrough, underline, bold, italic
|
|
20
|
+
- [x] nested blocks
|
|
21
|
+
- [ ] embeds
|
|
22
|
+
- [ ] tables
|
|
23
|
+
- [ ] videos
|
|
24
|
+
- [ ] convert returned markdown object to string
|
|
25
|
+
- [ ] add tests
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
```Bash
|
|
30
|
+
$ npm install notion-to-md
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### converting page to markdown
|
|
36
|
+
|
|
37
|
+
This is how the notion page looks for this example:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
const { Client } = require("@notionhq/client");
|
|
43
|
+
const notion2md = require("notion2md");
|
|
44
|
+
|
|
45
|
+
const notion = new Client({
|
|
46
|
+
auth: "your integration token",
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// passing notion client to the option
|
|
50
|
+
const n2m = new notion2md({ notionClient: notion });
|
|
51
|
+
|
|
52
|
+
(async () => {
|
|
53
|
+
const x = await n2m.pageToMarkdown("target_page_id");
|
|
54
|
+
console.log(x);
|
|
55
|
+
})();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Output:**
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
"parent": "# heading 1",
|
|
64
|
+
"children": []
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"parent": "- bullet 1",
|
|
68
|
+
"children": [
|
|
69
|
+
{
|
|
70
|
+
"parent": "- bullet 1.1",
|
|
71
|
+
"children": []
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"parent": "- bullet 1.2",
|
|
75
|
+
"children": []
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"parent": "- bullet 2",
|
|
81
|
+
"children": []
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"parent": "- [ ] check box 1",
|
|
85
|
+
"children": [
|
|
86
|
+
{
|
|
87
|
+
"parent": "- [x] check box 1.2",
|
|
88
|
+
"children": []
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"parent": "- [ ] check box 1.3",
|
|
92
|
+
"children": []
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"parent": "- [ ] checkbox 2",
|
|
98
|
+
"children": []
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### converting list of blocks to markdown
|
|
104
|
+
|
|
105
|
+
> This example blocks from a single page is used. One construct array of blocks from different notion pages and pass in to the `blocksToMarkdown()`
|
|
106
|
+
|
|
107
|
+
same notion page as before
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
const { Client } = require("@notionhq/client");
|
|
111
|
+
const notion2md = require("notion2md");
|
|
112
|
+
|
|
113
|
+
const notion = new Client({
|
|
114
|
+
auth: "your integration token",
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// passing notion client to the option
|
|
118
|
+
const n2m = new notion2md({ notionClient: notion });
|
|
119
|
+
|
|
120
|
+
(async () => {
|
|
121
|
+
// get all blocks in the page
|
|
122
|
+
const { results } = await notion.blocks.children.list({
|
|
123
|
+
block_id,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
//convert to markdown
|
|
127
|
+
const x = await n2m.blocksToMarkdown(results);
|
|
128
|
+
console.log(x);
|
|
129
|
+
})();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Output**: same as before
|
|
133
|
+
|
|
134
|
+
### Converting a single block to markdown
|
|
135
|
+
|
|
136
|
+
- just takes a single notion block and returns corresponding markdown string
|
|
137
|
+
- nesting is ignored
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
const result = n2m.blockToMarkdown(block);
|
|
141
|
+
console.log(result);
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**result**:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+

|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API
|
|
151
|
+
|
|
152
|
+
### `pageToMarkdown(page_id)`
|
|
153
|
+
|
|
154
|
+
- Takes page_id as input and converts all the blocks in the page to corresponding markdown
|
|
155
|
+
|
|
156
|
+
### `blockToMarkdown(block)`
|
|
157
|
+
|
|
158
|
+
- Takes one notion block and converts to markdown
|
|
159
|
+
- does not deal with nested notion blocks
|
|
160
|
+
- This method doesn't require the `notion-sdk-js`.
|
|
161
|
+
- Refer docs to know more about [notion blocks](https://developers.notion.com/reference/block)
|
|
162
|
+
|
|
163
|
+
### `blocksToMarkdown(blocks)`
|
|
164
|
+
|
|
165
|
+
> **Note**: requires <u>**notion-sdk-js**</u> unlike `blockToMarkdown`
|
|
166
|
+
|
|
167
|
+
- `blocks`: array of notion blocks
|
|
168
|
+
- deals with <u>**nested blocks**</u>
|
|
169
|
+
- uses `blockToMarkdown` internally.
|
|
170
|
+
|
|
171
|
+
## Contribution
|
|
172
|
+
|
|
173
|
+
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
|
|
174
|
+
Please make sure to update tests as appropriate.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
package/notion-to-md.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const md = require("./utils/md");
|
|
2
|
+
const { getBlockChildren } = require("./utils/notion");
|
|
3
|
+
|
|
4
|
+
class notion2md {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
if (!options || !options.notionClient)
|
|
7
|
+
throw new Error(
|
|
8
|
+
"notion2md takes notion client as argument, for more details check out https://github.com/souvikinator/notion2md"
|
|
9
|
+
);
|
|
10
|
+
this.notionClient = options.notionClient;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async pageToMarkdown(id) {
|
|
14
|
+
if (!id) throw new Error("pageToMarkdown takes page_id as argument");
|
|
15
|
+
const blocks = await getBlockChildren(this.notionClient, id);
|
|
16
|
+
const parsedData = await this.blocksToMarkdown(blocks);
|
|
17
|
+
return parsedData;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {object} blocks - list of notion blocks
|
|
22
|
+
* @returns array of md blocks with their children
|
|
23
|
+
*/
|
|
24
|
+
async blocksToMarkdown(blocks, mdBlocks = []) {
|
|
25
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
26
|
+
let block = blocks[i];
|
|
27
|
+
if (block.has_children) {
|
|
28
|
+
let child_blocks = await getBlockChildren(this.notionClient, block.id);
|
|
29
|
+
mdBlocks.push({ parent: this.blockToMarkdown(block), children: [] });
|
|
30
|
+
let l = mdBlocks.length;
|
|
31
|
+
await this.blocksToMarkdown(child_blocks, mdBlocks[l - 1].children);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
let tmp = this.blockToMarkdown(block);
|
|
35
|
+
// console.log(">>parsed data=", tmp, block);
|
|
36
|
+
mdBlocks.push({ parent: tmp, children: [] });
|
|
37
|
+
}
|
|
38
|
+
return mdBlocks;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {object} block - single notion block
|
|
43
|
+
* @returns markdown form of the passed block
|
|
44
|
+
*/
|
|
45
|
+
blockToMarkdown(block) {
|
|
46
|
+
let parsedData = "",
|
|
47
|
+
blockContent;
|
|
48
|
+
const { type } = block;
|
|
49
|
+
if (type === "image") {
|
|
50
|
+
blockContent = block[type];
|
|
51
|
+
const image_type = blockContent["type"];
|
|
52
|
+
|
|
53
|
+
return md.image("image", blockContent[image_type].url);
|
|
54
|
+
} else {
|
|
55
|
+
blockContent = block[type]["text"] || [];
|
|
56
|
+
blockContent.map((content) => {
|
|
57
|
+
const annotations = content.annotations;
|
|
58
|
+
let plain_text = content.plain_text;
|
|
59
|
+
|
|
60
|
+
plain_text = this.annotatePlainText(plain_text, annotations);
|
|
61
|
+
|
|
62
|
+
if (content["href"]) plain_text = md.link(plain_text, content["href"]);
|
|
63
|
+
|
|
64
|
+
parsedData += plain_text;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// TODO: need to take care of escape sequences
|
|
69
|
+
if (type === "code") parsedData = md.codeBlock(parsedData);
|
|
70
|
+
if (type === "heading_1") parsedData = md.heading1(parsedData);
|
|
71
|
+
if (type === "heading_2") parsedData = md.heading2(parsedData);
|
|
72
|
+
if (type === "heading_3") parsedData = md.heading3(parsedData);
|
|
73
|
+
if (type === "quote") parsedData = md.quote(parsedData);
|
|
74
|
+
|
|
75
|
+
if (type === "numbered_list_item" || type === "bulleted_list_item")
|
|
76
|
+
parsedData = md.bullet(parsedData);
|
|
77
|
+
|
|
78
|
+
if (type === "to_do") parsedData = md.todo(parsedData, block[type].checked);
|
|
79
|
+
|
|
80
|
+
return parsedData;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} text string to be annotated
|
|
85
|
+
* @param {object} annotations annotation object of a notion block
|
|
86
|
+
*/
|
|
87
|
+
annotatePlainText(text, annotations) {
|
|
88
|
+
if (annotations.code) text = md.inlineCode(text);
|
|
89
|
+
if (annotations.bold) text = md.bold(text);
|
|
90
|
+
if (annotations.italic) text = md.italic(text);
|
|
91
|
+
if (annotations.strikethrough) text = md.strikethrough(text);
|
|
92
|
+
if (annotations.underline) text = md.underline(text);
|
|
93
|
+
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = notion2md;
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "notion-to-md",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"notion",
|
|
10
|
+
"markdown",
|
|
11
|
+
"md",
|
|
12
|
+
"notion",
|
|
13
|
+
"notion-api",
|
|
14
|
+
"notion-to-md",
|
|
15
|
+
"notion2md"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/souvikinator/notion-to-md.git"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/souvikinator/notion-to-md/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/souvikinator/notion-to-md#readme",
|
|
27
|
+
"description": "convert notion pages, block and list of blocks to markdown (supports nesting)"
|
|
28
|
+
}
|
package/utils/md.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
exports.inlineCode = (text) => {
|
|
2
|
+
return `\`${text}\``;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
exports.bold = (text) => {
|
|
6
|
+
return `**${text}**`;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
exports.italic = (text) => {
|
|
10
|
+
return `_${text}_`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.strikethrough = (text) => {
|
|
14
|
+
return `~~${text}~~`;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
exports.underline = (text) => {
|
|
18
|
+
return `<u>${text}</u>`;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
exports.link = (text, href) => {
|
|
22
|
+
return `[${text}](${href})`;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
exports.codeBlock = (text) => {
|
|
26
|
+
return `\`\`\`${text}\`\`\``;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
exports.heading1 = (text) => {
|
|
30
|
+
return `# ${text}`;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
exports.heading2 = (text) => {
|
|
34
|
+
return `## ${text}`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
exports.heading3 = (text) => {
|
|
38
|
+
return `### ${text}`;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
exports.quote = (text) => {
|
|
42
|
+
return `| ${text}`;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
exports.bullet = (text) => {
|
|
46
|
+
return `- ${text}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
exports.todo = (text, checked) => {
|
|
50
|
+
return checked ? `- [x] ${text}` : `- [ ] ${text}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
exports.image = (alt, href) => {
|
|
54
|
+
return ``;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
//TODO: embed and fluid tags
|