@sillsdev/docu-notion 0.17.0-alpha.3 → 1.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -15
- package/dist/NotionPage.d.ts +1 -1
- package/dist/NotionPage.js +6 -1
- package/dist/NotionPage.spec.js +62 -0
- package/dist/latex.spec.js +3 -0
- package/dist/plugins/CalloutTransformer.js +30 -18
- package/dist/plugins/CalloutTransformer.spec.js +39 -2
- package/dist/plugins/ColumnListTransformer.js +6 -6
- package/dist/plugins/ColumnTransformer.d.ts +6 -0
- package/dist/plugins/ColumnTransformer.js +56 -33
- package/dist/plugins/ColumnTransformer.spec.js +84 -0
- package/dist/plugins/EscapeHtmlBlockModifier.spec.js +6 -1
- package/dist/plugins/HeadingTranformer.spec.js +105 -25
- package/dist/plugins/HeadingTransformer.js +11 -9
- package/dist/plugins/internalLinks.js +36 -10
- package/dist/plugins/internalLinks.spec.js +47 -1
- package/dist/plugins/pluginTestRun.d.ts +3 -2
- package/dist/plugins/pluginTestRun.js +6 -12
- package/dist/pull.d.ts +4 -0
- package/dist/pull.js +25 -10
- package/dist/run.js +7 -5
- package/dist/transform.js +31 -0
- package/dist/types.d.ts +1 -1
- package/package.json +3 -5
package/README.md
CHANGED
|
@@ -4,6 +4,24 @@ docu-notion lets you use Notion as your editor for [Docusaurus](https://docusaur
|
|
|
4
4
|
|
|
5
5
|
Example Site: https://sillsdev.github.io/docu-notion-sample-site/
|
|
6
6
|
|
|
7
|
+
# Docusaurus 3 Output
|
|
8
|
+
|
|
9
|
+
docu-notion emits Docusaurus v3-compatible markdown by default.
|
|
10
|
+
|
|
11
|
+
The default output changed in these ways:
|
|
12
|
+
|
|
13
|
+
- Heading anchors are emitted with Docusaurus v3's MDX-comment syntax, for example `# Heading {/* #my-explicit-id */}`, instead of Docusaurus v2's `{#...}` syntax.
|
|
14
|
+
- `⚠️` callouts now emit `:::warning[Caution]` instead of the deprecated `:::caution` syntax.
|
|
15
|
+
- Callouts with unrecognized emojis now emit `:::note[emoji]` instead of using the emoji itself as a custom admonition keyword.
|
|
16
|
+
- Named Notion callout icons that are not returned by the API as emoji are ignored and fall back to a plain `:::note` admonition.
|
|
17
|
+
- docu-notion now warns when a generated page contains more than one Markdown H1, and the warning lists the H1 headings it found.
|
|
18
|
+
|
|
19
|
+
If you need the previous output, pass `--docusaurus-v2`. This restores the legacy heading ID syntax, the `:::caution` output, and the raw-emoji admonition fallback.
|
|
20
|
+
|
|
21
|
+
If you use `--docusaurus-v2` on Docusaurus v3, keep the `markdown.mdx1Compat.headingIds` and `markdown.mdx1Compat.admonitions` compatibility options enabled. They are on by default in Docusaurus v3, but some sites turn them off during migration.
|
|
22
|
+
|
|
23
|
+
When upgrading an existing Docusaurus site to v3, it is also worth running `npx docusaurus-mdx-checker` on the site to catch MDX v3 issues in any hand-written docs or custom plugin output.
|
|
24
|
+
|
|
7
25
|
# Instructions
|
|
8
26
|
|
|
9
27
|
## 1. Set up your documentation site
|
|
@@ -122,19 +140,20 @@ Usage: `docu-notion -n <token> -r <root> [options]`
|
|
|
122
140
|
|
|
123
141
|
Options:
|
|
124
142
|
|
|
125
|
-
| flag
|
|
126
|
-
|
|
|
127
|
-
| `-n, --notion-token <string>` | required | notion api token, which looks like `secret_3bc1b50XFYb15123RHF243x43450XFY33250XFYa343`
|
|
128
|
-
| `-r, --root-page <string>` | required | The 31 character ID of the page which is the root of your docs page in notion. The code will look like `9120ec9960244ead80fa2ef4bc1bba25`. This page must have a child page named 'Outline'
|
|
129
|
-
| `-m, --markdown-output-path <string>` | | Root of the hierarchy for md files. WARNING: node-pull-mdx will delete files from this directory. Note also that if it finds localized images, it will create an i18n/ directory as a sibling. (default: `./docs`)
|
|
130
|
-
| `-t, --status-tag <string>` | | Database pages without a Notion page property 'status' matching this will be ignored. Use '\*' to ignore status altogether. (default: `Publish`)
|
|
131
|
-
| `--locales <codes>` | | Comma-separated list of iso 639-2 codes, the same list as in docusaurus.config.js, minus the primary (i.e. 'en'). This is needed for image localization. (default: `[]`)
|
|
132
|
-
| `-l, --log-level <level>` | | Log level (choices: `info`, `verbose`, `debug`)
|
|
133
|
-
| `-i, --img-output-path <string>` | | Path to directory where images will be stored. If this is not included, images will be placed in the same directory as the document that uses them, which then allows for localization of screenshots.
|
|
134
|
-
| `-p, --img-prefix-in-markdown <string>` | | When referencing an image from markdown, prefix with this path instead of the full img-output-path. Should be used only in conjunction with --img-output-path.
|
|
135
|
-
| `--require-slugs` | | If set, docu-notion will fail if any pages it would otherwise publish are missing a slug in Notion.
|
|
143
|
+
| flag | required? | description |
|
|
144
|
+
| --------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
+
| `-n, --notion-token <string>` | required | notion api token, which looks like `secret_3bc1b50XFYb15123RHF243x43450XFY33250XFYa343` |
|
|
146
|
+
| `-r, --root-page <string>` | required | The 31 character ID of the page which is the root of your docs page in notion. The code will look like `9120ec9960244ead80fa2ef4bc1bba25`. This page must have a child page named 'Outline' |
|
|
147
|
+
| `-m, --markdown-output-path <string>` | | Root of the hierarchy for md files. WARNING: node-pull-mdx will delete files from this directory. Note also that if it finds localized images, it will create an i18n/ directory as a sibling. (default: `./docs`) |
|
|
148
|
+
| `-t, --status-tag <string>` | | Database pages without a Notion page property 'status' matching this will be ignored. Use '\*' to ignore status altogether. (default: `Publish`) |
|
|
149
|
+
| `--locales <codes>` | | Comma-separated list of iso 639-2 codes, the same list as in docusaurus.config.js, minus the primary (i.e. 'en'). This is needed for image localization. (default: `[]`) |
|
|
150
|
+
| `-l, --log-level <level>` | | Log level (choices: `info`, `verbose`, `debug`) |
|
|
151
|
+
| `-i, --img-output-path <string>` | | Path to directory where images will be stored. If this is not included, images will be placed in the same directory as the document that uses them, which then allows for localization of screenshots. |
|
|
152
|
+
| `-p, --img-prefix-in-markdown <string>` | | When referencing an image from markdown, prefix with this path instead of the full img-output-path. Should be used only in conjunction with --img-output-path. |
|
|
153
|
+
| `--require-slugs` | | If set, docu-notion will fail if any pages it would otherwise publish are missing a slug in Notion. |
|
|
154
|
+
| `--docusaurus-v2` | | Emit Docusaurus v2-compatible markdown instead of the default Docusaurus v3-compatible output. This preserves legacy heading IDs, `:::caution`, and raw-emoji admonition keywords. |
|
|
136
155
|
| `--image-file-name-format <format>` | | choices:<ul><li>`default`: {page slug (if any)}.{image block ID}</li><li>`content-hash`: Use a hash of the image content.</li><li>`legacy`: Use the legacy (before v0.16) method of determining file names. Set this to maintain backward compatibility.</li></ul>All formats will use the original file extension. |
|
|
137
|
-
| `-h, --help` | | display help for command
|
|
156
|
+
| `-h, --help` | | display help for command |
|
|
138
157
|
|
|
139
158
|
# Plugins
|
|
140
159
|
|
|
@@ -142,16 +161,19 @@ If your project needs some processing that docu-notion doesn't already provide,
|
|
|
142
161
|
|
|
143
162
|
# Callouts ➜ Admonitions
|
|
144
163
|
|
|
145
|
-
To map Notion callouts to Docusaurus admonitions, ensure the icon is for the type you want.
|
|
164
|
+
To map Notion callouts to Docusaurus admonitions, ensure the icon is an emoji for the type you want.
|
|
146
165
|
|
|
147
166
|
- ℹ️ ➜ note
|
|
148
167
|
- 📝➜ note
|
|
149
168
|
- 💡➜ tip
|
|
150
169
|
- ❗➜ info
|
|
151
|
-
- ⚠️➜
|
|
170
|
+
- ⚠️➜ warning[Caution]
|
|
152
171
|
- 🔥➜ danger
|
|
172
|
+
- unknown emoji ➜ note[emoji]
|
|
173
|
+
|
|
174
|
+
If a Notion callout uses a named Notion icon instead of an emoji, docu-notion does not try to synthesize an equivalent symbol from the icon name or color. Those callouts fall back to a plain `:::note` admonition.
|
|
153
175
|
|
|
154
|
-
The default admonition type, if no matching icon is found, is "note".
|
|
176
|
+
The default admonition type, if no matching icon is found, is "note". Use `--docusaurus-v2` to keep the legacy `⚠️ ➜ caution` behavior and the old raw-emoji fallback.
|
|
155
177
|
|
|
156
178
|
# Known Workarounds
|
|
157
179
|
|
package/dist/NotionPage.d.ts
CHANGED
package/dist/NotionPage.js
CHANGED
|
@@ -20,6 +20,11 @@ var PageType;
|
|
|
20
20
|
PageType[PageType["DatabasePage"] = 0] = "DatabasePage";
|
|
21
21
|
PageType[PageType["Simple"] = 1] = "Simple";
|
|
22
22
|
})(PageType || (exports.PageType = PageType = {}));
|
|
23
|
+
function isDatabaseBackedPage(metadata) {
|
|
24
|
+
var _a;
|
|
25
|
+
const parentType = (_a = metadata.parent) === null || _a === void 0 ? void 0 : _a.type;
|
|
26
|
+
return parentType === "database_id" || parentType === "data_source_id";
|
|
27
|
+
}
|
|
23
28
|
class NotionPage {
|
|
24
29
|
constructor(args) {
|
|
25
30
|
this.layoutContext = args.layoutContext;
|
|
@@ -53,7 +58,7 @@ class NotionPage {
|
|
|
53
58
|
...
|
|
54
59
|
},
|
|
55
60
|
*/
|
|
56
|
-
return this.metadata
|
|
61
|
+
return isDatabaseBackedPage(this.metadata)
|
|
57
62
|
? PageType.DatabasePage
|
|
58
63
|
: PageType.Simple;
|
|
59
64
|
}
|
package/dist/NotionPage.spec.js
CHANGED
|
@@ -140,4 +140,66 @@ describe("NotionPage", () => {
|
|
|
140
140
|
expect(result).toBe("Default Value");
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
|
+
describe("page type detection", () => {
|
|
144
|
+
it("treats data_source_id pages as database pages", () => {
|
|
145
|
+
const page = new NotionPage_1.NotionPage({
|
|
146
|
+
layoutContext: "Test Context",
|
|
147
|
+
pageId: "123",
|
|
148
|
+
order: 1,
|
|
149
|
+
metadata: Object.assign(Object.assign({}, mockMetadata), { parent: {
|
|
150
|
+
type: "data_source_id",
|
|
151
|
+
data_source_id: "source-123",
|
|
152
|
+
database_id: "database-123",
|
|
153
|
+
}, properties: {
|
|
154
|
+
Name: {
|
|
155
|
+
id: "title",
|
|
156
|
+
type: "title",
|
|
157
|
+
title: [
|
|
158
|
+
{
|
|
159
|
+
type: "text",
|
|
160
|
+
text: {
|
|
161
|
+
content: "Columns",
|
|
162
|
+
link: null,
|
|
163
|
+
},
|
|
164
|
+
annotations: {
|
|
165
|
+
bold: false,
|
|
166
|
+
italic: false,
|
|
167
|
+
strikethrough: false,
|
|
168
|
+
underline: false,
|
|
169
|
+
code: false,
|
|
170
|
+
color: "default",
|
|
171
|
+
},
|
|
172
|
+
plain_text: "Columns",
|
|
173
|
+
href: null,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
Status: {
|
|
178
|
+
id: "status",
|
|
179
|
+
type: "select",
|
|
180
|
+
select: {
|
|
181
|
+
id: "publish",
|
|
182
|
+
name: "Publish",
|
|
183
|
+
color: "green",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
} }),
|
|
187
|
+
foundDirectlyInOutline: false,
|
|
188
|
+
});
|
|
189
|
+
expect(page.type).toBe(NotionPage_1.PageType.DatabasePage);
|
|
190
|
+
expect(page.nameOrTitle).toBe("Columns");
|
|
191
|
+
expect(page.status).toBe("Publish");
|
|
192
|
+
});
|
|
193
|
+
it("keeps workspace pages as simple pages", () => {
|
|
194
|
+
const page = new NotionPage_1.NotionPage({
|
|
195
|
+
layoutContext: "Test Context",
|
|
196
|
+
pageId: "123",
|
|
197
|
+
order: 1,
|
|
198
|
+
metadata: mockMetadata,
|
|
199
|
+
foundDirectlyInOutline: true,
|
|
200
|
+
});
|
|
201
|
+
expect(page.type).toBe(NotionPage_1.PageType.Simple);
|
|
202
|
+
expect(page.nameOrTitle).toBe("FooBar");
|
|
203
|
+
});
|
|
204
|
+
});
|
|
143
205
|
});
|
package/dist/latex.spec.js
CHANGED
|
@@ -49,6 +49,7 @@ test("Latex Rendering", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
49
49
|
imgOutputPath: "",
|
|
50
50
|
imgPrefixInMarkdown: "",
|
|
51
51
|
statusTag: "",
|
|
52
|
+
docusaurusV2: false,
|
|
52
53
|
},
|
|
53
54
|
pages: pages,
|
|
54
55
|
counts: counts, // review will this get copied or pointed to?
|
|
@@ -75,6 +76,7 @@ test("Latex Rendering", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
75
76
|
},
|
|
76
77
|
has_children: false,
|
|
77
78
|
archived: false,
|
|
79
|
+
in_trash: false,
|
|
78
80
|
type: "paragraph",
|
|
79
81
|
paragraph: {
|
|
80
82
|
rich_text: [
|
|
@@ -93,6 +95,7 @@ test("Latex Rendering", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
93
95
|
href: null,
|
|
94
96
|
},
|
|
95
97
|
],
|
|
98
|
+
icon: null,
|
|
96
99
|
color: "default",
|
|
97
100
|
},
|
|
98
101
|
},
|
|
@@ -10,14 +10,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.standardCalloutTransformer = void 0;
|
|
13
|
-
// In Notion, you can make a callout and change its emoji. We map
|
|
14
|
-
// to the
|
|
13
|
+
// In Notion, you can make a callout and change its emoji. We map common callout
|
|
14
|
+
// emojis to the built-in Docusaurus admonition styles.
|
|
15
15
|
// This is mostly a copy of the callout code from notion-to-md. The change is to output docusaurus
|
|
16
16
|
// admonitions instead of emulating a callout with markdown > syntax.
|
|
17
17
|
// Note: I haven't yet tested this with any emoji except "💡"/"tip", nor the case where the
|
|
18
18
|
// callout has-children. Not even sure what that would mean, since the document I was testing
|
|
19
19
|
// with has quite complex markup inside the callout, but still takes the no-children branch.
|
|
20
|
-
function notionCalloutToAdmonition(
|
|
20
|
+
function notionCalloutToAdmonition(context, block) {
|
|
21
21
|
return __awaiter(this, void 0, void 0, function* () {
|
|
22
22
|
// In this case typescript is not able to index the types properly, hence ignoring the error
|
|
23
23
|
// @ts-ignore
|
|
@@ -28,7 +28,7 @@ function notionCalloutToAdmonition(notionToMarkdown, getBlockChildren, block) {
|
|
|
28
28
|
blockContent.map((content) => {
|
|
29
29
|
const annotations = content.annotations;
|
|
30
30
|
let plain_text = content.plain_text;
|
|
31
|
-
plain_text = notionToMarkdown.annotatePlainText(plain_text, annotations);
|
|
31
|
+
plain_text = context.notionToMarkdown.annotatePlainText(plain_text, annotations);
|
|
32
32
|
if (content["href"])
|
|
33
33
|
plain_text = `[${plain_text}](${content["href"]})`;
|
|
34
34
|
parsedData += plain_text;
|
|
@@ -36,17 +36,17 @@ function notionCalloutToAdmonition(notionToMarkdown, getBlockChildren, block) {
|
|
|
36
36
|
let callout_string = "";
|
|
37
37
|
const { id, has_children } = block;
|
|
38
38
|
if (!has_children) {
|
|
39
|
-
const result1 = callout(parsedData, icon);
|
|
39
|
+
const result1 = callout(parsedData, icon, context.options);
|
|
40
40
|
return result1;
|
|
41
41
|
}
|
|
42
|
-
const callout_children_object = yield getBlockChildren(id);
|
|
42
|
+
const callout_children_object = yield context.getBlockChildren(id);
|
|
43
43
|
// // parse children blocks to md object
|
|
44
|
-
const callout_children = yield notionToMarkdown.blocksToMarkdown(callout_children_object);
|
|
44
|
+
const callout_children = yield context.notionToMarkdown.blocksToMarkdown(callout_children_object);
|
|
45
45
|
callout_string += `${parsedData}\n`;
|
|
46
46
|
callout_children.map(child => {
|
|
47
47
|
callout_string += `${child.parent}\n\n`;
|
|
48
48
|
});
|
|
49
|
-
const result = callout(callout_string.trim(), icon);
|
|
49
|
+
const result = callout(callout_string.trim(), icon, context.options);
|
|
50
50
|
return result;
|
|
51
51
|
});
|
|
52
52
|
}
|
|
@@ -55,34 +55,46 @@ const calloutsToAdmonitions = {
|
|
|
55
55
|
"📝": "note",
|
|
56
56
|
"💡": "tip",
|
|
57
57
|
"❗": "info",
|
|
58
|
-
"⚠️": "
|
|
58
|
+
"⚠️": "warning",
|
|
59
59
|
"🔥": "danger",
|
|
60
60
|
};
|
|
61
61
|
// This is the main change from the notion-to-md code.
|
|
62
|
-
function callout(text, icon) {
|
|
63
|
-
var _a;
|
|
62
|
+
function callout(text, icon, options) {
|
|
63
|
+
var _a, _b, _c;
|
|
64
64
|
let emoji;
|
|
65
65
|
if ((icon === null || icon === void 0 ? void 0 : icon.type) === "emoji") {
|
|
66
66
|
emoji = icon.emoji;
|
|
67
67
|
}
|
|
68
|
+
const docusaurusV2 = (_a = options.docusaurusV2) !== null && _a !== void 0 ? _a : false;
|
|
68
69
|
let docusaurusAdmonition = "note";
|
|
70
|
+
let admonitionTitle = "";
|
|
69
71
|
if (emoji) {
|
|
72
|
+
if (docusaurusV2) {
|
|
73
|
+
docusaurusAdmonition =
|
|
74
|
+
(_b = calloutsToAdmonitions[emoji]) !== null && _b !== void 0 ? _b : emoji;
|
|
75
|
+
if (docusaurusAdmonition === "warning") {
|
|
76
|
+
docusaurusAdmonition = "caution";
|
|
77
|
+
}
|
|
78
|
+
return `:::${docusaurusAdmonition}\n\n${text}\n\n:::\n\n`;
|
|
79
|
+
}
|
|
70
80
|
// the keyof typeof magic persuades typescript that it really is OK to use emoji as a key into calloutsToAdmonitions
|
|
71
81
|
docusaurusAdmonition =
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
(_c = calloutsToAdmonitions[emoji]) !== null && _c !== void 0 ? _c : "note";
|
|
83
|
+
if (emoji === "⚠️") {
|
|
84
|
+
admonitionTitle = "[Caution]";
|
|
85
|
+
}
|
|
86
|
+
else if (!(emoji in calloutsToAdmonitions)) {
|
|
87
|
+
admonitionTitle = `[${emoji}]`;
|
|
88
|
+
}
|
|
77
89
|
}
|
|
78
|
-
return `:::${docusaurusAdmonition}\n\n${text}\n\n:::\n\n`;
|
|
90
|
+
return `:::${docusaurusAdmonition}${admonitionTitle}\n\n${text}\n\n:::\n\n`;
|
|
79
91
|
}
|
|
80
92
|
exports.standardCalloutTransformer = {
|
|
81
93
|
name: "standardCalloutTransformer",
|
|
82
94
|
notionToMarkdownTransforms: [
|
|
83
95
|
{
|
|
84
96
|
type: "callout",
|
|
85
|
-
getStringFromBlock: (context, block) => notionCalloutToAdmonition(context
|
|
97
|
+
getStringFromBlock: (context, block) => notionCalloutToAdmonition(context, block),
|
|
86
98
|
},
|
|
87
99
|
],
|
|
88
100
|
};
|
|
@@ -116,7 +116,7 @@ test("external link inside callout, bold preserved", () => __awaiter(void 0, voi
|
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
118
|
]);
|
|
119
|
-
expect(results.trim()).toBe(`:::
|
|
119
|
+
expect(results.trim()).toBe(`:::warning[Caution]
|
|
120
120
|
|
|
121
121
|
Callouts inline [**great page**](https://github.com).
|
|
122
122
|
|
|
@@ -191,9 +191,46 @@ test("internal link inside callout, bold preserved", () => __awaiter(void 0, voi
|
|
|
191
191
|
},
|
|
192
192
|
},
|
|
193
193
|
], [slugTargetPage]);
|
|
194
|
-
expect(results.trim()).toBe(`:::
|
|
194
|
+
expect(results.trim()).toBe(`:::warning[Caution]
|
|
195
195
|
|
|
196
196
|
Callouts inline [**great page**](/hello-world#456) the end.
|
|
197
197
|
|
|
198
198
|
:::`);
|
|
199
199
|
}));
|
|
200
|
+
test("unknown emoji callout falls back to note with title in docusaurus v3 mode", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
|
+
const config = { plugins: [CalloutTransformer_1.standardCalloutTransformer] };
|
|
202
|
+
block.callout.icon.emoji = "🧪";
|
|
203
|
+
const results = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
204
|
+
block,
|
|
205
|
+
]);
|
|
206
|
+
expect(results.trim()).toBe(`:::note[🧪]
|
|
207
|
+
|
|
208
|
+
This is the callout
|
|
209
|
+
|
|
210
|
+
:::`);
|
|
211
|
+
}));
|
|
212
|
+
test("named notion icon callout falls back to plain note in docusaurus v3 mode", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
213
|
+
const config = { plugins: [CalloutTransformer_1.standardCalloutTransformer] };
|
|
214
|
+
block.callout.icon = {
|
|
215
|
+
type: "icon",
|
|
216
|
+
icon: { name: "airplane", color: "purple" },
|
|
217
|
+
};
|
|
218
|
+
const results = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
219
|
+
block,
|
|
220
|
+
]);
|
|
221
|
+
expect(results.trim()).toBe(`:::note
|
|
222
|
+
|
|
223
|
+
This is the callout
|
|
224
|
+
|
|
225
|
+
:::`);
|
|
226
|
+
}));
|
|
227
|
+
test("docusaurus-v2 flag keeps legacy callout syntax", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
228
|
+
const config = { plugins: [CalloutTransformer_1.standardCalloutTransformer] };
|
|
229
|
+
block.callout.icon.emoji = "⚠️";
|
|
230
|
+
const results = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [block], undefined, undefined, undefined, { docusaurusV2: true });
|
|
231
|
+
expect(results.trim()).toBe(`:::caution
|
|
232
|
+
|
|
233
|
+
This is the callout
|
|
234
|
+
|
|
235
|
+
:::`);
|
|
236
|
+
}));
|
|
@@ -10,16 +10,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.standardColumnListTransformer = void 0;
|
|
13
|
+
const ColumnTransformer_1 = require("./ColumnTransformer");
|
|
13
14
|
function notionColumnListToMarkdown(notionToMarkdown, getBlockChildren, block) {
|
|
14
15
|
return __awaiter(this, void 0, void 0, function* () {
|
|
15
|
-
|
|
16
|
-
// However https://github1s.com/NotionX/react-notion-x/blob/master/packages/react-notion-x/src/block.tsx#L528 can get it.
|
|
17
|
-
const { id, has_children } = block; // "any" because the notion api type system is complex with a union that don't know how to help TS to cope with
|
|
18
|
-
if (!has_children)
|
|
16
|
+
if (!(0, ColumnTransformer_1.isColumnListBlock)(block) || !block.has_children)
|
|
19
17
|
return "";
|
|
20
|
-
const column_list_children = yield getBlockChildren(id);
|
|
18
|
+
const column_list_children = yield getBlockChildren(block.id);
|
|
19
|
+
(0, ColumnTransformer_1.rememberColumnListChildren)(column_list_children);
|
|
20
|
+
const columnsToRender = column_list_children.filter((child) => child.type === "column");
|
|
21
21
|
const columns = [];
|
|
22
|
-
for (const column of
|
|
22
|
+
for (const column of columnsToRender) {
|
|
23
23
|
// Keep column rendering sequential. A column block can trigger more Notion
|
|
24
24
|
// reads downstream, so Promise.all() here would turn one page into a burst
|
|
25
25
|
// of concurrent API requests during stage 2.
|
|
@@ -1,2 +1,8 @@
|
|
|
1
|
+
import { ColumnListBlockObjectResponse } from "@notionhq/client";
|
|
2
|
+
import { ListBlockChildrenResponseResult } from "notion-to-md/build/types";
|
|
1
3
|
import { IPlugin } from "./pluginTypes";
|
|
4
|
+
import { NotionBlock } from "../types";
|
|
5
|
+
export declare function isColumnListBlock(block: NotionBlock | ListBlockChildrenResponseResult): block is ColumnListBlockObjectResponse;
|
|
2
6
|
export declare const standardColumnTransformer: IPlugin;
|
|
7
|
+
export declare function rememberColumnListChildren(columnBlocks: NotionBlock[]): void;
|
|
8
|
+
export declare function getColumnWidth(block: ListBlockChildrenResponseResult): string;
|
|
@@ -10,8 +10,31 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.standardColumnTransformer = void 0;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
exports.isColumnListBlock = isColumnListBlock;
|
|
14
|
+
exports.rememberColumnListChildren = rememberColumnListChildren;
|
|
15
|
+
exports.getColumnWidth = getColumnWidth;
|
|
16
|
+
const columnCountById = new Map();
|
|
17
|
+
const normalizedColumnRatioById = new Map();
|
|
18
|
+
function isColumnBlock(block) {
|
|
19
|
+
return "type" in block && block.type === "column";
|
|
20
|
+
}
|
|
21
|
+
function isColumnListBlock(block) {
|
|
22
|
+
return "type" in block && block.type === "column_list";
|
|
23
|
+
}
|
|
24
|
+
function getRawColumnRatio(block) {
|
|
25
|
+
if (!isColumnBlock(block))
|
|
26
|
+
return undefined;
|
|
27
|
+
const ratio = block.column.width_ratio;
|
|
28
|
+
return typeof ratio === "number" && Number.isFinite(ratio)
|
|
29
|
+
? ratio
|
|
30
|
+
: undefined;
|
|
31
|
+
}
|
|
32
|
+
function approximatelyEqual(left, right, epsilon = 0.000001) {
|
|
33
|
+
return Math.abs(left - right) < epsilon;
|
|
34
|
+
}
|
|
35
|
+
function isDefinedNumber(value) {
|
|
36
|
+
return value !== undefined;
|
|
37
|
+
}
|
|
15
38
|
exports.standardColumnTransformer = {
|
|
16
39
|
name: "standardColumnTransformer",
|
|
17
40
|
notionToMarkdownTransforms: [
|
|
@@ -21,14 +44,32 @@ exports.standardColumnTransformer = {
|
|
|
21
44
|
},
|
|
22
45
|
],
|
|
23
46
|
};
|
|
47
|
+
function rememberColumnListChildren(columnBlocks) {
|
|
48
|
+
const columns = columnBlocks.filter(isColumnBlock);
|
|
49
|
+
const columnCount = columns.length;
|
|
50
|
+
const explicitRatios = columns.map(getRawColumnRatio);
|
|
51
|
+
const allExplicit = explicitRatios.every(isDefinedNumber);
|
|
52
|
+
const explicitSum = explicitRatios.reduce((sum, ratio) => sum + (ratio !== null && ratio !== void 0 ? ratio : 0), 0);
|
|
53
|
+
const normalizedRatios = allExplicit && approximatelyEqual(explicitSum, 1)
|
|
54
|
+
? explicitRatios
|
|
55
|
+
: (() => {
|
|
56
|
+
const weights = explicitRatios.map(ratio => ratio !== null && ratio !== void 0 ? ratio : 1);
|
|
57
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
58
|
+
return totalWeight > 0
|
|
59
|
+
? weights.map(weight => weight / totalWeight)
|
|
60
|
+
: weights.map(() => 1 / Math.max(columnCount, 1));
|
|
61
|
+
})();
|
|
62
|
+
for (const [index, columnBlock] of columns.entries()) {
|
|
63
|
+
columnCountById.set(columnBlock.id, columnCount);
|
|
64
|
+
normalizedColumnRatioById.set(columnBlock.id, normalizedRatios[index]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
24
67
|
// This runs when notion-to-md encounters a column block
|
|
25
68
|
function notionColumnToMarkdown(notionToMarkdown, getBlockChildren, block) {
|
|
26
69
|
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
-
|
|
28
|
-
const { id, has_children } = block; // "any" because the notion api type system is complex with a union that don't know how to help TS to cope with
|
|
29
|
-
if (!has_children)
|
|
70
|
+
if (!isColumnBlock(block) || !block.has_children)
|
|
30
71
|
return "";
|
|
31
|
-
const columnChildren = yield getBlockChildren(id);
|
|
72
|
+
const columnChildren = yield getBlockChildren(block.id);
|
|
32
73
|
const childrenMdBlocksArray = [];
|
|
33
74
|
for (const child of columnChildren) {
|
|
34
75
|
// Intentionally serialize these subtree conversions. notion-to-md will fetch
|
|
@@ -37,38 +78,20 @@ function notionColumnToMarkdown(notionToMarkdown, getBlockChildren, block) {
|
|
|
37
78
|
childrenMdBlocksArray.push(yield notionToMarkdown.blocksToMarkdown([child]));
|
|
38
79
|
}
|
|
39
80
|
const childrenMarkdown = childrenMdBlocksArray.map(mdBlockArray => notionToMarkdown.toMarkdownString(mdBlockArray).parent);
|
|
40
|
-
const columnWidth =
|
|
81
|
+
const columnWidth = getColumnWidth(block);
|
|
41
82
|
return (`<div class='notion-column' style={{width: '${columnWidth}'}}>\n\n${childrenMarkdown.join("\n")}\n</div>` +
|
|
42
83
|
// Spacer between columns. CSS takes care of hiding this for the last column
|
|
43
84
|
// and when the screen is too narrow for multiple columns.
|
|
44
85
|
`<div className='notion-spacer'></div>`);
|
|
45
86
|
});
|
|
46
87
|
}
|
|
47
|
-
// The official API doesn't give us access to the format information, including column_ratio.
|
|
48
|
-
// So we use 'notion-client' which uses the unofficial API.
|
|
49
|
-
// Once the official API gives us access to the format information, we can remove this
|
|
50
|
-
// and the 'notion-client' dependency.
|
|
51
|
-
// This logic was mostly taken from react-notion-x (sister project of notion-client).
|
|
52
88
|
function getColumnWidth(block) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const blockResult = recordMap.block[blockId];
|
|
62
|
-
// ENHANCE: could we use https://github.com/NotionX/react-notion-x/tree/master/packages/notion-types
|
|
63
|
-
// to get away from "any", which might be particularly helpful in the future
|
|
64
|
-
// since this is using the unofficial (reverse engineered?) API.
|
|
65
|
-
const columnFormat = (_a = blockResult === null || blockResult === void 0 ? void 0 : blockResult.value) === null || _a === void 0 ? void 0 : _a.format;
|
|
66
|
-
const columnRatio = (columnFormat === null || columnFormat === void 0 ? void 0 : columnFormat.column_ratio) || 0.5;
|
|
67
|
-
const parentBlock = (_c = recordMap.block[(_b = blockResult === null || blockResult === void 0 ? void 0 : blockResult.value) === null || _b === void 0 ? void 0 : _b.parent_id]) === null || _c === void 0 ? void 0 : _c.value;
|
|
68
|
-
// I'm not sure why we wouldn't get a parent, but the react-notion-x has
|
|
69
|
-
// this fallback to a guess based on the columnRatio.
|
|
70
|
-
const columnCount = ((_d = parentBlock === null || parentBlock === void 0 ? void 0 : parentBlock.content) === null || _d === void 0 ? void 0 : _d.length) || Math.max(2, Math.ceil(1.0 / columnRatio));
|
|
71
|
-
const spacerWidth = `min(32px, 4vw)`; // This matches the value in css for 'notion-spacer'.
|
|
72
|
-
return `calc((100% - (${spacerWidth} * ${columnCount - 1})) * ${columnRatio})`;
|
|
73
|
-
});
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const columnRatio = (_b = (_a = normalizedColumnRatioById.get(block.id)) !== null && _a !== void 0 ? _a : getRawColumnRatio(block)) !== null && _b !== void 0 ? _b : 0.5;
|
|
91
|
+
// The spacer width depends on how many sibling columns are present. We record
|
|
92
|
+
// that when the parent column_list is converted, then fall back to the older
|
|
93
|
+
// estimate when a column is converted without that context.
|
|
94
|
+
const columnCount = columnCountById.get(block.id) || Math.max(2, Math.ceil(1.0 / columnRatio));
|
|
95
|
+
const spacerWidth = `min(32px, 4vw)`; // This matches the value in css for 'notion-spacer'.
|
|
96
|
+
return `calc((100% - (${spacerWidth} * ${columnCount - 1})) * ${columnRatio})`;
|
|
74
97
|
}
|
|
@@ -37,6 +37,90 @@ function getResults(children) {
|
|
|
37
37
|
}
|
|
38
38
|
const columnWrapperStart = "<div class='notion-column' style=\\{\\{width: '.*?'\\}\\}>\\n\\n";
|
|
39
39
|
const columnWrapperEnd = "\\n\\n<\\/div><div className='notion-spacer'><\\/div>";
|
|
40
|
+
test("getColumnWidth preserves docs-compliant normalized ratios", () => {
|
|
41
|
+
(0, ColumnTransformer_1.rememberColumnListChildren)([
|
|
42
|
+
{
|
|
43
|
+
id: "column-1",
|
|
44
|
+
type: "column",
|
|
45
|
+
column: {
|
|
46
|
+
width_ratio: 0.25,
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "column-2",
|
|
51
|
+
type: "column",
|
|
52
|
+
column: {
|
|
53
|
+
width_ratio: 0.75,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
const width = (0, ColumnTransformer_1.getColumnWidth)({
|
|
58
|
+
id: "column-1",
|
|
59
|
+
type: "column",
|
|
60
|
+
column: {
|
|
61
|
+
width_ratio: 0.25,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
expect(width).toBe("calc((100% - (min(32px, 4vw) * 1)) * 0.25)");
|
|
65
|
+
});
|
|
66
|
+
test("getColumnWidth normalizes missing ratios as equal weights", () => {
|
|
67
|
+
(0, ColumnTransformer_1.rememberColumnListChildren)([
|
|
68
|
+
{
|
|
69
|
+
id: "column-1",
|
|
70
|
+
type: "column",
|
|
71
|
+
column: {},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "column-2",
|
|
75
|
+
type: "column",
|
|
76
|
+
column: {},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "column-3",
|
|
80
|
+
type: "column",
|
|
81
|
+
column: {},
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
const width = (0, ColumnTransformer_1.getColumnWidth)({
|
|
85
|
+
id: "column-2",
|
|
86
|
+
type: "column",
|
|
87
|
+
column: {},
|
|
88
|
+
});
|
|
89
|
+
expect(width).toBe("calc((100% - (min(32px, 4vw) * 2)) * 0.3333333333333333)");
|
|
90
|
+
});
|
|
91
|
+
test("getColumnWidth normalizes mixed explicit and missing ratios", () => {
|
|
92
|
+
(0, ColumnTransformer_1.rememberColumnListChildren)([
|
|
93
|
+
{
|
|
94
|
+
id: "column-1",
|
|
95
|
+
type: "column",
|
|
96
|
+
column: {
|
|
97
|
+
width_ratio: 1,
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: "column-2",
|
|
102
|
+
type: "column",
|
|
103
|
+
column: {
|
|
104
|
+
width_ratio: 0.375,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "column-3",
|
|
109
|
+
type: "column",
|
|
110
|
+
column: {
|
|
111
|
+
width_ratio: 1,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
]);
|
|
115
|
+
const width = (0, ColumnTransformer_1.getColumnWidth)({
|
|
116
|
+
id: "column-2",
|
|
117
|
+
type: "column",
|
|
118
|
+
column: {
|
|
119
|
+
width_ratio: 0.375,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
expect(width).toBe("calc((100% - (min(32px, 4vw) * 2)) * 0.15789473684210525)");
|
|
123
|
+
});
|
|
40
124
|
if (runTestsWhichRequireAnyValidApiKey) {
|
|
41
125
|
columnBlock.has_children = true;
|
|
42
126
|
test("requires API key - column with paragraph", () => __awaiter(void 0, void 0, void 0, function* () {
|