@sillsdev/docu-notion 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -0
  3. package/dist/FlatGuidLayoutStrategy.d.ts +6 -0
  4. package/dist/FlatGuidLayoutStrategy.js +25 -0
  5. package/dist/HierarchicalNamedLayoutStrategy.d.ts +7 -0
  6. package/dist/HierarchicalNamedLayoutStrategy.js +80 -0
  7. package/dist/LayoutStrategy.d.ts +12 -0
  8. package/dist/LayoutStrategy.js +83 -0
  9. package/dist/MakeImagePersistencePlan.d.ts +2 -0
  10. package/dist/MakeImagePersistencePlan.js +66 -0
  11. package/dist/NotionImage-CaptionReading.spec.d.ts +1 -0
  12. package/dist/NotionImage-CaptionReading.spec.js +233 -0
  13. package/dist/NotionPage.d.ts +44 -0
  14. package/dist/NotionPage.js +194 -0
  15. package/dist/config/configuration.d.ts +5 -0
  16. package/dist/config/configuration.js +86 -0
  17. package/dist/config/default.docunotion.config.d.ts +3 -0
  18. package/dist/config/default.docunotion.config.js +37 -0
  19. package/dist/images.d.ts +24 -0
  20. package/dist/images.js +230 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +37 -0
  23. package/dist/log.d.ts +11 -0
  24. package/dist/log.js +61 -0
  25. package/dist/makeImagePersistencePlan.spec.d.ts +1 -0
  26. package/dist/makeImagePersistencePlan.spec.js +35 -0
  27. package/dist/notion-styles.css +58 -0
  28. package/dist/plugins/CalloutTransformer.d.ts +24 -0
  29. package/dist/plugins/CalloutTransformer.js +88 -0
  30. package/dist/plugins/CalloutTransformer.spec.d.ts +1 -0
  31. package/dist/plugins/CalloutTransformer.spec.js +199 -0
  32. package/dist/plugins/ColumnListTransformer.d.ts +2 -0
  33. package/dist/plugins/ColumnListTransformer.js +34 -0
  34. package/dist/plugins/ColumnTransformer.d.ts +2 -0
  35. package/dist/plugins/ColumnTransformer.js +67 -0
  36. package/dist/plugins/EscapeHtmlBlockModifier.d.ts +2 -0
  37. package/dist/plugins/EscapeHtmlBlockModifier.js +41 -0
  38. package/dist/plugins/EscapeHtmlBlockModifier.spec.d.ts +1 -0
  39. package/dist/plugins/EscapeHtmlBlockModifier.spec.js +130 -0
  40. package/dist/plugins/HeadingTranformer.spec.d.ts +1 -0
  41. package/dist/plugins/HeadingTranformer.spec.js +46 -0
  42. package/dist/plugins/HeadingTransformer.d.ts +2 -0
  43. package/dist/plugins/HeadingTransformer.js +63 -0
  44. package/dist/plugins/NumberedListTransformer.d.ts +2 -0
  45. package/dist/plugins/NumberedListTransformer.js +55 -0
  46. package/dist/plugins/NumberedListTransformer.spec.d.ts +1 -0
  47. package/dist/plugins/NumberedListTransformer.spec.js +86 -0
  48. package/dist/plugins/TableTransformer.d.ts +5 -0
  49. package/dist/plugins/TableTransformer.js +70 -0
  50. package/dist/plugins/embedTweaks.d.ts +5 -0
  51. package/dist/plugins/embedTweaks.js +46 -0
  52. package/dist/plugins/embedTweaks.spec.d.ts +1 -0
  53. package/dist/plugins/embedTweaks.spec.js +230 -0
  54. package/dist/plugins/externalLinks.d.ts +2 -0
  55. package/dist/plugins/externalLinks.js +26 -0
  56. package/dist/plugins/externalLinks.spec.d.ts +1 -0
  57. package/dist/plugins/externalLinks.spec.js +132 -0
  58. package/dist/plugins/internalLinks.d.ts +6 -0
  59. package/dist/plugins/internalLinks.js +78 -0
  60. package/dist/plugins/internalLinks.spec.d.ts +1 -0
  61. package/dist/plugins/internalLinks.spec.js +442 -0
  62. package/dist/plugins/pluginTestRun.d.ts +10 -0
  63. package/dist/plugins/pluginTestRun.js +248 -0
  64. package/dist/plugins/pluginTypes.d.ts +42 -0
  65. package/dist/plugins/pluginTypes.js +2 -0
  66. package/dist/pull.d.ts +12 -0
  67. package/dist/pull.js +253 -0
  68. package/dist/run.d.ts +1 -0
  69. package/dist/run.js +35 -0
  70. package/dist/transform.d.ts +6 -0
  71. package/dist/transform.js +195 -0
  72. package/dist/types.d.ts +8 -0
  73. package/dist/types.js +2 -0
  74. package/package.json +96 -0
@@ -0,0 +1,6 @@
1
+ import { IPlugin } from "./pluginTypes";
2
+ export declare function parseLinkId(fullLinkId: string): {
3
+ baseLinkId: string;
4
+ fragmentId: string;
5
+ };
6
+ export declare const standardInternalLinkConversion: IPlugin;
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.standardInternalLinkConversion = exports.parseLinkId = void 0;
4
+ const log_1 = require("../log");
5
+ function convertInternalLink(context, markdownLink) {
6
+ const linkRegExp = /\[([^\]]+)?\]\(\/?([^),^/]+)\)/g;
7
+ const match = linkRegExp.exec(markdownLink);
8
+ if (match === null) {
9
+ (0, log_1.warning)(`[standardInternalLinkConversion] Could not parse link ${markdownLink}`);
10
+ return markdownLink;
11
+ }
12
+ const labelFromNotion = match[1] || "";
13
+ const hrefFromNotion = match[2];
14
+ // verbose(
15
+ // `[standardInternalLinkConversion] Converting ${markdownLink} with has url ${hrefFromNotion}`
16
+ // );
17
+ const pages = context.pages;
18
+ // find the page where pageId matches hrefFromNotion
19
+ const targetPage = pages.find(p => {
20
+ return p.matchesLinkId(hrefFromNotion);
21
+ });
22
+ if (!targetPage) {
23
+ // About this situation. See https://github.com/sillsdev/docu-notion/issues/9
24
+ (0, log_1.warning)(`[standardInternalLinkConversion] Could not find the target of this link. Note that links to outline sections are not supported. ${markdownLink}. https://github.com/sillsdev/docu-notion/issues/9`);
25
+ return "**[Problem Internal Link]**";
26
+ }
27
+ const label = convertLinkLabel(targetPage, labelFromNotion);
28
+ const url = convertLinkHref(context, targetPage, hrefFromNotion);
29
+ return `[${label}](${url})`;
30
+ }
31
+ function convertLinkLabel(targetPage, text) {
32
+ // In Notion, if you just add a link to a page without linking it to any text, then in Notion
33
+ // you see the name of the page as the text of the link. But when Notion gives us that same
34
+ // link, it uses "link_to_page" as the text. So we have to look up the name of the page in
35
+ // order to fix that.;
36
+ if (text !== "link_to_page")
37
+ return text;
38
+ else
39
+ return targetPage.nameOrTitle;
40
+ }
41
+ function convertLinkHref(context, targetPage, url) {
42
+ let convertedLink = context.layoutStrategy.getLinkPathForPage(targetPage);
43
+ /*****************************
44
+ NOTE: as of this writing, the official Notion API completely drops links
45
+ to headings, unless they are part of a inline link.
46
+ *******************************/
47
+ // Include the fragment (# and after) if it exists
48
+ const { fragmentId } = parseLinkId(url);
49
+ //verbose(`Parsed ${url} and got Fragment ID: ${fragmentId}`);
50
+ convertedLink += fragmentId;
51
+ //verbose(`Converting Link ${url} --> ${convertedLink}`);
52
+ return convertedLink;
53
+ }
54
+ // Parse the link ID to get the base (before the #) and the fragment (# and after).
55
+ function parseLinkId(fullLinkId) {
56
+ const iHash = fullLinkId.indexOf("#");
57
+ if (iHash >= 0) {
58
+ return {
59
+ baseLinkId: fullLinkId.substring(0, iHash),
60
+ fragmentId: fullLinkId.substring(iHash),
61
+ };
62
+ }
63
+ return { baseLinkId: fullLinkId, fragmentId: "" };
64
+ }
65
+ exports.parseLinkId = parseLinkId;
66
+ exports.standardInternalLinkConversion = {
67
+ name: "standard internal link conversion",
68
+ linkModifier: {
69
+ // from notion (or notion-md?) we get slightly different hrefs depending on whether the links is "inline"
70
+ // (has some other text that's been turned into a link) or "raw".
71
+ // Raw links come in without a leading slash, e.g. [link_to_page](4a6de8c0-b90b-444b-8a7b-d534d6ec71a4)
72
+ // Inline links come in with a leading slash, e.g. [pointer to the introduction](/4a6de8c0b90b444b8a7bd534d6ec71a4)
73
+ // we only want the inline ones for this plugin
74
+ // review: currently we expect that internal links have an opening slash, but at one point it was optional.
75
+ match: /\[([^\]]+)?\]\(\/([^),^/]+)\)/,
76
+ convert: convertInternalLink,
77
+ },
78
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,442 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const log_1 = require("../log");
13
+ const pluginTestRun_1 = require("./pluginTestRun");
14
+ const CalloutTransformer_1 = require("./CalloutTransformer");
15
+ const externalLinks_1 = require("./externalLinks");
16
+ const internalLinks_1 = require("./internalLinks");
17
+ const NumberedListTransformer_1 = require("./NumberedListTransformer");
18
+ test("urls that show up as raw text get left that way", () => __awaiter(void 0, void 0, void 0, function* () {
19
+ const results = yield getMarkdown({
20
+ type: "paragraph",
21
+ paragraph: {
22
+ rich_text: [
23
+ {
24
+ type: "text",
25
+ text: { content: "https://github.com", link: null },
26
+ annotations: {
27
+ bold: false,
28
+ italic: false,
29
+ strikethrough: false,
30
+ underline: false,
31
+ code: false,
32
+ color: "default",
33
+ },
34
+ plain_text: "https://github.com",
35
+ href: null,
36
+ },
37
+ ],
38
+ },
39
+ });
40
+ expect(results.trim()).toBe("https://github.com");
41
+ }));
42
+ test("link to an existing page on this site that has no slug", () => __awaiter(void 0, void 0, void 0, function* () {
43
+ const targetPageId = "123";
44
+ const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
45
+ slug: undefined,
46
+ name: "Hello World",
47
+ id: targetPageId,
48
+ });
49
+ const results = yield getMarkdown({
50
+ type: "paragraph",
51
+ paragraph: {
52
+ rich_text: [
53
+ {
54
+ type: "text",
55
+ text: { content: "Inline ", link: null },
56
+ annotations: {
57
+ bold: false,
58
+ italic: false,
59
+ strikethrough: false,
60
+ underline: false,
61
+ code: false,
62
+ color: "default",
63
+ },
64
+ plain_text: "Inline ",
65
+ href: null,
66
+ },
67
+ {
68
+ type: "text",
69
+ text: {
70
+ content: "great page",
71
+ link: { url: `/${targetPageId}` },
72
+ },
73
+ annotations: {
74
+ bold: false,
75
+ italic: false,
76
+ strikethrough: false,
77
+ underline: false,
78
+ code: false,
79
+ color: "default",
80
+ },
81
+ plain_text: "great page",
82
+ href: `/${targetPageId}`,
83
+ },
84
+ {
85
+ type: "text",
86
+ text: { content: " the end.", link: null },
87
+ annotations: {
88
+ bold: false,
89
+ italic: false,
90
+ strikethrough: false,
91
+ underline: false,
92
+ code: false,
93
+ color: "default",
94
+ },
95
+ plain_text: " the end.",
96
+ href: null,
97
+ },
98
+ ],
99
+ color: "default",
100
+ },
101
+ }, targetPage);
102
+ expect(results.trim()).toBe(`Inline [great page](/${targetPageId}) the end.`);
103
+ }));
104
+ test("link to a heading block on a page", () => __awaiter(void 0, void 0, void 0, function* () {
105
+ const targetPageId = "123";
106
+ const blocks = {
107
+ type: "paragraph",
108
+ paragraph: {
109
+ rich_text: [
110
+ {
111
+ type: "text",
112
+ text: { content: "(Inline ", link: null },
113
+ annotations: {
114
+ bold: false,
115
+ italic: false,
116
+ strikethrough: false,
117
+ underline: false,
118
+ code: false,
119
+ color: "default",
120
+ },
121
+ plain_text: "(Inline ",
122
+ href: null,
123
+ },
124
+ {
125
+ type: "text",
126
+ text: {
127
+ content: "heading on some page",
128
+ link: { url: `/${targetPageId}#456` },
129
+ },
130
+ annotations: {
131
+ bold: false,
132
+ italic: false,
133
+ strikethrough: false,
134
+ underline: false,
135
+ code: false,
136
+ color: "default",
137
+ },
138
+ plain_text: "heading on some page",
139
+ href: `/${targetPageId}#456`,
140
+ },
141
+ {
142
+ type: "text",
143
+ text: { content: " the end.)", link: null },
144
+ annotations: {
145
+ bold: false,
146
+ italic: false,
147
+ strikethrough: false,
148
+ underline: false,
149
+ code: false,
150
+ color: "default",
151
+ },
152
+ plain_text: " the end.)",
153
+ href: null,
154
+ },
155
+ ],
156
+ color: "default",
157
+ },
158
+ };
159
+ (0, log_1.setLogLevel)("verbose");
160
+ const noSlugTargetPage = (0, pluginTestRun_1.makeSamplePageObject)({
161
+ slug: undefined,
162
+ name: "Hello World",
163
+ id: targetPageId,
164
+ });
165
+ const noSlugResults = yield getMarkdown(blocks, noSlugTargetPage);
166
+ // the ending parentheses messed up a regex at one point.
167
+ expect(noSlugResults.trim()).toBe(`(Inline [heading on some page](/${targetPageId}#456) the end.)`);
168
+ const slugTargetPage = (0, pluginTestRun_1.makeSamplePageObject)({
169
+ slug: "hello-world",
170
+ name: "Hello World",
171
+ id: targetPageId,
172
+ });
173
+ const slugResults = yield getMarkdown(blocks, slugTargetPage);
174
+ expect(slugResults.trim()).toBe(`(Inline [heading on some page](/hello-world#456) the end.)`);
175
+ }));
176
+ test("link to an existing page on this site uses slug", () => __awaiter(void 0, void 0, void 0, function* () {
177
+ const targetPageId = "123";
178
+ const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
179
+ slug: "hello-world",
180
+ name: "Hello World",
181
+ id: targetPageId,
182
+ });
183
+ const results = yield getMarkdown({
184
+ type: "paragraph",
185
+ paragraph: {
186
+ rich_text: [
187
+ {
188
+ type: "text",
189
+ text: { content: "Inline ", link: null },
190
+ annotations: {
191
+ bold: false,
192
+ italic: false,
193
+ strikethrough: false,
194
+ underline: false,
195
+ code: false,
196
+ color: "default",
197
+ },
198
+ plain_text: "Inline ",
199
+ href: null,
200
+ },
201
+ {
202
+ type: "text",
203
+ text: {
204
+ content: "It’s good",
205
+ link: { url: `/${targetPageId}` },
206
+ },
207
+ annotations: {
208
+ bold: false,
209
+ italic: false,
210
+ strikethrough: false,
211
+ underline: false,
212
+ code: false,
213
+ color: "default",
214
+ },
215
+ plain_text: "It’s good",
216
+ href: `/${targetPageId}`,
217
+ },
218
+ ],
219
+ color: "default",
220
+ },
221
+ }, targetPage);
222
+ expect(results.trim()).toBe("Inline [It’s good](/hello-world)");
223
+ }));
224
+ test("link in a bulleted list", () => __awaiter(void 0, void 0, void 0, function* () {
225
+ const targetPageId = "123";
226
+ const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
227
+ slug: "the-page",
228
+ name: "Something",
229
+ id: targetPageId,
230
+ });
231
+ const results = yield getMarkdown({
232
+ type: "bulleted_list_item",
233
+ bulleted_list_item: {
234
+ rich_text: [
235
+ {
236
+ type: "text",
237
+ text: {
238
+ content: "item",
239
+ link: { url: "/123" },
240
+ },
241
+ annotations: {
242
+ bold: false,
243
+ italic: false,
244
+ strikethrough: false,
245
+ underline: false,
246
+ code: false,
247
+ color: "default",
248
+ },
249
+ plain_text: "item",
250
+ href: "/123",
251
+ },
252
+ ],
253
+ color: "default",
254
+ },
255
+ }, targetPage);
256
+ expect(results.trim()).toBe("- [item](/the-page)");
257
+ }));
258
+ test("link to an a heading on a page on this site uses slug", () => __awaiter(void 0, void 0, void 0, function* () {
259
+ const targetPageId = "123";
260
+ const headingBlockId = "456";
261
+ const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
262
+ slug: "hello-world",
263
+ name: "Hello World",
264
+ id: targetPageId,
265
+ });
266
+ const results = yield getMarkdown({
267
+ type: "paragraph",
268
+ paragraph: {
269
+ rich_text: [
270
+ {
271
+ type: "text",
272
+ text: {
273
+ content: "see this heading",
274
+ link: { url: `/${targetPageId}#${headingBlockId}` },
275
+ },
276
+ annotations: {
277
+ bold: false,
278
+ italic: false,
279
+ strikethrough: false,
280
+ underline: false,
281
+ code: false,
282
+ color: "default",
283
+ },
284
+ plain_text: "see this heading",
285
+ href: `/${targetPageId}#${headingBlockId}`,
286
+ },
287
+ ],
288
+ color: "default",
289
+ },
290
+ }, targetPage);
291
+ expect(results.trim()).toBe(`[see this heading](/hello-world#${headingBlockId})`);
292
+ }));
293
+ test("does not interfere with mailto links", () => __awaiter(void 0, void 0, void 0, function* () {
294
+ const results = yield getMarkdown({
295
+ type: "paragraph",
296
+ paragraph: {
297
+ rich_text: [
298
+ {
299
+ type: "text",
300
+ text: {
301
+ content: "mailme",
302
+ link: { url: `mailto:foo@example.com` },
303
+ },
304
+ annotations: {
305
+ bold: false,
306
+ italic: false,
307
+ strikethrough: false,
308
+ underline: false,
309
+ code: false,
310
+ color: "default",
311
+ },
312
+ plain_text: "mailme",
313
+ href: `mailto:foo@example.com`,
314
+ },
315
+ ],
316
+ color: "default",
317
+ },
318
+ });
319
+ expect(results.trim()).toBe(`[mailme](mailto:foo@example.com)`);
320
+ }));
321
+ test("links to other notion pages that are not in this site give PROBLEM LINK", () => __awaiter(void 0, void 0, void 0, function* () {
322
+ const results = yield getMarkdown({
323
+ type: "paragraph",
324
+ paragraph: {
325
+ rich_text: [
326
+ {
327
+ type: "text",
328
+ text: { content: "Inline ", link: null },
329
+ annotations: {
330
+ bold: false,
331
+ italic: false,
332
+ strikethrough: false,
333
+ underline: false,
334
+ code: false,
335
+ color: "default",
336
+ },
337
+ plain_text: "Inline ",
338
+ href: null,
339
+ },
340
+ {
341
+ type: "text",
342
+ text: {
343
+ content: "links page",
344
+ link: { url: "/pretendidofpagewedonothaveinthissite" },
345
+ },
346
+ annotations: {
347
+ bold: false,
348
+ italic: false,
349
+ strikethrough: false,
350
+ underline: false,
351
+ code: false,
352
+ color: "default",
353
+ },
354
+ plain_text: "links page",
355
+ href: "/pretendidofpagewedonothaveinthissite",
356
+ },
357
+ ],
358
+ color: "default",
359
+ },
360
+ });
361
+ expect(results.trim()).toBe("Inline **[Problem Internal Link]**");
362
+ }));
363
+ test("internal link inside callout", () => __awaiter(void 0, void 0, void 0, function* () {
364
+ const targetPageId = "123";
365
+ const targetPage = (0, pluginTestRun_1.makeSamplePageObject)({
366
+ slug: "hello-world",
367
+ name: "Hello World",
368
+ id: targetPageId,
369
+ });
370
+ const results = yield getMarkdown({
371
+ type: "callout",
372
+ callout: {
373
+ rich_text: [
374
+ {
375
+ type: "text",
376
+ text: { content: "Callouts inline ", link: null },
377
+ annotations: {
378
+ bold: false,
379
+ italic: false,
380
+ strikethrough: false,
381
+ underline: false,
382
+ code: false,
383
+ color: "default",
384
+ },
385
+ plain_text: "Callouts inline ",
386
+ href: null,
387
+ },
388
+ {
389
+ type: "text",
390
+ text: {
391
+ content: "great page",
392
+ link: { url: `/${targetPageId}` },
393
+ },
394
+ annotations: {
395
+ bold: false,
396
+ italic: false,
397
+ strikethrough: false,
398
+ underline: false,
399
+ code: false,
400
+ color: "default",
401
+ },
402
+ plain_text: "great page",
403
+ href: `/${targetPageId}`,
404
+ },
405
+ {
406
+ type: "text",
407
+ text: { content: ".", link: null },
408
+ annotations: {
409
+ bold: false,
410
+ italic: false,
411
+ strikethrough: false,
412
+ underline: false,
413
+ code: false,
414
+ color: "default",
415
+ },
416
+ plain_text: ".",
417
+ href: null,
418
+ },
419
+ ],
420
+ icon: { type: "emoji", emoji: "⚠️" },
421
+ color: "gray_background",
422
+ },
423
+ }, targetPage);
424
+ expect(results.trim()).toBe(`:::caution
425
+
426
+ Callouts inline [great page](/hello-world).
427
+
428
+ :::`);
429
+ }));
430
+ function getMarkdown(block, targetPage) {
431
+ return __awaiter(this, void 0, void 0, function* () {
432
+ const config = {
433
+ plugins: [
434
+ CalloutTransformer_1.standardCalloutTransformer,
435
+ NumberedListTransformer_1.standardNumberedListTransformer,
436
+ internalLinks_1.standardInternalLinkConversion,
437
+ externalLinks_1.standardExternalLinkConversion,
438
+ ],
439
+ };
440
+ return yield (0, pluginTestRun_1.oneBlockToMarkdown)(config, block, targetPage);
441
+ });
442
+ }
@@ -0,0 +1,10 @@
1
+ import { NotionPage } from "../NotionPage";
2
+ import { IDocuNotionConfig } from "../config/configuration";
3
+ import { NotionBlock } from "../types";
4
+ export declare function blocksToMarkdown(config: IDocuNotionConfig, blocks: NotionBlock[], pages?: NotionPage[]): Promise<string>;
5
+ export declare function makeSamplePageObject(options: {
6
+ slug?: string;
7
+ name?: string;
8
+ id?: string;
9
+ }): NotionPage;
10
+ export declare function oneBlockToMarkdown(config: IDocuNotionConfig, block: object, targetPage?: NotionPage): Promise<string>;