@sillsdev/docu-notion 1.0.0-alpha.5 → 1.0.0-alpha.7
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
CHANGED
|
@@ -58,7 +58,7 @@ means that the ID is `0456aa5842946PRETEND4f37c97a0e5`.
|
|
|
58
58
|
Try it out:
|
|
59
59
|
|
|
60
60
|
```
|
|
61
|
-
npx @sillsdev/docu-notion -n secret_PRETEND123456789PRETEND123456789PRETEND6789 -r 0456aa5842946PRETEND4f37c97a0e5
|
|
61
|
+
npx @sillsdev/docu-notion -n secret_PRETEND123456789PRETEND123456789PRETEND6789 -r 0456aa5842946PRETEND4f37c97a0e5
|
|
62
62
|
```
|
|
63
63
|
|
|
64
64
|
Likely, you will want to store these codes in your environment variables and then use them like this:
|
|
@@ -6,8 +6,26 @@ exports.gifEmbed = {
|
|
|
6
6
|
regexMarkdownModifications: [
|
|
7
7
|
{
|
|
8
8
|
// I once saw a gif coming from Notion that wasn't a full
|
|
9
|
-
// url, which wouldn't work, hence the "http" requirement
|
|
10
|
-
|
|
9
|
+
// url, which wouldn't work, hence the "http" requirement.
|
|
10
|
+
//
|
|
11
|
+
// We only embed when the link's text is an AUTO-GENERATED label, not
|
|
12
|
+
// something the author typed. By the time this regex runs the label can
|
|
13
|
+
// take three auto shapes:
|
|
14
|
+
// - empty `[]` (an embed)
|
|
15
|
+
// - the literal `[bookmark]` placeholder notion-to-md emits for
|
|
16
|
+
// bookmark blocks (seen when standardExternalLinkConversion isn't in
|
|
17
|
+
// the config, e.g. the unit tests)
|
|
18
|
+
// - the URL repeated as the label, `[http...gif](http...gif)`, which is
|
|
19
|
+
// what standardExternalLinkConversion rewrites `[bookmark]` into in
|
|
20
|
+
// the production config (it runs in an earlier phase, see transform.ts)
|
|
21
|
+
// A link the author gave real text, e.g. `[see animation](...gif)`, is
|
|
22
|
+
// none of these, so it's left as a clickable link rather than turned into
|
|
23
|
+
// an image (which would also throw the text away).
|
|
24
|
+
//
|
|
25
|
+
// The optional leading "!" lets us match (and replace) a link that
|
|
26
|
+
// another mod has already turned into an image, instead of prepending
|
|
27
|
+
// a second "!" and producing "!".
|
|
28
|
+
regex: /!?\[(?:bookmark|https?:\/\/[^\]]*\.(?:gif|GIF)[^\]]*)?\]\((http.*?(\.(gif|GIF)))\)/,
|
|
11
29
|
replacementPattern: ``,
|
|
12
30
|
},
|
|
13
31
|
],
|
|
@@ -16,7 +34,14 @@ exports.imgurGifEmbed = {
|
|
|
16
34
|
name: "imgur",
|
|
17
35
|
regexMarkdownModifications: [
|
|
18
36
|
{
|
|
19
|
-
|
|
37
|
+
// Only embed links whose text is an auto-generated label (empty `[]`,
|
|
38
|
+
// `[bookmark]`, or the URL repeated as the label `[http...imgur.com/...]`),
|
|
39
|
+
// so that a link the author gave real text to is kept as a clickable link
|
|
40
|
+
// rather than converted to an image. See the longer note in gifEmbed above
|
|
41
|
+
// for why all three label shapes can occur here.
|
|
42
|
+
// The optional leading "!" lets us match a link that's already an image
|
|
43
|
+
// without prepending a second "!".
|
|
44
|
+
regex: /!?\[(?:bookmark|https?:\/\/[^\]]*imgur\.com\/[^\]]*)?\]\((.*?imgur\.com\/.*?)\)/, // imgur.com
|
|
20
45
|
// imgur links to gifs need a .gif at the end, but the url they give you doesn't have one.
|
|
21
46
|
replacementPattern: ``,
|
|
22
47
|
},
|
|
@@ -8,10 +8,38 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
const log_1 = require("../log");
|
|
13
16
|
const pluginTestRun_1 = require("./pluginTestRun");
|
|
14
17
|
const embedTweaks_1 = require("./embedTweaks");
|
|
18
|
+
const externalLinks_1 = require("./externalLinks");
|
|
19
|
+
const default_docunotion_config_1 = __importDefault(require("../config/default.docunotion.config"));
|
|
20
|
+
function paragraph(text) {
|
|
21
|
+
return {
|
|
22
|
+
type: "paragraph",
|
|
23
|
+
paragraph: {
|
|
24
|
+
rich_text: [
|
|
25
|
+
{
|
|
26
|
+
type: "text",
|
|
27
|
+
text: { content: text, link: null },
|
|
28
|
+
annotations: {
|
|
29
|
+
bold: false,
|
|
30
|
+
italic: false,
|
|
31
|
+
strikethrough: false,
|
|
32
|
+
underline: false,
|
|
33
|
+
code: false,
|
|
34
|
+
color: "default",
|
|
35
|
+
},
|
|
36
|
+
plain_text: text,
|
|
37
|
+
href: null,
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
15
43
|
test("imgur", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
16
44
|
(0, log_1.setLogLevel)("verbose");
|
|
17
45
|
const config = { plugins: [embedTweaks_1.imgurGifEmbed] };
|
|
@@ -25,6 +53,93 @@ test("imgur", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
25
53
|
]);
|
|
26
54
|
expect(result.trim()).toBe(``);
|
|
27
55
|
}));
|
|
56
|
+
// Regression test for the "!" bug: with both plugins active (the
|
|
57
|
+
// default config order), the imgur mod turns a bare imgur link into an image,
|
|
58
|
+
// then the gif mod sees that ".gif" image and must not prepend a second "!".
|
|
59
|
+
test("imgur + gif together produce a single leading bang", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
60
|
+
(0, log_1.setLogLevel)("verbose");
|
|
61
|
+
const config = { plugins: [embedTweaks_1.imgurGifEmbed, embedTweaks_1.gifEmbed] };
|
|
62
|
+
const result = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
63
|
+
{
|
|
64
|
+
object: "block",
|
|
65
|
+
id: "e36710d8-98ad-40dc-b41b-b376ebdd6894",
|
|
66
|
+
type: "bookmark",
|
|
67
|
+
bookmark: { caption: [], url: "https://imgur.com/E83qLj6" },
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
expect(result.trim()).toBe(``);
|
|
71
|
+
expect(result).not.toContain("!![]");
|
|
72
|
+
}));
|
|
73
|
+
// An inline link the user gave real text to must stay a clickable link: we must
|
|
74
|
+
// not turn it into an image (which also discards the text). Uses the default
|
|
75
|
+
// config order so both mods get a crack at it.
|
|
76
|
+
test("imgur link with author text is left as a clickable link", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
|
+
(0, log_1.setLogLevel)("verbose");
|
|
78
|
+
const config = { plugins: [embedTweaks_1.imgurGifEmbed, embedTweaks_1.gifEmbed] };
|
|
79
|
+
const result = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
80
|
+
paragraph("all at once ([see animation](https://imgur.com/gcrxl5k))."),
|
|
81
|
+
paragraph("(See an animation of these [new overlay features](https://imgur.com/E83qLj6))"),
|
|
82
|
+
]);
|
|
83
|
+
expect(result).toContain("[see animation](https://imgur.com/gcrxl5k)");
|
|
84
|
+
expect(result).toContain("[new overlay features](https://imgur.com/E83qLj6)");
|
|
85
|
+
// nothing should have been turned into an image
|
|
86
|
+
expect(result).not.toContain("![]");
|
|
87
|
+
expect(result).not.toContain(".gif");
|
|
88
|
+
}));
|
|
89
|
+
// Production-realistic: standardExternalLinkConversion runs in an earlier phase
|
|
90
|
+
// and rewrites a Notion `[bookmark](url)` into `[url](url)` before the embed
|
|
91
|
+
// regexes run. The embed must still recognize that as an auto-label and embed it.
|
|
92
|
+
test("imgur bookmark still embeds after external-link conversion", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
(0, log_1.setLogLevel)("verbose");
|
|
94
|
+
const config = {
|
|
95
|
+
plugins: [externalLinks_1.standardExternalLinkConversion, embedTweaks_1.imgurGifEmbed, embedTweaks_1.gifEmbed],
|
|
96
|
+
};
|
|
97
|
+
const result = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
98
|
+
{
|
|
99
|
+
object: "block",
|
|
100
|
+
id: "e36710d8-98ad-40dc-b41b-b376ebdd6894",
|
|
101
|
+
type: "bookmark",
|
|
102
|
+
bookmark: { caption: [], url: "https://imgur.com/gallery/U8TTNuI" },
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
expect(result.trim()).toBe(``);
|
|
106
|
+
expect(result).not.toContain("!![]");
|
|
107
|
+
}));
|
|
108
|
+
// ...but an authored inline link must survive that same pipeline as a link.
|
|
109
|
+
test("authored imgur link survives external-link conversion as a link", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
110
|
+
(0, log_1.setLogLevel)("verbose");
|
|
111
|
+
const config = {
|
|
112
|
+
plugins: [externalLinks_1.standardExternalLinkConversion, embedTweaks_1.imgurGifEmbed, embedTweaks_1.gifEmbed],
|
|
113
|
+
};
|
|
114
|
+
const result = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
|
|
115
|
+
paragraph("all at once ([see animation](https://imgur.com/gcrxl5k))."),
|
|
116
|
+
]);
|
|
117
|
+
expect(result).toContain("[see animation](https://imgur.com/gcrxl5k)");
|
|
118
|
+
expect(result).not.toContain("![]");
|
|
119
|
+
expect(result).not.toContain(".gif");
|
|
120
|
+
}));
|
|
121
|
+
// Strongest guard: run the ACTUAL default production config (full plugin set
|
|
122
|
+
// and order) rather than a hand-picked subset, so future config drift (plugin
|
|
123
|
+
// reordering, the external-link converter changing) can't silently reopen the
|
|
124
|
+
// bug. A Notion bookmark to imgur must embed; an authored inline link must stay
|
|
125
|
+
// a clickable link.
|
|
126
|
+
test("default production config: bookmark embeds, authored link stays a link", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
127
|
+
(0, log_1.setLogLevel)("verbose");
|
|
128
|
+
const result = yield (0, pluginTestRun_1.blocksToMarkdown)(default_docunotion_config_1.default, [
|
|
129
|
+
{
|
|
130
|
+
object: "block",
|
|
131
|
+
id: "e36710d8-98ad-40dc-b41b-b376ebdd6894",
|
|
132
|
+
type: "bookmark",
|
|
133
|
+
bookmark: { caption: [], url: "https://imgur.com/gallery/U8TTNuI" },
|
|
134
|
+
},
|
|
135
|
+
paragraph("all at once ([see animation](https://imgur.com/gcrxl5k))."),
|
|
136
|
+
]);
|
|
137
|
+
// the bookmark became an embedded gif...
|
|
138
|
+
expect(result).toContain("");
|
|
139
|
+
expect(result).not.toContain("!![]");
|
|
140
|
+
// ...while the authored link is untouched
|
|
141
|
+
expect(result).toContain("[see animation](https://imgur.com/gcrxl5k)");
|
|
142
|
+
}));
|
|
28
143
|
test("gif", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
29
144
|
(0, log_1.setLogLevel)("verbose");
|
|
30
145
|
const config = { plugins: [embedTweaks_1.gifEmbed] };
|
|
@@ -29,7 +29,10 @@ function normalizeLinkBaseId(baseLinkId) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
const withoutLeadingSlash = withoutQuery.replace(/^\/+/, "");
|
|
32
|
-
|
|
32
|
+
// Newer Notion links can also use a relative "/p/<page-id>" form.
|
|
33
|
+
return withoutLeadingSlash.startsWith("p/")
|
|
34
|
+
? withoutLeadingSlash.substring(2)
|
|
35
|
+
: withoutLeadingSlash;
|
|
33
36
|
}
|
|
34
37
|
// converts a url to a local link, if it is a link to a page in the Notion site
|
|
35
38
|
// only here for plugins, notion won't normally be giving us raw urls (at least not that I've noticed)
|
|
@@ -125,7 +128,7 @@ exports.standardInternalLinkConversion = {
|
|
|
125
128
|
// "Mention" links come in as full URLs, e.g. [link_to_page](https://www.notion.so/62f1187010214b0883711a1abb277d31)
|
|
126
129
|
// Newer Notion links can also use app.notion.com, including /p/<page-id> URLs.
|
|
127
130
|
// YOu can create them either with @+the name of a page, or by pasting a URL and then selecting the "Mention" option.
|
|
128
|
-
match: /\[([^\]]+)?\]\((?!mailto:)(https?:\/\/(?:www\.)?notion\.so\/[^),^/]+|https?:\/\/app\.notion\.com\/(?:p\/)?[^),^/]+|\/?[^),^/]+)\)/,
|
|
131
|
+
match: /\[([^\]]+)?\]\((?!mailto:)(https?:\/\/(?:www\.)?notion\.so\/[^),^/]+|https?:\/\/app\.notion\.com\/(?:p\/)?[^),^/]+|\/?(?:p\/)?[^),^/]+)\)/,
|
|
129
132
|
convert: convertInternalLink,
|
|
130
133
|
},
|
|
131
134
|
};
|
|
@@ -121,6 +121,16 @@ test("parseLinkId extracts the page id from app.notion.com URLs", () => {
|
|
|
121
121
|
fragmentId: "",
|
|
122
122
|
});
|
|
123
123
|
});
|
|
124
|
+
test("parseLinkId extracts the page id from relative /p/ links", () => {
|
|
125
|
+
expect((0, internalLinks_1.parseLinkId)("/p/123456781234123412341234567890ab")).toEqual({
|
|
126
|
+
baseLinkId: "123456781234123412341234567890ab",
|
|
127
|
+
fragmentId: "",
|
|
128
|
+
});
|
|
129
|
+
expect((0, internalLinks_1.parseLinkId)("/p/123456781234123412341234567890ab#heading")).toEqual({
|
|
130
|
+
baseLinkId: "123456781234123412341234567890ab",
|
|
131
|
+
fragmentId: "#heading",
|
|
132
|
+
});
|
|
133
|
+
});
|
|
124
134
|
test("link to an existing page on this site that has no slug", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
135
|
const targetPageId = "123";
|
|
126
136
|
const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
|
|
@@ -183,6 +193,68 @@ test("link to an existing page on this site that has no slug", () => __awaiter(v
|
|
|
183
193
|
}, targetPage);
|
|
184
194
|
expect(results.trim()).toBe(`Inline [great page](/${targetPageId}) the end.`);
|
|
185
195
|
}));
|
|
196
|
+
test("inline link using the newer relative /p/ form", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
+
const targetPageId = "123";
|
|
198
|
+
const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
|
|
199
|
+
slug: undefined,
|
|
200
|
+
name: "Hello World",
|
|
201
|
+
id: targetPageId,
|
|
202
|
+
});
|
|
203
|
+
const results = yield getMarkdown({
|
|
204
|
+
type: "paragraph",
|
|
205
|
+
paragraph: {
|
|
206
|
+
rich_text: [
|
|
207
|
+
{
|
|
208
|
+
type: "text",
|
|
209
|
+
text: { content: "Inline ", link: null },
|
|
210
|
+
annotations: {
|
|
211
|
+
bold: false,
|
|
212
|
+
italic: false,
|
|
213
|
+
strikethrough: false,
|
|
214
|
+
underline: false,
|
|
215
|
+
code: false,
|
|
216
|
+
color: "default",
|
|
217
|
+
},
|
|
218
|
+
plain_text: "Inline ",
|
|
219
|
+
href: null,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: "text",
|
|
223
|
+
text: {
|
|
224
|
+
content: "great page",
|
|
225
|
+
link: { url: `/p/${targetPageId}` },
|
|
226
|
+
},
|
|
227
|
+
annotations: {
|
|
228
|
+
bold: false,
|
|
229
|
+
italic: false,
|
|
230
|
+
strikethrough: false,
|
|
231
|
+
underline: false,
|
|
232
|
+
code: false,
|
|
233
|
+
color: "default",
|
|
234
|
+
},
|
|
235
|
+
plain_text: "great page",
|
|
236
|
+
href: `/p/${targetPageId}`,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
type: "text",
|
|
240
|
+
text: { content: " the end.", link: null },
|
|
241
|
+
annotations: {
|
|
242
|
+
bold: false,
|
|
243
|
+
italic: false,
|
|
244
|
+
strikethrough: false,
|
|
245
|
+
underline: false,
|
|
246
|
+
code: false,
|
|
247
|
+
color: "default",
|
|
248
|
+
},
|
|
249
|
+
plain_text: " the end.",
|
|
250
|
+
href: null,
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
color: "default",
|
|
254
|
+
},
|
|
255
|
+
}, targetPage);
|
|
256
|
+
expect(results.trim()).toBe(`Inline [great page](/${targetPageId}) the end.`);
|
|
257
|
+
}));
|
|
186
258
|
test("link to a heading block on a page", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
187
259
|
const targetPageId = "123";
|
|
188
260
|
const blocks = {
|
package/package.json
CHANGED