├── .all-contributorsrc
├── .cz-config.js
├── .editorconfig
├── .env.example
├── .eslintrc.json
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .release-it.json
├── .vscode
└── extensions.json
├── CHANGELOG.md
├── README.md
├── jest.config.js
├── jest.preset.js
├── libs
├── blocks-html-parser
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── lib
│ │ │ ├── blocks-html-parser.spec.ts
│ │ │ ├── blocks-html-parser.ts
│ │ │ └── interfaces.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── blocks-markdown-parser
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── lib
│ │ │ ├── blocks-markdown-parser.spec.ts
│ │ │ ├── blocks-markdown-parser.ts
│ │ │ └── external-video.util.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── scully-plugin-notion
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── lib
│ │ │ ├── plugin-options.ts
│ │ │ ├── plugin.ts
│ │ │ ├── process-page-properties.ts
│ │ │ └── utils.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
└── v4-types
│ ├── .babelrc
│ ├── .eslintrc.json
│ ├── README.md
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── index.ts
│ └── lib
│ │ └── types.ts
│ ├── tsconfig.json
│ ├── tsconfig.lib.json
│ └── tsconfig.spec.json
├── migrations.json
├── nx.json
├── package.json
├── pnpm-lock.yaml
├── tools
├── generators
│ └── .gitkeep
└── tsconfig.tools.json
├── tsconfig.base.json
└── workspace.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "notion-stuff",
3 | "projectOwner": "nartc",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 75,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [
13 | {
14 | "login": "nartc",
15 | "name": "Chau Tran",
16 | "avatar_url": "https://avatars.githubusercontent.com/u/25516557?v=4",
17 | "profile": "https://nartc.me/",
18 | "contributions": [
19 | "code",
20 | "doc"
21 | ]
22 | },
23 | {
24 | "login": "barbados-clemens",
25 | "name": "Caleb Ukle",
26 | "avatar_url": "https://avatars.githubusercontent.com/u/23272162?v=4",
27 | "profile": "https://github.com/barbados-clemens",
28 | "contributions": [
29 | "code"
30 | ]
31 | },
32 | {
33 | "login": "MarcARoberge",
34 | "name": "MarcARoberge",
35 | "avatar_url": "https://avatars.githubusercontent.com/u/12281920?v=4",
36 | "profile": "https://github.com/MarcARoberge",
37 | "contributions": [
38 | "code"
39 | ]
40 | },
41 | {
42 | "login": "Panthaaaa",
43 | "name": "Panthaaaa",
44 | "avatar_url": "https://avatars.githubusercontent.com/u/1465893?v=4",
45 | "profile": "https://github.com/Panthaaaa",
46 | "contributions": [
47 | "code"
48 | ]
49 | }
50 | ],
51 | "contributorsPerLine": 7
52 | }
53 |
--------------------------------------------------------------------------------
/.cz-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | types: [
3 | { value: 'feat', name: 'feat: A new feature' },
4 | { value: 'fix', name: 'fix: A bug fix' },
5 | { value: 'docs', name: 'docs: Documentation only changes' },
6 | {
7 | value: 'cleanup',
8 | name: 'cleanup: A code change that neither fixes a bug nor adds a feature',
9 | },
10 | {
11 | value: 'chore',
12 | name: "chore: Other changes that don't modify src or test files",
13 | },
14 | ],
15 |
16 | scopes: [
17 | { name: 'html', description: 'HTML parser' },
18 | { name: 'markdown', description: 'Markdown parser' },
19 | { name: 'scully', description: 'anything Scully plugin specific' },
20 | { name: 'v4-types', description: 'anything related to Notion 0.4 Typings' },
21 | ],
22 | // override the messages, defaults are as follows
23 | messages: {
24 | type: "Select the type of change that you're committing:",
25 | scope: '\nDenote the SCOPE of this change (optional):',
26 | // used if allowCustomScopes is true
27 | customScope: 'Denote the SCOPE of this change:',
28 | subject:
29 | 'Write a SHORT, IMPERATIVE (lowercase) description of the change:\n',
30 | body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
31 | breaking: 'List any BREAKING CHANGES (optional):\n',
32 | footer:
33 | 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
34 | confirmCommit: 'Are you sure you want to proceed with the commit above?',
35 | },
36 |
37 | allowCustomScopes: true,
38 | allowBreakingChanges: ['feat', 'fix'],
39 | // skip any questions you want
40 | skipQuestions: ['ticketNumber'],
41 |
42 | // limit subject length
43 | subjectLimit: 100,
44 | };
45 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | GITHUB_TOKEN=your github token
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["**/*"],
4 | "plugins": ["@nrwl/nx"],
5 | "overrides": [
6 | {
7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8 | "rules": {
9 | "@nrwl/nx/enforce-module-boundaries": [
10 | "error",
11 | {
12 | "enforceBuildableLibDependency": true,
13 | "allow": [],
14 | "depConstraints": [
15 | {
16 | "sourceTag": "*",
17 | "onlyDependOnLibsWithTags": ["*"]
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | },
24 | {
25 | "files": ["*.ts", "*.tsx"],
26 | "extends": ["plugin:@nrwl/nx/typescript"],
27 | "rules": {}
28 | },
29 | {
30 | "files": ["*.js", "*.jsx"],
31 | "extends": ["plugin:@nrwl/nx/javascript"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | yarn-error.log
34 | testem.log
35 | /typings
36 |
37 | # System Files
38 | .DS_Store
39 | Thumbs.db
40 |
41 | .env
42 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/.release-it.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": {
3 | "@release-it/conventional-changelog": {
4 | "preset": {
5 | "name": "conventionalcommits",
6 | "types": [
7 | {
8 | "type": "feat",
9 | "section": "Features"
10 | },
11 | {
12 | "type": "fix",
13 | "section": "Bug Fixes"
14 | },
15 | {
16 | "type": "cleanup",
17 | "section": "Cleanup"
18 | },
19 | {
20 | "type": "docs",
21 | "section": "Documentations"
22 | }
23 | ]
24 | },
25 | "infile": "CHANGELOG.md"
26 | },
27 | "@release-it/bumper": {
28 | "out": [
29 | "libs/**/package*.json"
30 | ]
31 | }
32 | },
33 | "git": {
34 | "commitMessage": "chore: release ${version}"
35 | },
36 | "npm": {
37 | "publish": false
38 | },
39 | "github": {
40 | "release": true,
41 | "releaseName": "Release ${version}"
42 | },
43 | "hooks": {
44 | "after:bump": "pnpm run build"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "nrwl.angular-console",
4 | "esbenp.prettier-vscode",
5 | "dbaeumer.vscode-eslint",
6 | "firsttris.vscode-jest-runner"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## [6.0.0](https://github.com/nartc/notion-stuff/compare/5.1.0...6.0.0) (2022-04-01)
4 |
5 |
6 | ### ⚠ BREAKING CHANGES
7 |
8 | * **repo:** @notionhq/client has been updated to 1.0.0
9 |
10 | ### Features
11 |
12 | * **repo:** update notion client to 1.0.0 ([497a88f](https://github.com/nartc/notion-stuff/commit/497a88f0f5fd1b3e30727eecc56207c6056e2581))
13 |
14 |
15 | ### Bug Fixes
16 |
17 | * **html:** only set mdToHtmlOptions.highlight if not already passed in ([0349311](https://github.com/nartc/notion-stuff/commit/034931181eb1ef70a0cf6dd7a8918510f3bc2627)), closes [#11](https://github.com/nartc/notion-stuff/issues/11)
18 | * **markdown:** adjust markdown parser type for 1.0.0 ([3163924](https://github.com/nartc/notion-stuff/commit/31639247f4afbea44bf4868ccbd01f5ad7b7f26d))
19 | * **scully:** adjust scully plugin type for 1.0.0 ([cb462da](https://github.com/nartc/notion-stuff/commit/cb462dae75477c4009a8355cd50c0892ae5b8213))
20 | * **v4-types:** add PostResult ([f15c938](https://github.com/nartc/notion-stuff/commit/f15c9381d6b39ea5a8e7050b6e531e5c1a7945b6))
21 |
22 |
23 | ### Documentations
24 |
25 | * add @Panthaaaa as a contributor ([4edea81](https://github.com/nartc/notion-stuff/commit/4edea81349b9dfc5a92a3f5207f194153613cf2a))## [5.1.0](https://github.com/nartc/notion-stuff/compare/5.0.0...5.1.0) (2021-12-26)
26 |
27 |
28 | ### Features
29 |
30 | * **html:** render nested markdown in html blocks ([3e0f241](https://github.com/nartc/notion-stuff/commit/3e0f24115701097fb08f67360663ac168decc713))
31 |
32 | ## [5.0.0](https://github.com/nartc/notion-stuff/compare/4.0.0...5.0.0) (2021-12-25)
33 |
34 |
35 | ### ⚠ BREAKING CHANGES
36 |
37 | * **v4-types:** - 0.4.11 adds aliases that do not conform with other existing aliases in QueryResult type leading to
38 | breaking changes
39 |
40 | ### Features
41 |
42 | * **v4-types:** update 0.4.11 ([caf4b59](https://github.com/nartc/notion-stuff/commit/caf4b5978e006f4103c58dc6f91e431050f65de4))
43 |
44 | ## [4.0.0](https://github.com/nartc/notion-stuff/compare/3.2.0...4.0.0) (2021-11-11)
45 |
46 | ### ⚠ BREAKING CHANGES
47 |
48 | * **html:** - Marked 4 no longer provides default import
49 | * **v4-types:** - Update notionhq/client to 0.4.6
50 |
51 | ### Features
52 |
53 | * **html:** adapt to Marked 4 ([ad8f1fe](https://github.com/nartc/notion-stuff/commit/ad8f1fe09bca72cadf1dccf7fc344e0bef0b1c57))
54 | * **markdown:** update notionhq/client 0.4.6 ([66c8f64](https://github.com/nartc/notion-stuff/commit/66c8f645fa5c12205b2ca35fd1ff20bf42248ea1))
55 | * **scully:** update notionhq/client 0.4.6 ([cb7fc51](https://github.com/nartc/notion-stuff/commit/cb7fc51266e6aee96ec5812b055c7d89fbff2bf0))
56 | * **v4-types:** add Column and ColumnList ([009ebaa](https://github.com/nartc/notion-stuff/commit/009ebaab605ec49f9f22560542a48f57bac8cf84)), closes [#8](https://github.com/nartc/notion-stuff/issues/8)
57 |
58 | ## [3.2.0](https://github.com/nartc/notion-stuff/compare/3.1.3...3.2.0) (2021-10-28)
59 |
60 |
61 | ### Features
62 |
63 | * **markdown:** add support for empty paragraph with non-breaking space ([4531ae0](https://github.com/nartc/notion-stuff/commit/4531ae0877fe70761cb32d277deff406bf9fddfe))
64 |
65 |
66 | ### Cleanup
67 |
68 | * **markdown:** reformat ([543597f](https://github.com/nartc/notion-stuff/commit/543597f82e74953305deb36c1b2e40c9304a0978))
69 |
70 |
71 | ### Documentations
72 |
73 | * add @MarcARoberge as a contributor ([0a62355](https://github.com/nartc/notion-stuff/commit/0a62355d538d67c9176a341059ccf148beeafb4d))
74 |
75 | ### [3.1.3](https://github.com/nartc/notion-stuff/compare/3.1.2...3.1.3) (2021-10-21)
76 |
77 |
78 | ### Bug Fixes
79 |
80 | * **html:** handle undefined language param ([#2](https://github.com/nartc/notion-stuff/issues/2)) ([a77409a](https://github.com/nartc/notion-stuff/commit/a77409a2b4f1393d25d0d7982059d687269cd0d1))
81 |
82 |
83 | ### Documentations
84 |
85 | * add [@barbados-clemens](https://github.com/barbados-clemens) as a contributor ([4b81cba](https://github.com/nartc/notion-stuff/commit/4b81cba2ad92f68d5525a1f9f631f074852f5301))
86 |
87 | ### [3.1.2](https://github.com/nartc/notion-stuff/compare/3.1.1...3.1.2) (2021-10-21)
88 |
89 |
90 | ### Bug Fixes
91 |
92 | * **scully:** null check cover and icon before processing ([c034ef6](https://github.com/nartc/notion-stuff/commit/c034ef686992f5152d352a4e734d828a4ddba1ff))
93 |
94 | ### [3.1.1](https://github.com/nartc/notion-stuff/compare/3.1.0...3.1.1) (2021-10-21)
95 |
96 |
97 | ### Bug Fixes
98 |
99 | * **scully:** ensure to get the plugin options after plugin has been registered ([4e9571e](https://github.com/nartc/notion-stuff/commit/4e9571e5e88587e677231cd190fa0d74621fea76))
100 |
101 | ## [3.1.0](https://github.com/nartc/notion-stuff/compare/3.0.0...3.1.0) (2021-10-20)
102 |
103 |
104 | ### Features
105 |
106 | * bump notionClient dep ([cca14f4](https://github.com/nartc/notion-stuff/commit/cca14f4556128bc997b24dbad0e30515ccb4c4e0))
107 |
108 | ## [3.0.0](https://github.com/nartc/notion-stuff/compare/2.4.0...3.0.0) (2021-10-20)
109 |
110 |
111 | ### ⚠ BREAKING CHANGES
112 |
113 | * **scully:** - `NotionPluginOptions` -> `NotionDomPluginOptions`
114 |
115 | ### Features
116 |
117 | * **html:** expose MarkdownParser ([600ed9c](https://github.com/nartc/notion-stuff/commit/600ed9cf97e3b0cb8133f9dbe273ab9162797925))
118 | * **markdown:** support Divider now ([37116e7](https://github.com/nartc/notion-stuff/commit/37116e74cb9b23c881403d746b90788e8363aefd))
119 | * **scully:** support icon, cover, and more customization for Page Properties ([2b9e1ac](https://github.com/nartc/notion-stuff/commit/2b9e1ac27c0e3eb7d6da60ddaca372e9b18358a7))
120 |
121 |
122 | ### Bug Fixes
123 |
124 | * **v4-types:** widen file type for cover ([26868bc](https://github.com/nartc/notion-stuff/commit/26868bc2fd408b4815643283942ee592dc575008))
125 |
126 |
127 | ### Documentations
128 |
129 | * **scully:** add info about database column naming conversion/convention ([#1](https://github.com/nartc/notion-stuff/issues/1)) ([c52b178](https://github.com/nartc/notion-stuff/commit/c52b178f07275f89b6eb8e08406d20e8de2184fa))
130 |
131 | ## [2.4.0](https://github.com/nartc/notion-stuff/compare/2.3.2...2.4.0) (2021-10-18)
132 |
133 |
134 | ### Features
135 |
136 | * **markdown:** add youtube video processor to process youtube url ([6e4f99c](https://github.com/nartc/notion-stuff/commit/6e4f99cd66ce2a2716f2b3b7e2fd7645be160b9b))
137 |
138 |
139 | ### Bug Fixes
140 |
141 | * **markdown:** change paragraph parser to have EOL before and after ([2ab15c8](https://github.com/nartc/notion-stuff/commit/2ab15c86f64fb197bcbf7da817537274d2fe215e))
142 |
143 | ### [2.3.2](https://github.com/nartc/notion-stuff/compare/2.3.1...2.3.2) (2021-10-16)
144 |
145 |
146 | ### Bug Fixes
147 |
148 | * **html:** coerce plain text language to none ([ca9057a](https://github.com/nartc/notion-stuff/commit/ca9057a243501636682c2e58aaf153023b0f51fa))
149 |
150 | ### [2.3.1](https://github.com/nartc/notion-stuff/compare/2.3.0...2.3.1) (2021-10-16)
151 |
152 |
153 | ### Bug Fixes
154 |
155 | * **markdown:** parse callout with div ([50d0537](https://github.com/nartc/notion-stuff/commit/50d053745937d70f03361783fbcbfe2200136017))
156 |
157 | ## [2.3.0](https://github.com/nartc/notion-stuff/compare/2.2.2...2.3.0) (2021-10-14)
158 |
159 |
160 | ### Features
161 |
162 | * **scully:** instantiate a global notion instead ([686dec7](https://github.com/nartc/notion-stuff/commit/686dec7914a6d9b8b36a69fed6d2430ae20a551b))
163 |
164 | ### [2.2.2](https://github.com/nartc/notion-stuff/compare/2.2.1...2.2.2) (2021-10-14)
165 |
166 |
167 | ### Bug Fixes
168 |
169 | * **markdown:** add EOL_MD to before blockquote ([fcc90cf](https://github.com/nartc/notion-stuff/commit/fcc90cf751676450de0f7dedf1db4e3ba059d80a))
170 |
171 | ### [2.2.1](https://github.com/nartc/notion-stuff/compare/2.2.0...2.2.1) (2021-10-13)
172 |
173 |
174 | ### Documentations
175 |
176 | * update README for v4-types ([69d7898](https://github.com/nartc/notion-stuff/commit/69d7898114654c8776354e8ffa1cab51ddd14158))
177 |
178 | ## [2.2.0](https://github.com/nartc/notion-stuff/compare/2.1.1...2.2.0) (2021-10-13)
179 |
180 |
181 | ### Features
182 |
183 | * **html:** use v4-types and update to notion 0.4 ([82bf4d7](https://github.com/nartc/notion-stuff/commit/82bf4d70cc2eee10d6eeeeefd0743c119f7bfd85))
184 | * **markdown:** use v4-types and update to notion 0.4 ([1431612](https://github.com/nartc/notion-stuff/commit/143161244c1543a119d517baa7646dc0bd9c5bc4))
185 | * **scully:** use v4-types and update to notion 0.4 ([40c3e16](https://github.com/nartc/notion-stuff/commit/40c3e1603909b3130abf6a7e1a0f97d558af5080))
186 | * **v4-types:** add v4-types ([9806b41](https://github.com/nartc/notion-stuff/commit/9806b41b0168764d7ff2586749e5164322313683))
187 |
188 | ### [2.1.1](https://github.com/nartc/notion-stuff/compare/2.1.0...2.1.1) (2021-10-03)
189 |
190 |
191 | ### Bug Fixes
192 |
193 | * **markdown:** annotate text first before annotate link ([0159038](https://github.com/nartc/notion-stuff/commit/01590384b1ca420db9ca6c29e8594ebfadc400d5))
194 |
195 | ## [2.1.0](https://github.com/nartc/notion-stuff/compare/2.0.5...2.1.0) (2021-10-02)
196 |
197 |
198 | ### Features
199 |
200 | * **markdown:** add imageAsFigure to parser options to parse image as figure element ([ffce05a](https://github.com/nartc/notion-stuff/commit/ffce05a94adb3d130d70572cdcdb8bb57de0e12f))
201 |
202 |
203 | ### Bug Fixes
204 |
205 | * **html:** use Partial for MarkdownParserOptions instead ([0224a5e](https://github.com/nartc/notion-stuff/commit/0224a5ec3b55b3bdf9751c462a035cecf2238a04))
206 |
207 | ### [2.0.5](https://github.com/nartc/notion-stuff/compare/2.0.4...2.0.5) (2021-10-02)
208 |
209 |
210 | ### Bug Fixes
211 |
212 | * **html:** add codeTransformer to return correct template for correct highlighter ([ef4a8fd](https://github.com/nartc/notion-stuff/commit/ef4a8fd9540f6d86aef5af4db846565153913597))
213 |
214 | ### [2.0.4](https://github.com/nartc/notion-stuff/compare/2.0.3...2.0.4) (2021-10-02)
215 |
216 |
217 | ### Bug Fixes
218 |
219 | * **markdown:** adjust format of codeBlockParser ([7c881a5](https://github.com/nartc/notion-stuff/commit/7c881a5a24402ad9dc9e2da6833014d5ad3d6902))
220 |
221 | ### [2.0.3](https://github.com/nartc/notion-stuff/compare/2.0.2...2.0.3) (2021-10-02)
222 |
223 |
224 | ### Bug Fixes
225 |
226 | * **markdown:** double EOL for paragraph and adjust CodeBlock Parser ([2a4f122](https://github.com/nartc/notion-stuff/commit/2a4f122897d9bd515f9a44f11af0c06fa10b1bf8))
227 |
228 | ### [2.0.2](https://github.com/nartc/notion-stuff/compare/2.0.1...2.0.2) (2021-10-01)
229 |
230 |
231 | ### Bug Fixes
232 |
233 | * **markdown:** null check children before recusive call ([6d96a01](https://github.com/nartc/notion-stuff/commit/6d96a019ed4e51fbc8ab12bb6a70408cbaa718b1))
234 |
235 | ### [2.0.1](https://github.com/nartc/notion-stuff/compare/2.0.0...2.0.1) (2021-10-01)
236 |
237 |
238 | ### Bug Fixes
239 |
240 | * **scully:** move scully to peerDep instead ([7171397](https://github.com/nartc/notion-stuff/commit/7171397f995f975f8f096124f84c02cd99762826))
241 |
242 |
243 | ### Documentations
244 |
245 | * **html:** fix highlight.js dep ([45fa3db](https://github.com/nartc/notion-stuff/commit/45fa3db9874c872219d3d7d1fe9931a1dd7ca831))
246 |
247 | ## [2.0.0](https://github.com/nartc/notion-stuff/compare/1.0.4...2.0.0) (2021-09-30)
248 |
249 |
250 | ### ⚠ BREAKING CHANGES
251 |
252 | * **scully:** Scully version has been bumped to v2
253 | * **html:** HTML Parser now uses Markdown parser. Please check README for details
254 |
255 | ### Features
256 |
257 | * **html:** use Markdown parser ([9882e90](https://github.com/nartc/notion-stuff/commit/9882e9071d9034aa5ba33306689ad2daa36ed8a8))
258 | * **markdown:** add markdown parser ([6ed8398](https://github.com/nartc/notion-stuff/commit/6ed8398d7ef9c07c5676452b99f9427662b0ab35))
259 | * **scully:** bump scully version ([025c3a1](https://github.com/nartc/notion-stuff/commit/025c3a1f1425e2d7e9986bfb65e428404db4682c))
260 |
261 | ### [1.0.4](https://github.com/nartc/notion-stuff/compare/1.0.3...1.0.4) (2021-09-24)
262 |
263 |
264 | ### Bug Fixes
265 |
266 | * **scully:** adjust Formula and RollUp block type to match NotionAPi ([53e895c](https://github.com/nartc/notion-stuff/commit/53e895c1fdc00e78296376e4abc6a92ffa5616b5))
267 |
268 | ### [1.0.3](https://github.com/nartc/notion-stuff/compare/1.0.2...1.0.3) (2021-09-08)
269 |
270 |
271 | ### Bug Fixes
272 |
273 | * **html:** fix list items being undefined ([1b95be1](https://github.com/nartc/notion-stuff/commit/1b95be1e5f382cef6ee79f068eba436c1d4f2b66))
274 |
275 | ### [1.0.2](https://github.com/nartc/notion-stuff/compare/1.0.1...1.0.2) (2021-09-08)
276 |
277 |
278 | ### Bug Fixes
279 |
280 | * **html:** grab plain text as plain image caption instead of captionTransformer ([2e36175](https://github.com/nartc/notion-stuff/commit/2e36175f708200e33824dbd8b8005d4dd3a2b09a))
281 |
282 | ### [1.0.1](https://github.com/nartc/notion-stuff/compare/1.0.0...1.0.1) (2021-09-08)
283 |
284 |
285 | ### Bug Fixes
286 |
287 | * **html:** use image caption as alt if available. fix "this" issue ([5831dac](https://github.com/nartc/notion-stuff/commit/5831dac6b3f53e570e69e488d62341f3881be780))
288 |
289 | ## 1.0.0 (2021-09-08)
290 |
291 |
292 | ### Features
293 |
294 | * **html:** add HTML parser ([7fa11d9](https://github.com/nartc/notion-stuff/commit/7fa11d92404ba9ffcf115150a90cfe27a5be29c0))
295 | * **scully:** add Scully plugin ([7c81cef](https://github.com/nartc/notion-stuff/commit/7c81cef1041499bf7889f84e0c649c21802745e2))
296 |
297 |
298 | ### Documentations
299 |
300 | * add [@nartc](https://github.com/nartc) as a contributor ([e1ea76f](https://github.com/nartc/notion-stuff/commit/e1ea76f2b00847e7e4375d15b62511fb404a39f4))
301 | * add README ([c981380](https://github.com/nartc/notion-stuff/commit/c981380ad1e7df2bbe7b60645eb0c74fb48b312a))
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Notion Stuff
2 |
3 | - [Notion Blocks to Markdown parser](libs/blocks-markdown-parser/README.md)
4 | - [Notion Blocks to HTML parser](libs/blocks-html-parser/README.md)
5 | - [Scully plugin](libs/scully-plugin-notion/README.md)
6 | - [Notion 0.4 Types](libs/v4-types/README.md)
7 |
8 | ## Contributors ✨
9 |
10 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
11 |
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
30 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const { getJestProjects } = require('@nrwl/jest');
2 |
3 | module.exports = {
4 | projects: getJestProjects(),
5 | };
6 |
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
1 | const nxPreset = require('@nrwl/jest/preset');
2 |
3 | module.exports = { ...nxPreset };
4 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/README.md:
--------------------------------------------------------------------------------
1 | # @notion-stuff/blocks-html-parser
2 |
3 | This is a parser to parse Notion blocks to HTML parser. If you're here, you are probably aware of the [Notion API](https://developers.notion.com/), which is currently in beta. Hence, this parser is being updated (trying to) as the Notion API is updated.
4 |
5 | ## Installation
6 |
7 | ```bash
8 | npm install @notion-stuff/blocks-html-parser
9 | ```
10 |
11 | > `@notion-stuff/blocks-html-parser` depends on `@notionhq/client` and `@notion-stuff/blocks-markdown-parser`
12 |
13 | If you are using `NotionBlocksHtmlParser` with default configuration, you need to also install `marked` and `hljs`. More info [here](#usage)
14 |
15 | ```bash
16 | npm install marked highlight.js
17 | ```
18 |
19 | ## Usage
20 |
21 | 1. Get the parser instance with `NotionBlocksHtmlParser.getInstance()`
22 | 1. Optionally pass in a `NotionBlocksHtmlParserOptions` object to customize the parser. The passed-in option will be merged with the default options.
23 | 2. Call `instance.parse(blocks)` with `blocks` being `Block[]` that you'd get from a Notion page.
24 |
25 | `NotionBlocksHtmlParser` uses `NotionBlocksMarkdownParser` under the hood to parse Notion blocks to Markdown first.
26 |
27 | By default, `NotionBlocksHtmlParser` will use [marked](https://github.com/markedjs/marked) to compile Markdown to HTML. The default `marked` configuration is as follow:
28 |
29 | ```ts
30 | export const defaultMarkedOptions = {
31 | pedantic: false,
32 | gfm: true,
33 | breaks: false,
34 | sanitize: false,
35 | smartLists: true,
36 | smartypants: false,
37 | xhtml: false,
38 | };
39 | ```
40 | > `defaultMarkedOptions` is exported so you can merge it with your custom configuration.
41 |
42 | For highlighting, `hljs` will be used by default.
43 |
44 | You can customize:
45 |
46 | - Markdown to HTML compilation with `NotionBlocksHtmlParserOptions#mdToHtmlOptions`
47 | - If you pass in a function, you are taking over the compilation
48 | - Otherwise, you can pass in an `MarkedOptions` object to customize `marked`
49 | > Make sure to `npm install marked`
50 | - Markdown Highlighting with `NotionBlocksHtmlParserOptions#mdHighlightingOptions`
51 | - If you pass in a function, you are taking over the highlighting
52 | - If you pass in `hljs`, the parser will use `highlight.js` to highlight the code blocks.
53 | > Make sure to `npm install highlight.js`
54 | - If you pass in `prism`, the parse will use `prismjs` to highlight the code blocks
55 | > Make sure to `npm install prismjs`
56 |
57 | ## Configuration
58 |
59 | ```ts
60 | export interface NotionBlocksHtmlParserOptions {
61 | mdParserOptions?: NotionBlocksMarkdownParserOptions;
62 | mdToHtmlOptions?:
63 | | ((
64 | markdown: string,
65 | htmlParserOptions: NotionBlocksHtmlParserOptions
66 | ) => string)
67 | | MarkedOptions;
68 | /**
69 | * Only applicable with @link{mdToHtmlOptions} as @link{MarkedOptions}
70 | * @default 'hljs'
71 | */
72 | mdHighlightingOptions?: 'hljs' | 'prismjs' | MarkedOptions['highlight'];
73 | }
74 | ```
75 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'blocks-html-parser',
3 | preset: '../../jest.preset.js',
4 | globals: {
5 | 'ts-jest': {
6 | tsconfig: '/tsconfig.spec.json',
7 | },
8 | },
9 | testEnvironment: 'node',
10 | transform: {
11 | '^.+\\.[tj]sx?$': 'ts-jest',
12 | },
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
14 | coverageDirectory: '../../coverage/libs/blocks-html-parser',
15 | };
16 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@notion-stuff/blocks-html-parser",
3 | "version": "6.0.0",
4 | "dependencies": {
5 | "@notionhq/client": "~1.0.0"
6 | },
7 | "publishConfig": {
8 | "access": "public"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/blocks-html-parser';
2 | export * from './lib/interfaces';
3 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/src/lib/blocks-html-parser.spec.ts:
--------------------------------------------------------------------------------
1 | import { NotionBlocksHtmlParser } from './blocks-html-parser';
2 | import type { NotionBlocksHtmlParserOptions } from './interfaces';
3 |
4 | describe(NotionBlocksHtmlParser.name, () => {
5 | let parser: NotionBlocksHtmlParser;
6 |
7 | function setup(options: NotionBlocksHtmlParserOptions = {}) {
8 | parser = NotionBlocksHtmlParser.getInstance(options);
9 | }
10 |
11 | it('should create instance', () => {
12 | setup();
13 | expect(parser).toBeTruthy();
14 | });
15 |
16 | it('should create ONLY one instance', () => {
17 | setup();
18 | const instance = NotionBlocksHtmlParser.getInstance();
19 |
20 | expect(instance).toBe(parser);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/src/lib/blocks-html-parser.ts:
--------------------------------------------------------------------------------
1 | import { NotionBlocksMarkdownParser } from '@notion-stuff/blocks-markdown-parser';
2 | import type { Blocks } from '@notion-stuff/v4-types';
3 | import type { marked as Marked } from 'marked';
4 | import type { NotionBlocksHtmlParserOptions } from './interfaces';
5 |
6 | export const defaultMarkedOptions = {
7 | pedantic: false,
8 | gfm: true,
9 | breaks: false,
10 | sanitize: false,
11 | smartLists: true,
12 | smartypants: false,
13 | xhtml: false,
14 | };
15 |
16 | let marked, hljs, prism;
17 |
18 | export class NotionBlocksHtmlParser {
19 | private static instance: NotionBlocksHtmlParser;
20 | private static markdownParser: NotionBlocksMarkdownParser;
21 | private readonly parserOptions: Required;
22 |
23 | private constructor(parserOptions: NotionBlocksHtmlParserOptions = {}) {
24 | this.parserOptions = NotionBlocksHtmlParser.assignDefaultOptions(
25 | parserOptions
26 | ) as Required;
27 | }
28 |
29 | static getInstance(parserOptions: NotionBlocksHtmlParserOptions = {}) {
30 | if (!this.instance) {
31 | this.instance = new this(parserOptions);
32 | if (typeof this.instance.parserOptions.mdToHtmlOptions === 'object') {
33 | this.setupMarked(this.instance.parserOptions);
34 | }
35 | }
36 |
37 | if (!this.markdownParser) {
38 | this.markdownParser = NotionBlocksMarkdownParser.getInstance(
39 | this.instance.parserOptions.mdParserOptions
40 | );
41 | }
42 |
43 | return this.instance;
44 | }
45 |
46 | static getMarkdownParser(): NotionBlocksMarkdownParser {
47 | if (!this.markdownParser) {
48 | throw new Error(
49 | 'MarkdownParser is not available until HtmlParser instance has been created'
50 | );
51 | }
52 |
53 | return this.markdownParser;
54 | }
55 |
56 | parse(blocks: Blocks) {
57 | const markdown = NotionBlocksHtmlParser.markdownParser.parse(blocks);
58 | const { mdToHtmlOptions } = this.parserOptions;
59 |
60 | if (typeof mdToHtmlOptions === 'object') {
61 | return marked(markdown, mdToHtmlOptions);
62 | }
63 |
64 | return mdToHtmlOptions(markdown, this.parserOptions);
65 | }
66 |
67 | private static assignDefaultOptions(
68 | parserOptions: NotionBlocksHtmlParserOptions
69 | ): NotionBlocksHtmlParserOptions {
70 | return {
71 | mdParserOptions: {},
72 | mdHighlightingOptions: 'hljs',
73 | mdToHtmlOptions: defaultMarkedOptions,
74 | ...parserOptions,
75 | };
76 | }
77 |
78 | private static setupMarked({
79 | mdToHtmlOptions,
80 | mdHighlightingOptions,
81 | }: Required) {
82 | try {
83 | // eslint-disable-next-line @typescript-eslint/no-var-requires
84 | const _marked = require('marked');
85 | marked = _marked.marked || _marked;
86 | } catch (e) {
87 | const message = `Error importing package: marked. Please install "marked" package.`;
88 | console.error(message);
89 | throw new Error(message);
90 | }
91 |
92 | const renderer = new marked.Renderer();
93 |
94 | const codeTransformer = (code: unknown, language: string) => {
95 | const langClass =
96 | 'language-' +
97 | (!language || language.includes('plain') ? 'none' : language);
98 | if (mdHighlightingOptions === 'hljs') {
99 | return `${
100 | (code as Record).value
101 | }
`;
102 | }
103 |
104 | // prism
105 | if (!language) {
106 | return `${code}
`;
107 | }
108 | // e.g. "language-js"
109 | return `${code}
`;
110 | };
111 |
112 | renderer.code = function (this: typeof renderer, code, language) {
113 | code = this.options.highlight(code, language);
114 | return codeTransformer(code, code.language || language);
115 | };
116 |
117 | renderer.html = function (this: typeof renderer, mixedHtml: string) {
118 | return mixedHtml.replace(/[^<>]+?(?=<\/[figcaption|span])/g, (match) => {
119 | const tokens = (marked as typeof Marked).lexer(match);
120 | return (marked as typeof Marked).parser(tokens);
121 | });
122 | };
123 |
124 | (mdToHtmlOptions as Marked.MarkedOptions).renderer = renderer;
125 |
126 | if (!(mdToHtmlOptions as Marked.MarkedOptions).highlight) {
127 | if (mdHighlightingOptions === 'hljs') {
128 | if (!hljs) {
129 | try {
130 | hljs = require('highlight.js');
131 | } catch (e) {
132 | const message = `Error importing package: highlight.js. Please install "highlight.js" package.`;
133 | console.error(message);
134 | throw new Error(message);
135 | }
136 | }
137 | (mdToHtmlOptions as Marked.MarkedOptions).highlight = (code, lang) => {
138 | const language = hljs.getLanguage(lang) ? lang : 'plaintext';
139 | return hljs.highlight(code, { language });
140 | };
141 | } else if (mdHighlightingOptions === 'prismjs') {
142 | if (!prism) {
143 | try {
144 | prism = require('prismjs');
145 | require('prismjs/components/prism-bash');
146 | require('prismjs/components/prism-css');
147 | require('prismjs/components/prism-javascript');
148 | require('prismjs/components/prism-json');
149 | require('prismjs/components/prism-markup');
150 | require('prismjs/components/prism-markdown');
151 | require('prismjs/components/prism-typescript');
152 | require('prismjs/components/prism-jsx');
153 | require('prismjs/components/prism-tsx');
154 | require('prismjs/components/prism-docker');
155 | } catch (e) {
156 | const message = `Error importing package: prismjs. Please install "prismjs" package.`;
157 | console.error(message);
158 | throw new Error(message);
159 | }
160 | }
161 |
162 | (mdToHtmlOptions as Marked.MarkedOptions).highlight = (code, lang) => {
163 | if (!prism.languages[lang]) {
164 | return code;
165 | }
166 |
167 | return prism.highlight(code, prism.languages[lang]);
168 | };
169 | } else {
170 | (mdToHtmlOptions as Marked.MarkedOptions).highlight =
171 | mdHighlightingOptions;
172 | }
173 | }
174 |
175 |
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/src/lib/interfaces.ts:
--------------------------------------------------------------------------------
1 | import type { NotionBlocksMarkdownParserOptions } from '@notion-stuff/blocks-markdown-parser';
2 | import type { marked } from 'marked';
3 |
4 | export interface NotionBlocksHtmlParserOptions {
5 | mdParserOptions?: Partial;
6 | mdToHtmlOptions?:
7 | | ((
8 | markdown: string,
9 | htmlParserOptions: NotionBlocksHtmlParserOptions
10 | ) => string)
11 | | marked.MarkedOptions;
12 | /**
13 | * Only applicable with @link{mdToHtmlOptions} as @link{MarkedOptions}
14 | * @default 'hljs'
15 | */
16 | mdHighlightingOptions?:
17 | | 'hljs'
18 | | 'prismjs'
19 | | marked.MarkedOptions['highlight'];
20 | }
21 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.lib.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "../../dist/out-tsc",
6 | "declaration": true,
7 | "types": ["node"]
8 | },
9 | "exclude": ["**/*.spec.ts"],
10 | "include": ["**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/libs/blocks-html-parser/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "**/*.spec.ts",
10 | "**/*.spec.tsx",
11 | "**/*.spec.js",
12 | "**/*.spec.jsx",
13 | "**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/README.md:
--------------------------------------------------------------------------------
1 | # @notion-stuff/blocks-html-parser
2 |
3 | This is a parser to parse Notion blocks to Markdown. If you're here, you are probably aware of the [Notion API](https://developers.notion.com/), which is currently in beta. Hence, this parser is being updated (trying to) as the Notion API is updated.
4 |
5 | ## Installation
6 |
7 | ```bash
8 | npm install @notion-stuff/blocks-markdown-parser
9 | ```
10 |
11 | > `@notion-stuff/blocks-markdown-parser` depends on `@notionhq/client`
12 |
13 | ## Usage
14 |
15 | 1. Get the parser instance with `NotionBlocksMarkdownParser.getInstance()`
16 | 1. Optionally pass in a `NotionBlocksMarkdownParserOptions` object to customize the parser. The passed-in option will be merged with the default options.
17 | 2. Call `instance.parse(blocks)` with `blocks` being `Block[]` that you'd get from a Notion page.
18 |
19 | ## Configuration
20 |
21 | ```ts
22 | export interface NotionBlocksMarkdownParserOptions {}
23 | ```
24 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'blocks-markdown-parser',
3 | preset: '../../jest.preset.js',
4 | globals: {
5 | 'ts-jest': {
6 | tsconfig: '/tsconfig.spec.json',
7 | },
8 | },
9 | testEnvironment: 'node',
10 | transform: {
11 | '^.+\\.[tj]sx?$': 'ts-jest',
12 | },
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
14 | coverageDirectory: '../../coverage/libs/blocks-markdown-parser',
15 | };
16 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@notion-stuff/blocks-markdown-parser",
3 | "version": "6.0.0",
4 | "dependencies": {
5 | "@notionhq/client": "~1.0.0"
6 | },
7 | "publishConfig": {
8 | "access": "public"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/blocks-markdown-parser';
2 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/src/lib/blocks-markdown-parser.spec.ts:
--------------------------------------------------------------------------------
1 | import { NotionBlocksMarkdownParser } from './blocks-markdown-parser';
2 |
3 | describe(NotionBlocksMarkdownParser.name, () => {
4 | it('should work', () => {
5 | expect(NotionBlocksMarkdownParser.getInstance()).toBeTruthy();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/src/lib/blocks-markdown-parser.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Annotations,
3 | AudioBlock,
4 | Blocks,
5 | BulletedListItemBlock,
6 | CalloutBlock,
7 | CalloutIconEmoji,
8 | CalloutIconExternal,
9 | CalloutIconFile,
10 | CodeBlock,
11 | EmbedBlock,
12 | ExternalFileWithCaption,
13 | FileBlock,
14 | FileWithCaption,
15 | HeadingBlock,
16 | ImageBlock,
17 | NumberedListItemBlock,
18 | ParagraphBlock,
19 | PDFBlock,
20 | QuoteBlock,
21 | RichText,
22 | RichTextEquation,
23 | RichTextMention,
24 | RichTextText,
25 | ToDoBlock,
26 | ToggleBlock,
27 | VideoBlock,
28 | } from '@notion-stuff/v4-types';
29 | import { processExternalVideoUrl } from './external-video.util';
30 |
31 | const EOL_MD = '\n';
32 |
33 | export interface NotionBlocksMarkdownParserOptions {
34 | /**
35 | * Use and
36 | */
37 | imageAsFigure: boolean;
38 | /**
39 | * When a paragraphBlock#text is empty, render a
40 | */
41 | emptyParagraphToNonBreakingSpace: boolean;
42 | }
43 |
44 | export class NotionBlocksMarkdownParser {
45 | private static instance: NotionBlocksMarkdownParser;
46 | private readonly parserOptions: Required;
47 |
48 | private constructor(options?: Partial) {
49 | this.parserOptions = {
50 | imageAsFigure: true,
51 | emptyParagraphToNonBreakingSpace: false,
52 | ...(options || {}),
53 | };
54 | }
55 |
56 | static getInstance(options?: Partial) {
57 | if (!this.instance) {
58 | this.instance = new this(options);
59 | }
60 |
61 | return this.instance;
62 | }
63 |
64 | parse(blocks: Blocks, depth = 0): string {
65 | return blocks
66 | .reduce((markdown, childBlock) => {
67 | let childBlockString = '';
68 | if (childBlock.has_children && childBlock[childBlock.type].children) {
69 | childBlockString = ' '
70 | .repeat(depth)
71 | .concat(
72 | childBlockString,
73 | this.parse(childBlock[childBlock.type].children, depth + 2)
74 | );
75 | }
76 |
77 | if (childBlock.type === 'unsupported') {
78 | markdown += 'NotionAPI Unsupported'.concat(
79 | EOL_MD.repeat(2),
80 | childBlockString
81 | );
82 | }
83 |
84 | if (childBlock.type === 'paragraph') {
85 | markdown += this.parseParagraph(childBlock).concat(childBlockString);
86 | }
87 |
88 | if (childBlock.type === 'code') {
89 | markdown += this.parseCodeBlock(childBlock).concat(childBlockString);
90 | }
91 |
92 | if (childBlock.type === 'quote') {
93 | markdown += this.parseQuoteBlock(childBlock).concat(childBlockString);
94 | }
95 |
96 | if (childBlock.type === 'callout') {
97 | markdown +=
98 | this.parseCalloutBlock(childBlock).concat(childBlockString);
99 | }
100 |
101 | if (childBlock.type.startsWith('heading_')) {
102 | const headingLevel = Number(childBlock.type.split('_')[1]);
103 | markdown += this.parseHeading(
104 | childBlock as HeadingBlock,
105 | headingLevel
106 | ).concat(childBlockString);
107 | }
108 |
109 | if (childBlock.type === 'bulleted_list_item') {
110 | markdown +=
111 | this.parseBulletedListItems(childBlock).concat(childBlockString);
112 | }
113 |
114 | if (childBlock.type === 'numbered_list_item') {
115 | markdown +=
116 | this.parseNumberedListItems(childBlock).concat(childBlockString);
117 | }
118 |
119 | if (childBlock.type === 'to_do') {
120 | markdown += this.parseTodoBlock(childBlock).concat(childBlockString);
121 | }
122 |
123 | if (childBlock.type === 'toggle') {
124 | markdown += this.parseToggleBlock(childBlock).replace(
125 | '{{childBlock}}',
126 | childBlockString
127 | );
128 | }
129 |
130 | if (childBlock.type === 'image') {
131 | markdown += this.parseImageBlock(childBlock).concat(childBlockString);
132 | }
133 |
134 | if (childBlock.type === 'embed') {
135 | markdown += this.parseEmbedBlock(childBlock).concat(childBlockString);
136 | }
137 |
138 | if (childBlock.type === 'audio') {
139 | markdown += this.parseAudioBlock(childBlock).concat(childBlockString);
140 | }
141 |
142 | if (childBlock.type === 'video') {
143 | markdown += this.parseVideoBlock(childBlock).concat(childBlockString);
144 | }
145 |
146 | if (childBlock.type === 'file') {
147 | markdown += this.parseFileBlock(childBlock).concat(childBlockString);
148 | }
149 |
150 | if (childBlock.type === 'pdf') {
151 | markdown += this.parsePdfBlock(childBlock).concat(childBlockString);
152 | }
153 |
154 | if (childBlock.type === 'divider') {
155 | markdown += EOL_MD.concat('---', EOL_MD, childBlockString);
156 | }
157 |
158 | return markdown;
159 | }, '')
160 | .concat(EOL_MD);
161 | }
162 |
163 | parseParagraph(paragraphBlock: ParagraphBlock): string {
164 | let text: string;
165 |
166 | if (
167 | this.parserOptions.emptyParagraphToNonBreakingSpace &&
168 | paragraphBlock.paragraph.rich_text.length === 0
169 | ) {
170 | text = ' ';
171 | } else {
172 | text = this.parseRichTexts(paragraphBlock.paragraph.rich_text);
173 | }
174 |
175 | return EOL_MD.concat(text, EOL_MD);
176 | }
177 |
178 | parseCodeBlock(codeBlock: CodeBlock): string {
179 | return `\`\`\`${codeBlock.code.language.toLowerCase() || ''}
180 | ${(codeBlock.code.rich_text[0] as RichTextText).text.content}
181 | \`\`\``.concat(EOL_MD);
182 | }
183 |
184 | parseQuoteBlock(quoteBlock: QuoteBlock): string {
185 | return EOL_MD.concat(
186 | `> ${this.parseRichTexts(quoteBlock.quote.rich_text)}`,
187 | EOL_MD
188 | );
189 | }
190 |
191 | parseCalloutBlock(calloutBlock: CalloutBlock) {
192 | const callout = `
193 | {{icon}}
194 |
195 | ${this.parseRichTexts(calloutBlock.callout.rich_text)}
196 |
197 |
`;
198 |
199 | function getCalloutIcon(
200 | icon: CalloutIconEmoji | CalloutIconExternal | CalloutIconFile
201 | ) {
202 | switch (icon.type) {
203 | case 'emoji':
204 | return `${icon.emoji}`;
205 | case 'external':
206 | return `
`;
207 | case 'file':
208 | // TODO: add support for Callout File
209 | return `notion-callout-file`;
210 | }
211 | }
212 |
213 | return EOL_MD.concat(
214 | callout.replace('{{icon}}', getCalloutIcon(calloutBlock.callout.icon)),
215 | EOL_MD
216 | );
217 | }
218 |
219 | parseHeading(headingBlock: HeadingBlock, headingLevel: number): string {
220 | return EOL_MD.concat(
221 | '#'.repeat(headingLevel),
222 | ' ',
223 | this.parseRichTexts(headingBlock[headingBlock.type].rich_text),
224 | EOL_MD
225 | );
226 | }
227 |
228 | parseBulletedListItems(bulletedListItemBlock: BulletedListItemBlock): string {
229 | return '* '.concat(
230 | this.parseRichTexts(bulletedListItemBlock.bulleted_list_item.rich_text),
231 | EOL_MD
232 | );
233 | }
234 |
235 | parseNumberedListItems(numberedListItemBlock: NumberedListItemBlock): string {
236 | return '1. '.concat(
237 | this.parseRichTexts(numberedListItemBlock.numbered_list_item.rich_text),
238 | EOL_MD
239 | );
240 | }
241 |
242 | parseTodoBlock(todoBlock: ToDoBlock): string {
243 | return `- [${todoBlock.to_do.checked ? 'x' : ' '}] `.concat(
244 | this.parseRichTexts(todoBlock.to_do.rich_text),
245 | EOL_MD
246 | );
247 | }
248 |
249 | parseToggleBlock(toggleBlock: ToggleBlock): string {
250 | return `${this.parseRichTexts(
251 | toggleBlock.toggle.rich_text
252 | )}
{{childBlock}} `;
253 | }
254 |
255 | parseImageBlock(imageBlock: ImageBlock): string {
256 | const { url, caption } = this.parseFile(imageBlock.image);
257 | if (this.parserOptions.imageAsFigure) {
258 | return `
259 |
260 |
261 | ${caption}
262 |
263 | `.concat(EOL_MD);
264 | }
265 | return ``.concat(EOL_MD);
266 | }
267 |
268 | parseAudioBlock(audioBlock: AudioBlock): string {
269 | const { url, caption } = this.parseFile(audioBlock.audio);
270 | return ``;
271 | }
272 |
273 | parseVideoBlock(videoBlock: VideoBlock): string {
274 | const { url, caption } = this.parseFile(videoBlock.video);
275 |
276 | const [processed, iframeOrUrl] = processExternalVideoUrl(url);
277 |
278 | if (processed) {
279 | return EOL_MD.concat(iframeOrUrl, EOL_MD);
280 | }
281 |
282 | return `To be supported: ${url} with ${caption}`.concat(EOL_MD);
283 | }
284 |
285 | parseFileBlock(fileBlock: FileBlock): string {
286 | const { url, caption } = this.parseFile(fileBlock.file);
287 | return `To be supported: ${url} with ${caption}`.concat(EOL_MD);
288 | }
289 |
290 | parsePdfBlock(pdfBlock: PDFBlock): string {
291 | const { url, caption } = this.parseFile(pdfBlock.pdf);
292 | return `
293 |
294 |
295 | ${caption}
296 |
297 | `.concat(EOL_MD);
298 | }
299 |
300 | parseEmbedBlock(embedBlock: EmbedBlock): string {
301 | const embedded = ``;
302 |
303 | if (embedBlock.embed.caption) {
304 | return `
305 |
306 | ${embedded}
307 | ${this.parseRichTexts(embedBlock.embed.caption)}
308 | `.concat(EOL_MD);
309 | }
310 |
311 | return embedded.concat(EOL_MD);
312 | }
313 |
314 | parseRichTexts(richTexts: RichText[]): string {
315 | return richTexts.reduce((parsedContent, richText) => {
316 | switch (richText.type) {
317 | case 'text':
318 | parsedContent += this.parseText(richText);
319 | break;
320 | case 'mention':
321 | parsedContent += this.parseMention(richText);
322 | break;
323 | case 'equation':
324 | parsedContent += this.parseEquation(richText);
325 | break;
326 | }
327 |
328 | return parsedContent;
329 | }, '');
330 | }
331 |
332 | parseText(richText: RichTextText): string {
333 | const content = this.annotate(richText.annotations, richText.text.content);
334 |
335 | return richText.text.link
336 | ? this.annotateLink(richText.text, content)
337 | : content;
338 | }
339 |
340 | // TODO: support mention when we know what it actually means
341 |
342 | parseMention(mention: RichTextMention): string {
343 | switch (mention.mention.type) {
344 | case 'user':
345 | break;
346 | case 'page':
347 | break;
348 | case 'database':
349 | break;
350 | case 'date':
351 | break;
352 | }
353 | return this.annotate(mention.annotations, mention.plain_text);
354 | }
355 |
356 | parseEquation(equation: RichTextEquation): string {
357 | return this.annotate(
358 | equation.annotations,
359 | `$${equation.equation.expression}$`
360 | );
361 | }
362 |
363 | parseFile(file: ExternalFileWithCaption | FileWithCaption): {
364 | caption: string;
365 | url: string;
366 | } {
367 | const fileContent = {
368 | caption: '',
369 | url: '',
370 | };
371 |
372 | switch (file.type) {
373 | case 'external':
374 | fileContent.url = file.external.url;
375 | break;
376 | case 'file':
377 | fileContent.url = file.file.url;
378 | break;
379 | }
380 |
381 | fileContent.caption = file.caption
382 | ? this.parseRichTexts(file.caption)
383 | : fileContent.url;
384 |
385 | return fileContent;
386 | }
387 |
388 | private annotate(annotations: Annotations, originalContent: string): string {
389 | return Object.entries(annotations).reduce(
390 | (
391 | annotatedContent,
392 | [modifier, isOnOrColor]: [
393 | keyof Annotations,
394 | boolean | Annotations['color']
395 | ]
396 | ) =>
397 | isOnOrColor
398 | ? this.annotateModifier(
399 | modifier,
400 | annotatedContent,
401 | isOnOrColor as Annotations['color']
402 | )
403 | : annotatedContent,
404 | originalContent
405 | );
406 | }
407 |
408 | private annotateLink(
409 | text: RichTextText['text'],
410 | annotatedContent: string
411 | ): string {
412 | return `[${annotatedContent}](${
413 | text.link.url ? text.link.url : text.link
414 | })`;
415 | }
416 |
417 | private annotateModifier(
418 | modifier: keyof Annotations,
419 | originalContent: string,
420 | color?: Annotations['color']
421 | ): string {
422 | switch (modifier) {
423 | case 'bold':
424 | return `**${originalContent}**`;
425 | case 'italic':
426 | return `_${originalContent}_`;
427 | case 'strikethrough':
428 | return `~~${originalContent}~~`;
429 | case 'underline':
430 | return `${originalContent}`;
431 | case 'code':
432 | return `\`${originalContent}\``;
433 | case 'color':
434 | if (color !== 'default') {
435 | return `${originalContent}`;
436 | }
437 | return originalContent;
438 | }
439 | }
440 | }
441 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/src/lib/external-video.util.ts:
--------------------------------------------------------------------------------
1 | export function processExternalVideoUrl(url: string): [boolean, string] {
2 | if (url.includes('youtu')) {
3 | return processYoutubeUrl(url);
4 | }
5 |
6 | return [false, url];
7 | }
8 |
9 | function processYoutubeUrl(youtubeUrl: string): [boolean, string] {
10 | const lastPart = youtubeUrl.split('/').pop();
11 |
12 | const youtubeIframe = ``;
13 |
14 | if (lastPart.includes('watch')) {
15 | const [, queryString] = lastPart.split('watch');
16 | const queryParams = new URLSearchParams(queryString);
17 | if (queryParams.has('v')) {
18 | return [true, youtubeIframe.replace('{{videoId}}', queryParams.get('v'))];
19 | }
20 |
21 | return [false, youtubeUrl];
22 | }
23 |
24 | return [true, youtubeIframe.replace('{{videoId}}', lastPart)];
25 | }
26 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.lib.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "../../dist/out-tsc",
6 | "declaration": true,
7 | "types": ["node"]
8 | },
9 | "exclude": ["**/*.spec.ts"],
10 | "include": ["**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/libs/blocks-markdown-parser/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "**/*.spec.ts",
10 | "**/*.spec.tsx",
11 | "**/*.spec.js",
12 | "**/*.spec.jsx",
13 | "**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/README.md:
--------------------------------------------------------------------------------
1 | # @notion-stuff/scully-plugin-notion
2 |
3 | This is a [Scully](https://scully.io) plugin to convert [Notion](https://notion.so) pages to static HTML pages
4 | utilizing [@notion-stuff/blocks-html-parser](../blocks-html-parser/README.md)
5 |
6 | > [Notion API](https://developers.notion.com/) is currently in beta. There are content types that aren't being supported. Check the Notion API documentations for details
7 |
8 | ## Features
9 |
10 | - [x] Convert Notion pages to static HTML
11 | - [x] Access to Notion page properties as `Frontmatter` (should be `ScullyRoute` from `ScullyRoutesService`)
12 | - [ ] Customize Notion page properties
13 | - [x] Customize HTML parsers. Please check out `NotionBlocksHtmlParser` documentations for more details.
14 |
15 | ## Installation
16 |
17 | ```bash
18 | npm install --save-dev @notion-stuff/scully-plugin-notion
19 | ```
20 |
21 | > `@notion-stuff/scully-plugin-notion` depends on `@notionhq/client`
22 |
23 | ## Usage
24 |
25 | > Notion integration steps courtesy of [Gatsby Source Notion Plugin author](https://www.gatsbyjs.com/plugins/gatsby-source-notion-api/)
26 |
27 | 1. Created a Notion integration (sign in to Notion, go
28 | to `Settings & Memberships → Integrations → Develop your own integrations`
29 | , [short link to the Integrations creation section](https://www.notion.so/my-integrations)). It’s OK to use an
30 | internal one. Don’t forget to copy the token:
31 | 
32 |
33 | 2. Go to the database you want to have access to from Scully, and share it with the integration (`Share` → Select the
34 | integration in the `Invite` dropdown). Don’t forget the database in the URL. It’s a series of characters after the
35 | last slash and before the question mark.
36 | 
37 |
38 | > Here’s a reference: https://www.notion.so/{USER}/**{DATABASE_ID}**?{someotherirrelevantstuff}
39 | > If you only have 1 ID before the question mark in the URL, then the first ID is the Database ID
40 | > i.e. https://www.notion.so/**{DATABASE_ID}**?{OtherIdThatDoesNotMatter}
41 |
42 | 3. Your database needs to have the following Page Properties (Table column headers)
43 |
44 | | Title | Status | Slug |
45 | | ---------------------- | ----------------------------------------- | ---- |
46 | | This should be default | Select (a `Published` option is required) | Text |
47 |
48 | - `Status` is required
49 | - `Published` status is required because this is being used by the plugin to set the `published` flag for a
50 | specific `ScullyRoute`. Although this is required for the plugin to work as expected, your intention (as plugin
51 | consumers) to use the `published` flag is totally up to you.
52 | - `Slug` is utilized to setup the route to the post which is needed to be set manually as of the moment.
53 | - You can call `Slug` whatever you want but this needs to match `slugKey`
54 |
55 | 4. Configure the plugin in `scully.your-app.config.ts`
56 |
57 | ## Frontmatter (route metadata)
58 |
59 | All columns in the table will be added to the route data which can be consumed
60 | via [ScullyRouteService](https://scully.io/docs/Reference/ngLib/scully-routes-service/) along with the additional items from notion
61 |
62 | > All column headers will be turned into [camelCase keys](https://github.com/nartc/notion-stuff/blob/main/libs/scully-plugin-notion/src/lib/utils.ts#L16) to be used in the frontmatter/config of the plugin
63 |
64 | ```ts
65 | interface AdditionalNotionProperties {
66 | // link to the notion page cover image or null if one isn't set. Note: [someKey] is commonly external or file
67 | cover: null | { type: string, [someKey: string]: { url: string } };
68 | // url to the notion page
69 | notionUrl: string;
70 | // GUID of the notion page
71 | id: string;
72 | }
73 | ```
74 |
75 | ```ts
76 | import { ScullyConfig, setPluginConfig } from '@scullyio/scully';
77 | import {
78 | NotionDom,
79 | NotionDomRouter,
80 | NotionDomPluginOptions,
81 | NotionDomRouterPluginOptions
82 | } from '@notion-stuff/scully-plugin-notion';
83 |
84 | setPluginConfig(NotionDom, {
85 | notionBlocksHtmlParserOptions: {
86 | /**
87 | ... customer the parser ...
88 | */
89 | },
90 | } as NotionDomPluginOptions);
91 |
92 | export const config: ScullyConfig = {
93 | projectRoot: './src',
94 | projectName: 'my-project',
95 | outDir: './dist/static',
96 | routes: {
97 | '/blog/:slug': {
98 | type: NotionDomRouter,
99 | postRenderers: [NotionDom],
100 | databaseId: 'your-database-id', // required
101 | notionApiKey: 'your-integration-secret', // if NOTION_API_KEY is set in Environment Variables, it is used instead of notionApiKey
102 | basePath: '/blog', // optional, should match your route here
103 | titleSuffix: '', // optional
104 | slugKey: 'slug', // optional
105 | } as NotionDomRouterPluginOptions,
106 | },
107 | };
108 | ```
109 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'scully-plugin-notion',
3 | preset: '../../jest.preset.js',
4 | globals: {
5 | 'ts-jest': {
6 | tsconfig: '/tsconfig.spec.json',
7 | },
8 | },
9 | testEnvironment: 'node',
10 | transform: {
11 | '^.+\\.[tj]sx?$': 'ts-jest',
12 | },
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
14 | coverageDirectory: '../../coverage/libs/scully-plugin-notion',
15 | };
16 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@notion-stuff/scully-plugin-notion",
3 | "version": "6.0.0",
4 | "dependencies": {
5 | "@notionhq/client": "~1.0.0"
6 | },
7 | "peerDependencies": {
8 | "@scullyio/scully": "^2.0.0"
9 | },
10 | "publishConfig": {
11 | "access": "public"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/plugin';
2 | export * from './lib/plugin-options';
3 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/src/lib/plugin-options.ts:
--------------------------------------------------------------------------------
1 | import type { NotionBlocksHtmlParserOptions } from '@notion-stuff/blocks-html-parser';
2 | import type { PostResult } from '@notion-stuff/v4-types';
3 | import type { RouteConfig } from '@scullyio/scully';
4 | import type { GetPagePropertyResponse } from '@notionhq/client/build/src/api-endpoints';
5 |
6 | export interface NotionDomRouterPluginOptions
7 | extends Omit {
8 | type: string;
9 | /**
10 | * @requires string Notion DatabaseID
11 | */
12 | databaseId: string;
13 |
14 | /**
15 | * Notion API Key. This should be provided via NOTION_API_KEY environment variable instead
16 | */
17 | notionApiKey?: string;
18 |
19 | /**
20 | * A custom function that will process the {PostResult} from Notion. You take over the Frontmatter with this function
21 | */
22 | postResultProcessor?>(
23 | post: PostResult,
24 | options: NotionDomRouterPluginOptions,
25 | propertyValueParser: (propertyValue: GetPagePropertyResponse) => unknown
26 | ): TObject;
27 |
28 | /**
29 | * A custom function that will resolve the `published` flag that Scully needs.
30 | */
31 | isPublished?>(
32 | frontmatter: TObject
33 | ): boolean;
34 |
35 | /**
36 | * Default icon for your post
37 | */
38 | defaultPostIcon?: string;
39 |
40 | /**
41 | * The key that you use as your "Slug" in your Notion table.
42 | * @default "slug" (requires you to have a "Slug" property in your table)
43 | */
44 | slugKey?: string;
45 |
46 | /**
47 | * The base path of the route when setup in Scully config
48 | * @example
49 | * {
50 | * "/basePath/:slug": {
51 | * basePath: "/basePath" // needs to match /basePath
52 | * }
53 | * }
54 | *
55 | * @default "/blog"
56 | */
57 | basePath?: string;
58 | titleSuffix?: string;
59 | }
60 |
61 | export interface NotionDomPluginOptions {
62 | notionBlocksHtmlParserOptions?: NotionBlocksHtmlParserOptions;
63 | }
64 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/src/lib/plugin.ts:
--------------------------------------------------------------------------------
1 | import { NotionBlocksHtmlParser } from '@notion-stuff/blocks-html-parser';
2 | import type { Blocks, PostResult } from '@notion-stuff/v4-types';
3 | import { Client } from '@notionhq/client/build/src';
4 | import type { HandledRoute, RouteConfig } from '@scullyio/scully';
5 | import {
6 | getPluginConfig,
7 | log,
8 | red,
9 | registerPlugin,
10 | yellow,
11 | } from '@scullyio/scully';
12 | import { injectHtml } from '@scullyio/scully/src/lib/renderPlugins/content-render-utils/injectHtml';
13 | import type {
14 | NotionDomPluginOptions,
15 | NotionDomRouterPluginOptions,
16 | } from './plugin-options';
17 | import { processPageProperties } from './process-page-properties';
18 |
19 | export const NotionDom = 'notionDom';
20 | export const NotionDomRouter = 'notionDomRouter';
21 |
22 | let notion: Client;
23 | let htmlParser: NotionBlocksHtmlParser;
24 | let pluginOptions: NotionDomPluginOptions;
25 |
26 | function setupParserAndPluginOptions() {
27 | if (!pluginOptions) {
28 | pluginOptions = getPluginConfig(NotionDom, 'postProcessByDom') || {};
29 | }
30 |
31 | if (!htmlParser) {
32 | htmlParser = NotionBlocksHtmlParser.getInstance(
33 | pluginOptions.notionBlocksHtmlParserOptions
34 | );
35 | }
36 | }
37 |
38 | try {
39 | notion = new Client({ auth: process.env.NOTION_API_KEY });
40 | } catch {
41 | log(
42 | yellow(
43 | 'No NOTION_API_KEY in Environment Variables. Attempting to use API key from the route config'
44 | )
45 | );
46 | }
47 |
48 | async function notionDomRouterPlugin(
49 | route: string | undefined,
50 | config: NotionDomRouterPluginOptions
51 | ) {
52 | const mergedConfig = {
53 | ...{
54 | slugKey: 'slug',
55 | basePath: '/blog',
56 | titleSuffix: '',
57 | defaultPostIcon: '',
58 | },
59 | ...config,
60 | };
61 |
62 | try {
63 | if (!notion) {
64 | if (!mergedConfig.notionApiKey) {
65 | return Promise.reject(
66 | new Error(
67 | 'Notion Client has not been instantiated. Please provide the Notion API Key'
68 | )
69 | );
70 | }
71 |
72 | notion = new Client({
73 | auth: mergedConfig.notionApiKey,
74 | });
75 | }
76 |
77 | const posts = await notion.databases.query({
78 | database_id: mergedConfig.databaseId,
79 | });
80 |
81 | setupParserAndPluginOptions();
82 |
83 | return Promise.resolve(
84 | posts.results.map((result) => {
85 | const postResult = result as unknown as PostResult;
86 | const frontmatter = processPageProperties(postResult, mergedConfig);
87 |
88 | let cover = '';
89 | let icon = mergedConfig.defaultPostIcon;
90 |
91 | if (postResult.cover) {
92 | cover = NotionBlocksHtmlParser.getMarkdownParser().parseFile(
93 | postResult.cover
94 | ).url;
95 | }
96 |
97 | if (postResult.icon) {
98 | switch (postResult.icon.type) {
99 | case 'emoji':
100 | icon = postResult.icon.emoji;
101 | break;
102 | case 'external':
103 | case 'file':
104 | icon = NotionBlocksHtmlParser.getMarkdownParser().parseFile(
105 | postResult.icon
106 | ).url;
107 | break;
108 | }
109 | }
110 |
111 | return {
112 | type: mergedConfig.type,
113 | route: `${mergedConfig.basePath}/${
114 | frontmatter[mergedConfig.slugKey]
115 | }`,
116 | title: mergedConfig.titleSuffix
117 | ? `${frontmatter.title} | ${mergedConfig.titleSuffix}`
118 | : frontmatter.title,
119 | data: {
120 | ...frontmatter,
121 | id: postResult.id,
122 | notionUrl: postResult.url,
123 | cover,
124 | icon,
125 | },
126 | } as HandledRoute;
127 | })
128 | );
129 | } catch (e) {
130 | throw new Error(`Something went wrong. ${e}`);
131 | }
132 | }
133 |
134 | const notionDomRouterValidator = (config: RouteConfig) => {
135 | const errors: string[] = [];
136 |
137 | if (!config.databaseId) {
138 | errors.push('Missing "databaseId"');
139 | }
140 |
141 | return errors;
142 | };
143 |
144 | async function notionDomPlugin(dom: unknown, route: HandledRoute | undefined) {
145 | if (!route) return dom;
146 |
147 | const postId = route.data?.['id'];
148 |
149 | if (!notion) {
150 | log(yellow(`Notion Client not found. Skipping ${route.route}`));
151 | return Promise.resolve(dom);
152 | }
153 |
154 | if (!postId) {
155 | log(yellow(`Post ID not found. Skipping ${route.route}`));
156 | return Promise.resolve(dom);
157 | }
158 |
159 | try {
160 | const blocks = await notion.blocks.children.list({
161 | block_id: postId,
162 | });
163 | if (!blocks || !blocks.results.length) {
164 | log(yellow(`Post does not have any blocks. Skipping ${route.route}`));
165 | return Promise.resolve(dom);
166 | }
167 |
168 | return injectHtml(dom, htmlParser.parse(blocks.results as Blocks), route);
169 | } catch (e) {
170 | log(red(`Something went wrong. ${e}`));
171 | return Promise.resolve(dom);
172 | }
173 | }
174 |
175 | registerPlugin(
176 | 'router',
177 | NotionDomRouter,
178 | notionDomRouterPlugin,
179 | notionDomRouterValidator
180 | );
181 |
182 | registerPlugin('postProcessByDom', NotionDom, notionDomPlugin);
183 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/src/lib/process-page-properties.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | PostResult,
3 | PropertyValueFormula,
4 | PropertyValueRollup,
5 | PropertyValueSelect,
6 | PropertyValueUser,
7 | } from '@notion-stuff/v4-types';
8 | import type { NotionDomRouterPluginOptions } from './plugin-options';
9 |
10 | import { camelize } from './utils';
11 | import { GetPagePropertyResponse } from '@notionhq/client/build/src/api-endpoints';
12 |
13 | export function processPageProperties(
14 | post: PostResult,
15 | options: NotionDomRouterPluginOptions
16 | ) {
17 | if (options.postResultProcessor) {
18 | return options.postResultProcessor(post, options, parsePropertyValue);
19 | }
20 |
21 | const frontmatter: Record = { published: false };
22 |
23 | for (const [propertyKey, propertyValue] of Object.entries(post.properties)) {
24 | const camelizedKey = camelize(propertyKey);
25 | frontmatter[camelizedKey] = parsePropertyValue(
26 | propertyValue as unknown as GetPagePropertyResponse
27 | );
28 |
29 | if (!options.isPublished && propertyKey.toLowerCase() === 'status') {
30 | frontmatter.published =
31 | (
32 | propertyValue as unknown as PropertyValueSelect
33 | ).select?.name.toLowerCase() === 'published';
34 | }
35 | }
36 |
37 | if (options.isPublished) {
38 | frontmatter.published = options.isPublished(frontmatter);
39 | }
40 |
41 | return frontmatter;
42 | }
43 |
44 | function parsePropertyValue(propertyValue: GetPagePropertyResponse) {
45 | switch (propertyValue.type) {
46 | case 'title':
47 | return propertyValue.title[0].plain_text;
48 | case 'rich_text':
49 | return propertyValue.rich_text[0].plain_text;
50 | case 'number':
51 | return propertyValue.number;
52 | case 'select':
53 | return propertyValue.select?.name;
54 | case 'multi_select':
55 | return propertyValue.multi_select.map(
56 | (multiSelectOption) => multiSelectOption.name
57 | );
58 | case 'date':
59 | if (!propertyValue?.date) return null;
60 | if (propertyValue.date.end) {
61 | return [
62 | new Date(propertyValue.date.start),
63 | new Date(propertyValue.date.end),
64 | ];
65 | }
66 | return new Date(propertyValue.date.start);
67 | case 'formula':
68 | return formularize(propertyValue);
69 | case 'rollup':
70 | return rollup(propertyValue);
71 | case 'people':
72 | return personify(propertyValue.people as PropertyValueUser);
73 | case 'files':
74 | return propertyValue.files.map((fileWithName) => fileWithName.name);
75 | case 'checkbox':
76 | return propertyValue.checkbox;
77 | case 'url':
78 | return propertyValue.url;
79 | case 'email':
80 | return propertyValue.email;
81 | case 'phone_number':
82 | return propertyValue.phone_number;
83 | case 'created_time':
84 | return new Date(propertyValue.created_time);
85 | case 'created_by':
86 | return personify(propertyValue.created_by as PropertyValueUser);
87 | case 'last_edited_time':
88 | return new Date(propertyValue.last_edited_time);
89 | case 'last_edited_by':
90 | return personify(propertyValue.last_edited_by as PropertyValueUser);
91 | }
92 | }
93 |
94 | function formularize(formulaValue: PropertyValueFormula) {
95 | let value: unknown;
96 |
97 | switch (formulaValue.formula.type) {
98 | case 'string':
99 | value = formulaValue.formula.string;
100 | break;
101 | case 'number':
102 | value = formulaValue.formula.number;
103 | break;
104 | case 'boolean':
105 | value = formulaValue.formula.boolean;
106 | break;
107 | case 'date':
108 | if (formulaValue.formula.date?.end) {
109 | value = [
110 | formulaValue.formula.date?.start,
111 | formulaValue.formula.date?.end,
112 | ];
113 | } else {
114 | value = formulaValue.formula.date?.start;
115 | }
116 | break;
117 | }
118 | return value;
119 | }
120 |
121 | function rollup(rollupValue: PropertyValueRollup) {
122 | let value: unknown;
123 |
124 | switch (rollupValue.rollup.type) {
125 | case 'number':
126 | value = rollupValue.rollup.number;
127 | break;
128 | case 'date':
129 | if (rollupValue.rollup.date?.end) {
130 | value = [rollupValue.rollup.date?.start, rollupValue.rollup.date?.end];
131 | } else {
132 | value = rollupValue.rollup.date?.start;
133 | }
134 | break;
135 | case 'array':
136 | value = rollupValue.rollup.array.map((propertyValue) =>
137 | parsePropertyValue(propertyValue as unknown as GetPagePropertyResponse)
138 | );
139 | break;
140 | }
141 |
142 | return value;
143 | }
144 |
145 | function personify(user: PropertyValueUser) {
146 | return {
147 | name: user.name,
148 | avatar: user.avatar_url,
149 | };
150 | }
151 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | const STRING_CAMELIZE_REGEXP = /(-|_|\.|\s)+(.)?/g;
2 |
3 | /**
4 | Returns the lowerCamelCase form of a string.
5 | ```javascript
6 | camelize('innerHTML'); // 'innerHTML'
7 | camelize('action_name'); // 'actionName'
8 | camelize('css-class-name'); // 'cssClassName'
9 | camelize('my favorite items'); // 'myFavoriteItems'
10 | camelize('My Favorite Items'); // 'myFavoriteItems'
11 | ```
12 | @method camelize
13 | @param {String} str The string to camelize.
14 | @return {String} the camelized string.
15 | */
16 | export function camelize(str: string): string {
17 | return str
18 | .replace(
19 | STRING_CAMELIZE_REGEXP,
20 | (_match: string, _separator: string, chr: string) => {
21 | return chr ? chr.toUpperCase() : '';
22 | }
23 | )
24 | .replace(/^([A-Z])/, (match: string) => match.toLowerCase());
25 | }
26 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.lib.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "../../dist/out-tsc",
6 | "declaration": true,
7 | "types": ["node"]
8 | },
9 | "exclude": ["**/*.spec.ts"],
10 | "include": ["**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/libs/scully-plugin-notion/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "**/*.spec.ts",
10 | "**/*.spec.tsx",
11 | "**/*.spec.js",
12 | "**/*.spec.jsx",
13 | "**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/libs/v4-types/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@nrwl/web/babel", { "useBuiltIns": "usage" }]]
3 | }
4 |
--------------------------------------------------------------------------------
/libs/v4-types/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7 | "rules": {}
8 | },
9 | {
10 | "files": ["*.ts", "*.tsx"],
11 | "rules": {}
12 | },
13 | {
14 | "files": ["*.js", "*.jsx"],
15 | "rules": {}
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/libs/v4-types/README.md:
--------------------------------------------------------------------------------
1 | # @notion-stuff/v4-types
2 |
3 |
4 | This package is to cover the typings of `@notionhq/client@0.4`.
5 |
6 | Since v0.4, `@notionhq/client` uses generated typings for their API Responses which breaks all the Interfaces/Types downstream for community packages that depend on those Interfaces/Types. This package aims to address this issue by construct Type Aliases from `@notion/client` API Responses Types.
7 |
8 | ## Installation
9 |
10 | ```bash
11 | npm i --save-dev @notion-stuff/v4-types
12 | ```
13 | or
14 | ```bash
15 | yarn add --dev @notion-stuff/v4-types
16 | ```
17 |
18 | ## Feature
19 | - [x] Blocks
20 | - [x] Page Properties
21 |
22 | If you find anything missing, feel free to create an issue.
23 |
--------------------------------------------------------------------------------
/libs/v4-types/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'v4-types',
3 | preset: '../../jest.preset.js',
4 | globals: {
5 | 'ts-jest': {
6 | tsconfig: '/tsconfig.spec.json',
7 | },
8 | },
9 | testEnvironment: 'node',
10 | transform: {
11 | '^.+\\.[tj]sx?$': 'ts-jest',
12 | },
13 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
14 | coverageDirectory: '../../coverage/libs/v4-types',
15 | };
16 |
--------------------------------------------------------------------------------
/libs/v4-types/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@notion-stuff/v4-types",
3 | "version": "6.0.0",
4 | "dependencies": {
5 | "@notionhq/client": "~1.0.0"
6 | },
7 | "peerDependencies": {
8 | "@scullyio/scully": "^2.0.0"
9 | },
10 | "publishConfig": {
11 | "access": "public"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/libs/v4-types/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './lib/types';
2 |
--------------------------------------------------------------------------------
/libs/v4-types/src/lib/types.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | GetBlockResponse,
3 | GetDatabaseResponse,
4 | GetPagePropertyResponse,
5 | } from '@notionhq/client/build/src/api-endpoints';
6 |
7 | /** Property **/
8 |
9 | export type PostResult = Extract;
10 | export type PropertyValueType = GetPagePropertyResponse['type'];
11 |
12 | export type ExtractedPropertyValue = Extract<
13 | GetPagePropertyResponse,
14 | { type: TType }
15 | >;
16 |
17 | export type PropertyValuePropertyItem = ExtractedPropertyValue<'property_item'>;
18 | export type PropertyValueTitle = ExtractedPropertyValue<'title'>;
19 | export type PropertyValueRichText = ExtractedPropertyValue<'rich_text'>;
20 | export type PropertyValueNumber = ExtractedPropertyValue<'number'>;
21 | export type PropertyValueUrl = ExtractedPropertyValue<'url'>;
22 | export type PropertyValueSelect = ExtractedPropertyValue<'select'>;
23 | export type PropertyValueMultiSelect = ExtractedPropertyValue<'multi_select'>;
24 | export type PropertyValuePeople = ExtractedPropertyValue<'people'>;
25 | export type PropertyValueEmail = ExtractedPropertyValue<'email'>;
26 | export type PropertyValuePhoneNumber = ExtractedPropertyValue<'phone_number'>;
27 | export type PropertyValueDate = ExtractedPropertyValue<'date'>;
28 | export type PropertyValueFiles = ExtractedPropertyValue<'files'>;
29 | export type PropertyValueFormula = ExtractedPropertyValue<'formula'>;
30 | export type PropertyValueRelation = ExtractedPropertyValue<'relation'>;
31 | export type PropertyValueRollup = ExtractedPropertyValue<'rollup'>;
32 | export type PropertyValueCreatedTime = ExtractedPropertyValue<'created_time'>;
33 | export type PropertyValueCreatedBy = ExtractedPropertyValue<'created_by'>;
34 | export type PropertyValueEditedTime =
35 | ExtractedPropertyValue<'last_edited_time'>;
36 | export type PropertyValueEditedBy = ExtractedPropertyValue<'last_edited_by'>;
37 | export type PropertyValueCheckbox = ExtractedPropertyValue<'checkbox'>;
38 |
39 | /** People **/
40 | export type PropertyValueUser = Extract<
41 | PropertyValuePeople['people'],
42 | { type: string }
43 | >;
44 | export type PropertyValueUserType = PropertyValueUser['type'];
45 |
46 | export type PropertyValueUserPerson = Extract<
47 | PropertyValueUser,
48 | { type: 'person' }
49 | >;
50 | export type PropertyValueUserBot = Extract;
51 |
52 | /** Block **/
53 | export type Block = Extract;
54 |
55 | export type BlockType = Block['type'];
56 |
57 | export type ExtractedBlockType = Extract<
58 | Block,
59 | { type: TType }
60 | >;
61 |
62 | export type Blocks = Block[];
63 |
64 | export type ParagraphBlock = ExtractedBlockType<'paragraph'>;
65 |
66 | export type HeadingOneBlock = ExtractedBlockType<'heading_1'>;
67 | export type HeadingTwoBlock = ExtractedBlockType<'heading_2'>;
68 | export type HeadingThreeBlock = ExtractedBlockType<'heading_3'>;
69 |
70 | export type HeadingBlock =
71 | | HeadingOneBlock
72 | | HeadingTwoBlock
73 | | HeadingThreeBlock;
74 |
75 | export type BulletedListItemBlock = ExtractedBlockType<'bulleted_list_item'>;
76 | export type NumberedListItemBlock = ExtractedBlockType<'numbered_list_item'>;
77 |
78 | export type TableBlock = ExtractedBlockType<'table'>;
79 | export type TableRowBlock = ExtractedBlockType<'table_row'>;
80 |
81 | export type QuoteBlock = ExtractedBlockType<'quote'>;
82 | export type EquationBlock = ExtractedBlockType<'equation'>;
83 | export type CodeBlock = ExtractedBlockType<'code'>;
84 | export type CalloutBlock = ExtractedBlockType<'callout'>;
85 | export type ToDoBlock = ExtractedBlockType<'to_do'>;
86 | export type BookmarkBlock = ExtractedBlockType<'bookmark'>;
87 | export type ToggleBlock = ExtractedBlockType<'toggle'>;
88 | export type TemplateBlock = ExtractedBlockType<'template'>;
89 | export type SyncedBlock = ExtractedBlockType<'synced_block'>;
90 | export type BreadcrumbBlock = ExtractedBlockType<'breadcrumb'>;
91 |
92 | export type ChildPageBlock = ExtractedBlockType<'child_page'>;
93 | export type ChildDatabaseBlock = ExtractedBlockType<'child_database'>;
94 |
95 | export type EmbedBlock = ExtractedBlockType<'embed'>;
96 | export type ImageBlock = ExtractedBlockType<'image'>;
97 | export type VideoBlock = ExtractedBlockType<'video'>;
98 | export type PDFBlock = ExtractedBlockType<'pdf'>;
99 | export type FileBlock = ExtractedBlockType<'file'>;
100 | export type AudioBlock = ExtractedBlockType<'audio'>;
101 |
102 | export type TocBlock = ExtractedBlockType<'table_of_contents'>;
103 | export type DividerBlock = ExtractedBlockType<'divider'>;
104 |
105 | export type ColumnBlock = ExtractedBlockType<'column'>;
106 | export type ColumnListBlock = ExtractedBlockType<'column_list'>;
107 |
108 | export type LinkPreviewBlock = ExtractedBlockType<'link_preview'>;
109 | export type LinkToPageBlock = ExtractedBlockType<'link_to_page'>;
110 |
111 | export type UnsupportedBlock = ExtractedBlockType<'unsupported'>;
112 |
113 | /** RichText **/
114 | export type RichText = ParagraphBlock['paragraph']['rich_text'][number];
115 |
116 | export type Annotations = RichText['annotations'];
117 |
118 | export type RichTextType = RichText['type'];
119 |
120 | export type ExtractedRichText = Extract<
121 | RichText,
122 | { type: TType }
123 | >;
124 |
125 | export type RichTextText = ExtractedRichText<'text'>;
126 | export type RichTextMention = ExtractedRichText<'mention'>;
127 | export type RichTextEquation = ExtractedRichText<'equation'>;
128 |
129 | /** File **/
130 | export type File = ImageBlock['image'];
131 |
132 | export type FileType = File['type'];
133 |
134 | export type ExtractedFile = Extract<
135 | File,
136 | { type: TType }
137 | >;
138 |
139 | export type ExternalFileWithCaption = Omit<
140 | ExtractedFile<'external'>,
141 | 'caption'
142 | > & { caption?: ExtractedFile<'external'>['caption'] };
143 | export type FileWithCaption = Omit, 'caption'> & {
144 | caption?: ExtractedFile<'file'>['caption'];
145 | };
146 |
147 | /** Callout */
148 | export type CalloutIcon = CalloutBlock['callout']['icon'];
149 |
150 | export type CalloutIconType = CalloutIcon['type'];
151 |
152 | export type ExtractedCalloutIcon = Extract<
153 | CalloutIcon,
154 | { type: TType }
155 | >;
156 |
157 | export type CalloutIconEmoji = ExtractedCalloutIcon<'emoji'>;
158 | export type CalloutIconExternal = ExtractedCalloutIcon<'external'>;
159 | export type CalloutIconFile = ExtractedCalloutIcon<'file'>;
160 |
--------------------------------------------------------------------------------
/libs/v4-types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.lib.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/libs/v4-types/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "outDir": "../../dist/out-tsc",
6 | "declaration": true,
7 | "types": ["node"]
8 | },
9 | "exclude": ["**/*.spec.ts"],
10 | "include": ["**/*.ts"]
11 | }
12 |
--------------------------------------------------------------------------------
/libs/v4-types/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "include": [
9 | "**/*.spec.ts",
10 | "**/*.spec.tsx",
11 | "**/*.spec.js",
12 | "**/*.spec.jsx",
13 | "**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/migrations.json:
--------------------------------------------------------------------------------
1 | {
2 | "migrations": [
3 | {
4 | "version": "13.6.0-beta.0",
5 | "description": "Remove old options that are no longer used",
6 | "cli": "nx",
7 | "implementation": "./src/migrations/update-13-6-0/remove-old-task-runner-options",
8 | "package": "@nrwl/workspace",
9 | "name": "13-6-0-remove-old-task-runner-options"
10 | },
11 | {
12 | "version": "13.9.0-beta.0",
13 | "description": "Update the decorate-angular-cli script to require nx instead of @nrwl/cli",
14 | "cli": "nx",
15 | "implementation": "./src/migrations/update-13-9-0/update-decorate-cli",
16 | "package": "@nrwl/workspace",
17 | "name": "13-9-0-update-decorate-cli"
18 | },
19 | {
20 | "version": "13.9.0-beta.0",
21 | "description": "Replace @nrwl/tao with nx",
22 | "cli": "nx",
23 | "implementation": "./src/migrations/update-13-9-0/replace-tao-with-nx",
24 | "package": "@nrwl/workspace",
25 | "name": "13-9-0-replace-tao-with-nx"
26 | },
27 | {
28 | "version": "13.4.4-beta.0",
29 | "cli": "nx",
30 | "description": "Create a root babel config file if it doesn't exist and using babel-jest in jest.config.js and add @nrwl/web as needed",
31 | "factory": "./src/migrations/update-13-4-4/add-missing-root-babel-config",
32 | "package": "@nrwl/jest",
33 | "name": "add-missing-root-babel-config"
34 | },
35 | {
36 | "cli": "nx",
37 | "version": "13.8.5-beta.1",
38 | "description": "Renames @nrwl/node:build to @nrwl/node:webpack",
39 | "factory": "./src/migrations/update-13-8-5/rename-build-to-webpack",
40 | "package": "@nrwl/node",
41 | "name": "rename-build-to-webpack"
42 | },
43 | {
44 | "cli": "nx",
45 | "version": "13.8.5-beta.1",
46 | "description": "Renames @nrwl/node:execute to @nrwl/node:node",
47 | "factory": "./src/migrations/update-13-8-5/rename-execute-to-node",
48 | "package": "@nrwl/node",
49 | "name": "rename-execute-to-node"
50 | },
51 | {
52 | "cli": "nx",
53 | "version": "13.8.5-beta.1",
54 | "description": "Renames @nrwl/node:package to @nrwl/js:tsc",
55 | "factory": "./src/migrations/update-13-8-5/update-package-to-tsc",
56 | "package": "@nrwl/node",
57 | "name": "update-package-to-tsc"
58 | }
59 | ]
60 | }
61 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmScope": "notion-stuff",
3 | "affected": {
4 | "defaultBase": "main"
5 | },
6 | "implicitDependencies": {
7 | "package.json": {
8 | "dependencies": "*",
9 | "devDependencies": "*"
10 | },
11 | ".eslintrc.json": "*"
12 | },
13 | "tasksRunnerOptions": {
14 | "default": {
15 | "runner": "@nrwl/workspace/tasks-runners/default",
16 | "options": {
17 | "cacheableOperations": [
18 | "build",
19 | "lint",
20 | "test",
21 | "e2e"
22 | ],
23 | "parallel": 1
24 | }
25 | }
26 | },
27 | "targetDependencies": {
28 | "build": [
29 | {
30 | "target": "build",
31 | "projects": "dependencies"
32 | }
33 | ]
34 | },
35 | "cli": {
36 | "defaultCollection": "@nrwl/workspace"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "notion-stuff",
3 | "version": "6.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "start": "nx serve",
7 | "test": "nx test",
8 | "commit": "git-cz",
9 | "release": "dotenv release-it --",
10 | "build": "nx run-many --target=build --all --verbose",
11 | "publish": "nx run-many --target=publish --all --parallel",
12 | "contributors:init": "all-contributors init",
13 | "contributors:add": "all-contributors add"
14 | },
15 | "private": true,
16 | "dependencies": {
17 | "tslib": "^2.3.1"
18 | },
19 | "devDependencies": {
20 | "@notionhq/client": "~1.0.4",
21 | "@nrwl/cli": "13.9.5",
22 | "@nrwl/eslint-plugin-nx": "13.9.5",
23 | "@nrwl/jest": "13.9.5",
24 | "@nrwl/js": "13.9.5",
25 | "@nrwl/linter": "13.9.5",
26 | "@nrwl/node": "13.9.5",
27 | "@nrwl/workspace": "13.9.5",
28 | "@release-it/bumper": "3.0.1",
29 | "@release-it/conventional-changelog": "4.2.2",
30 | "@scullyio/scully": "2.1.29",
31 | "@types/jest": "27.4.1",
32 | "@types/marked": "4.0.3",
33 | "@types/node": "16.11.7",
34 | "@typescript-eslint/eslint-plugin": "5.17.0",
35 | "@typescript-eslint/parser": "5.17.0",
36 | "all-contributors-cli": "6.20.0",
37 | "commitizen": "4.2.4",
38 | "cz-conventional-changelog": "3.3.0",
39 | "cz-customizable": "6.3.0",
40 | "dotenv-cli": "5.1.0",
41 | "eslint": "8.12.0",
42 | "eslint-config-prettier": "8.5.0",
43 | "highlight.js": "11.5.0",
44 | "jest": "27.5.1",
45 | "marked": "4.0.12",
46 | "nx": "13.9.5",
47 | "prettier": "2.6.1",
48 | "prismjs": "1.27.0",
49 | "release-it": "14.14.0",
50 | "ts-jest": "27.1.4",
51 | "typescript": "~4.6.3"
52 | },
53 | "config": {
54 | "commitizen": {
55 | "path": "./node_modules/cz-customizable"
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/tools/generators/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nartc/notion-stuff/905af932a1e376997775e0288d5c833f90298260/tools/generators/.gitkeep
--------------------------------------------------------------------------------
/tools/tsconfig.tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc/tools",
5 | "rootDir": ".",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": ["node"],
9 | "importHelpers": false
10 | },
11 | "include": ["**/*.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "importHelpers": true,
11 | "target": "es2015",
12 | "module": "esnext",
13 | "lib": ["es2017", "dom"],
14 | "skipLibCheck": true,
15 | "skipDefaultLibCheck": true,
16 | "baseUrl": ".",
17 | "paths": {
18 | "@notion-stuff/blocks-html-parser": [
19 | "libs/blocks-html-parser/src/index.ts"
20 | ],
21 | "@notion-stuff/blocks-markdown-parser": [
22 | "libs/blocks-markdown-parser/src/index.ts"
23 | ],
24 | "@notion-stuff/scully-plugin-notion": [
25 | "libs/scully-plugin-notion/src/index.ts"
26 | ],
27 | "@notion-stuff/v4-types": ["libs/v4-types/src/index.ts"]
28 | }
29 | },
30 | "exclude": ["node_modules", "tmp"]
31 | }
32 |
--------------------------------------------------------------------------------
/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "projects": {
4 | "blocks-html-parser": {
5 | "root": "libs/blocks-html-parser",
6 | "sourceRoot": "libs/blocks-html-parser/src",
7 | "projectType": "library",
8 | "targets": {
9 | "build": {
10 | "executor": "@nrwl/js:tsc",
11 | "outputs": ["{options.outputPath}"],
12 | "options": {
13 | "outputPath": "dist/libs/blocks-html-parser",
14 | "tsConfig": "libs/blocks-html-parser/tsconfig.lib.json",
15 | "packageJson": "libs/blocks-html-parser/package.json",
16 | "main": "libs/blocks-html-parser/src/index.ts",
17 | "assets": ["libs/blocks-html-parser/*.md"]
18 | }
19 | },
20 | "lint": {
21 | "executor": "@nrwl/linter:eslint",
22 | "outputs": ["{options.outputFile}"],
23 | "options": {
24 | "lintFilePatterns": ["libs/blocks-html-parser/**/*.ts"]
25 | }
26 | },
27 | "test": {
28 | "executor": "@nrwl/jest:jest",
29 | "outputs": ["coverage/libs/blocks-html-parser"],
30 | "options": {
31 | "jestConfig": "libs/blocks-html-parser/jest.config.js",
32 | "passWithNoTests": true
33 | }
34 | },
35 | "publish": {
36 | "executor": "@nrwl/workspace:run-commands",
37 | "options": {
38 | "command": "npm publish",
39 | "cwd": "dist/libs/blocks-html-parser"
40 | }
41 | }
42 | },
43 | "tags": []
44 | },
45 | "blocks-markdown-parser": {
46 | "root": "libs/blocks-markdown-parser",
47 | "sourceRoot": "libs/blocks-markdown-parser/src",
48 | "projectType": "library",
49 | "targets": {
50 | "build": {
51 | "executor": "@nrwl/js:tsc",
52 | "outputs": ["{options.outputPath}"],
53 | "options": {
54 | "outputPath": "dist/libs/blocks-markdown-parser",
55 | "tsConfig": "libs/blocks-markdown-parser/tsconfig.lib.json",
56 | "packageJson": "libs/blocks-markdown-parser/package.json",
57 | "main": "libs/blocks-markdown-parser/src/index.ts",
58 | "assets": ["libs/blocks-markdown-parser/*.md"]
59 | }
60 | },
61 | "lint": {
62 | "executor": "@nrwl/linter:eslint",
63 | "outputs": ["{options.outputFile}"],
64 | "options": {
65 | "lintFilePatterns": ["libs/blocks-markdown-parser/**/*.ts"]
66 | }
67 | },
68 | "test": {
69 | "executor": "@nrwl/jest:jest",
70 | "outputs": ["coverage/libs/blocks-markdown-parser"],
71 | "options": {
72 | "jestConfig": "libs/blocks-markdown-parser/jest.config.js",
73 | "passWithNoTests": true
74 | }
75 | },
76 | "publish": {
77 | "executor": "@nrwl/workspace:run-commands",
78 | "options": {
79 | "command": "npm publish",
80 | "cwd": "dist/libs/blocks-markdown-parser"
81 | }
82 | }
83 | },
84 | "tags": []
85 | },
86 | "scully-plugin-notion": {
87 | "root": "libs/scully-plugin-notion",
88 | "sourceRoot": "libs/scully-plugin-notion/src",
89 | "projectType": "library",
90 | "targets": {
91 | "build": {
92 | "executor": "@nrwl/js:tsc",
93 | "outputs": ["{options.outputPath}"],
94 | "options": {
95 | "outputPath": "dist/libs/scully-plugin-notion",
96 | "tsConfig": "libs/scully-plugin-notion/tsconfig.lib.json",
97 | "packageJson": "libs/scully-plugin-notion/package.json",
98 | "main": "libs/scully-plugin-notion/src/index.ts",
99 | "assets": ["libs/scully-plugin-notion/*.md"]
100 | }
101 | },
102 | "lint": {
103 | "executor": "@nrwl/linter:eslint",
104 | "outputs": ["{options.outputFile}"],
105 | "options": {
106 | "lintFilePatterns": ["libs/scully-plugin-notion/**/*.ts"]
107 | }
108 | },
109 | "test": {
110 | "executor": "@nrwl/jest:jest",
111 | "outputs": ["coverage/libs/scully-plugin-notion"],
112 | "options": {
113 | "jestConfig": "libs/scully-plugin-notion/jest.config.js",
114 | "passWithNoTests": true
115 | }
116 | },
117 | "publish": {
118 | "executor": "@nrwl/workspace:run-commands",
119 | "options": {
120 | "command": "npm publish",
121 | "cwd": "dist/libs/scully-plugin-notion"
122 | }
123 | }
124 | },
125 | "tags": []
126 | },
127 | "v4-types": {
128 | "root": "libs/v4-types",
129 | "sourceRoot": "libs/v4-types/src",
130 | "projectType": "library",
131 | "targets": {
132 | "build": {
133 | "executor": "@nrwl/js:tsc",
134 | "outputs": ["{options.outputPath}"],
135 | "options": {
136 | "outputPath": "dist/libs/v4-types",
137 | "tsConfig": "libs/v4-types/tsconfig.lib.json",
138 | "packageJson": "libs/v4-types/package.json",
139 | "main": "libs/v4-types/src/index.ts",
140 | "assets": ["libs/v4-types/*.md"]
141 | }
142 | },
143 | "lint": {
144 | "executor": "@nrwl/linter:eslint",
145 | "outputs": ["{options.outputFile}"],
146 | "options": {
147 | "lintFilePatterns": ["libs/v4-types/**/*.ts"]
148 | }
149 | },
150 | "test": {
151 | "executor": "@nrwl/jest:jest",
152 | "outputs": ["coverage/libs/v4-types"],
153 | "options": {
154 | "jestConfig": "libs/v4-types/jest.config.js",
155 | "passWithNoTests": true
156 | }
157 | },
158 | "publish": {
159 | "executor": "@nrwl/workspace:run-commands",
160 | "options": {
161 | "command": "npm publish",
162 | "cwd": "dist/libs/v4-types"
163 | }
164 | }
165 | },
166 | "tags": ["scope:v4-types", "type:lib"]
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------