@sillsdev/docu-notion 0.14.0-alpha.2 → 0.14.0-alpha.4

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.
@@ -1,2 +1,3 @@
1
1
  import { ImageSet } from "./images";
2
2
  export declare function makeImagePersistencePlan(imageSet: ImageSet, imageOutputRootPath: string, imagePrefix: string): void;
3
+ export declare function hashOfString(s: string): number;
@@ -23,21 +23,21 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.makeImagePersistencePlan = void 0;
26
+ exports.hashOfString = exports.makeImagePersistencePlan = void 0;
27
27
  const Path = __importStar(require("path"));
28
28
  const log_1 = require("./log");
29
29
  const process_1 = require("process");
30
30
  function makeImagePersistencePlan(imageSet, imageOutputRootPath, imagePrefix) {
31
- var _a;
31
+ var _a, _b;
32
32
  if ((_a = imageSet.fileType) === null || _a === void 0 ? void 0 : _a.ext) {
33
33
  // Since most images come from pasting screenshots, there isn't normally a filename. That's fine, we just make a hash of the url
34
34
  // Images that are stored by notion come to us with a complex url that changes over time, so we pick out the UUID that doesn't change. Example:
35
35
  // https://s3.us-west-2.amazonaws.com/secure.notion-static.com/d1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject
36
- let thingToHash = imageSet.primaryUrl;
37
- const m = /.*secure\.notion-static\.com\/(.*)\//gm.exec(imageSet.primaryUrl);
38
- if (m && m.length > 1) {
39
- thingToHash = m[1];
40
- }
36
+ // But around Sept 2023, they changed the url to be something like:
37
+ // https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject
38
+ // The thing we want is the last UUID before the ?
39
+ const urlBeforeQuery = imageSet.primaryUrl.split("?")[0];
40
+ const thingToHash = (_b = findLastUuid(urlBeforeQuery)) !== null && _b !== void 0 ? _b : urlBeforeQuery;
41
41
  const hash = hashOfString(thingToHash);
42
42
  imageSet.outputFileName = `${hash}.${imageSet.fileType.ext}`;
43
43
  imageSet.primaryFileOutputPath = Path.posix.join((imageOutputRootPath === null || imageOutputRootPath === void 0 ? void 0 : imageOutputRootPath.length) > 0
@@ -58,9 +58,18 @@ function makeImagePersistencePlan(imageSet, imageOutputRootPath, imagePrefix) {
58
58
  }
59
59
  }
60
60
  exports.makeImagePersistencePlan = makeImagePersistencePlan;
61
+ function findLastUuid(url) {
62
+ // Regex for a UUID surrounded by slashes
63
+ const uuidPattern = /(?<=\/)[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}(?=\/)/gi;
64
+ // Find all UUIDs
65
+ const uuids = url.match(uuidPattern);
66
+ // Return the last UUID if any exist, else return null
67
+ return uuids ? uuids[uuids.length - 1].trim() : null;
68
+ }
61
69
  function hashOfString(s) {
62
70
  let hash = 0;
63
71
  for (let i = 0; i < s.length; ++i)
64
72
  hash = Math.imul(31, hash) + s.charCodeAt(i);
65
73
  return Math.abs(hash);
66
74
  }
75
+ exports.hashOfString = hashOfString;
@@ -8,7 +8,6 @@ const ColumnListTransformer_1 = require("../plugins/ColumnListTransformer");
8
8
  const ColumnTransformer_1 = require("../plugins/ColumnTransformer");
9
9
  const EscapeHtmlBlockModifier_1 = require("../plugins/EscapeHtmlBlockModifier");
10
10
  const HeadingTransformer_1 = require("../plugins/HeadingTransformer");
11
- const NumberedListTransformer_1 = require("../plugins/NumberedListTransformer");
12
11
  const TableTransformer_1 = require("../plugins/TableTransformer");
13
12
  const VideoTransformer_1 = require("../plugins/VideoTransformer");
14
13
  const externalLinks_1 = require("../plugins/externalLinks");
@@ -24,7 +23,6 @@ const defaultConfig = {
24
23
  images_1.standardImageTransformer,
25
24
  CalloutTransformer_1.standardCalloutTransformer,
26
25
  TableTransformer_1.standardTableTransformer,
27
- NumberedListTransformer_1.standardNumberedListTransformer,
28
26
  VideoTransformer_1.standardVideoTransformer,
29
27
  // Link modifiers, which are special because they can read metadata from all the pages in order to figure out the correct url
30
28
  internalLinks_1.standardInternalLinkConversion,
@@ -9,9 +9,10 @@ test("primary file with explicit file output path and prefix", () => {
9
9
  fileType: { ext: "png", mime: "image/png" },
10
10
  };
11
11
  (0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
12
- expect(imageSet.outputFileName).toBe("463556435.png");
13
- expect(imageSet.primaryFileOutputPath).toBe("static/notion_imgs/463556435.png");
14
- expect(imageSet.filePathToUseInMarkdown).toBe("/notion_imgs/463556435.png");
12
+ const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("https://s3.us-west-2.amazonaws.com/primaryImage");
13
+ expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
14
+ expect(imageSet.primaryFileOutputPath).toBe(`static/notion_imgs/${expectedHash}.png`);
15
+ expect(imageSet.filePathToUseInMarkdown).toBe(`/notion_imgs/${expectedHash}.png`);
15
16
  });
16
17
  test("primary file with defaults for image output path and prefix", () => {
17
18
  const imageSet = {
@@ -21,10 +22,31 @@ test("primary file with defaults for image output path and prefix", () => {
21
22
  fileType: { ext: "png", mime: "image/png" },
22
23
  };
23
24
  (0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "", "");
24
- expect(imageSet.outputFileName).toBe("463556435.png");
25
+ const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("https://s3.us-west-2.amazonaws.com/primaryImage");
26
+ expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
25
27
  // the default behavior is to put the image next to the markdown file
26
- expect(imageSet.primaryFileOutputPath).toBe("/pathToParentSomewhere/463556435.png");
27
- expect(imageSet.filePathToUseInMarkdown).toBe("./463556435.png");
28
+ expect(imageSet.primaryFileOutputPath).toBe(`/pathToParentSomewhere/${expectedHash}.png`);
29
+ expect(imageSet.filePathToUseInMarkdown).toBe(`./${expectedHash}.png`);
30
+ });
31
+ test("properly extract UUID from old-style notion image url", () => {
32
+ const imageSet = {
33
+ primaryUrl: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/e1058f46-4d2f-4292-8388-4ad393383439/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220516%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220516T233630Z&X-Amz-Expires=3600&X-Amz-Signature=f215704094fcc884d37073b0b108cf6d1c9da9b7d57a898da38bc30c30b4c4b5&X-Amz-SignedHeaders=host&x-id=GetObject",
34
+ localizedUrls: [],
35
+ fileType: { ext: "png", mime: "image/png" },
36
+ };
37
+ (0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
38
+ const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("e1058f46-4d2f-4292-8388-4ad393383439");
39
+ expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
40
+ });
41
+ test("properly extract UUID from new-style (Sept 2023) notion image url", () => {
42
+ const imageSet = {
43
+ primaryUrl: "https://prod-files-secure.s3.us-west-2.amazonaws.com/d9a2b712-cf69-4bd6-9d65-87a4ceeacca2/d1bcdc8c-b065-4e40-9a11-392aabeb220e/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20230915%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20230915T161258Z&X-Amz-Expires=3600&X-Amz-Signature=28fca48e65fba86d539c3c4b7676fce1fa0857aa194f7b33dd4a468ecca6ab24&X-Amz-SignedHeaders=host&x-id=GetObject",
44
+ localizedUrls: [],
45
+ fileType: { ext: "png", mime: "image/png" },
46
+ };
47
+ (0, MakeImagePersistencePlan_1.makeImagePersistencePlan)(imageSet, "./static/notion_imgs", "/notion_imgs");
48
+ const expectedHash = (0, MakeImagePersistencePlan_1.hashOfString)("d1bcdc8c-b065-4e40-9a11-392aabeb220e");
49
+ expect(imageSet.outputFileName).toBe(`${expectedHash}.png`);
28
50
  });
29
51
  // In order to make image fallback work with other languages, we have to have
30
52
  // a file for each image, in each Docusaurus language directory. This is true
@@ -28,13 +28,11 @@ function notionColumnToMarkdown(notionToMarkdown, getBlockChildren, block) {
28
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
29
  if (!has_children)
30
30
  return "";
31
- const children = yield getBlockChildren(id);
32
- const childrenPromises = children.map((column) => __awaiter(this, void 0, void 0, function* () { return yield notionToMarkdown.blockToMarkdown(column); }));
33
- const childrenStrings = yield Promise.all(childrenPromises);
31
+ const columnChildren = yield getBlockChildren(id);
32
+ const childrenMdBlocksArray = yield Promise.all(columnChildren.map((child) => __awaiter(this, void 0, void 0, function* () { return yield notionToMarkdown.blocksToMarkdown([child]); })));
33
+ const childrenMarkdown = childrenMdBlocksArray.map(mdBlockArray => notionToMarkdown.toMarkdownString(mdBlockArray).parent);
34
34
  const columnWidth = yield getColumnWidth(block);
35
- // note: it would look better in the markup with \n, but that
36
- // causes notion-to-md to give us ":::A" instead of \n for some reason.
37
- return (`<div class='notion-column' style={{width: '${columnWidth}'}}>\n\n${childrenStrings.join("\n\n")}\n\n</div>` +
35
+ return (`<div class='notion-column' style={{width: '${columnWidth}'}}>\n\n${childrenMarkdown.join("\n")}\n</div>` +
38
36
  // Spacer between columns. CSS takes care of hiding this for the last column
39
37
  // and when the screen is too narrow for multiple columns.
40
38
  `<div className='notion-spacer'></div>`);
@@ -0,0 +1,118 @@
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 pluginTestRun_1 = require("./pluginTestRun");
13
+ const ColumnTransformer_1 = require("./ColumnTransformer");
14
+ // Even though we can set up most tests with our own children
15
+ // so that we aren't relying on real data from Notion,
16
+ // we can't prevent the notion-to-md library from making an API call
17
+ // every time it processes a block with has_children:true.
18
+ // So for these tests with children, we need any valid API key.
19
+ const runTestsWhichRequireAnyValidApiKey = !!process.env.DOCU_NOTION_INTEGRATION_TOKEN;
20
+ // To test grandchildren, we can't get around notion-to-md making an API call
21
+ // to get real children. So we need a specific notion record.
22
+ // For that reason, we don't try to run these tests unless the user changes this flag.
23
+ // But it is an important test; grandchildren in columns were broken.
24
+ // See https://github.com/sillsdev/docu-notion/issues/70.
25
+ const runManualTestsWhichRequireSpecificNotionRecords = false;
26
+ const columnBlock = {
27
+ object: "block",
28
+ id: "e6d2d7b7-b1ed-464a-86d2-bb5f6be78a03",
29
+ has_children: true,
30
+ type: "column",
31
+ column: {},
32
+ };
33
+ function getResults(children) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ return yield (0, pluginTestRun_1.blocksToMarkdown)({ plugins: [ColumnTransformer_1.standardColumnTransformer] }, [columnBlock], undefined, children, process.env.DOCU_NOTION_INTEGRATION_TOKEN);
36
+ });
37
+ }
38
+ const columnWrapperStart = "<div class='notion-column' style=\\{\\{width: '.*?'\\}\\}>\\n\\n";
39
+ const columnWrapperEnd = "\\n\\n<\\/div><div className='notion-spacer'><\\/div>";
40
+ if (runTestsWhichRequireAnyValidApiKey) {
41
+ columnBlock.has_children = true;
42
+ test("requires API key - column with paragraph", () => __awaiter(void 0, void 0, void 0, function* () {
43
+ const results = yield getResults([getTestParagraphBlock()]);
44
+ expect(results).toMatch(new RegExp(`${columnWrapperStart}\\s*?my paragraph\\s*?${columnWrapperEnd}`));
45
+ }), 20000);
46
+ test("requires API key - column with two paragraphs", () => __awaiter(void 0, void 0, void 0, function* () {
47
+ const results = yield getResults([
48
+ getTestParagraphBlock(1),
49
+ getTestParagraphBlock(2),
50
+ ]);
51
+ expect(results).toMatch(new RegExp(`${columnWrapperStart}\\s*?my paragraph 1\\s+?my paragraph 2\\s*?${columnWrapperEnd}`));
52
+ }), 20000);
53
+ test("requires API key - column with numbered list", () => __awaiter(void 0, void 0, void 0, function* () {
54
+ const results = yield getResults([
55
+ getNumberedListItemBlock(1),
56
+ getNumberedListItemBlock(2),
57
+ ]);
58
+ expect(results).toMatch(new RegExp(`${columnWrapperStart}\\s*?1\\. Numbered list item 1\\s+?2\\. Numbered list item 2\\s*?${columnWrapperEnd}`, "s"));
59
+ }), 20000);
60
+ if (runManualTestsWhichRequireSpecificNotionRecords) {
61
+ test("manual test - requires specific notion record and API key - column with numbered list with sublist", () => __awaiter(void 0, void 0, void 0, function* () {
62
+ const realNumberedListBlock = getNumberedListItemBlock(1);
63
+ realNumberedListBlock.id = "ca08d14b-9b70-4f6f-9d17-9fd74b57afeb";
64
+ realNumberedListBlock.has_children = true;
65
+ const results = yield getResults([realNumberedListBlock]);
66
+ expect(results).toMatch(new RegExp(`${columnWrapperStart}\\s*?1\\. Numbered list item 1\\s+?- unordered sub-bullet\\s*?${columnWrapperEnd}`, "s"));
67
+ }), 20000);
68
+ }
69
+ }
70
+ else {
71
+ // This test prevents an error when runTestsWhichRequireAnyValidApiKey is false
72
+ // due to having a test suite with no tests.
73
+ test("no column transformer tests were run because there is no API key provided", () => {
74
+ expect(true).toBe(true);
75
+ });
76
+ }
77
+ function getNumberedListItemBlock(identifier) {
78
+ const content = identifier
79
+ ? `Numbered list item ${identifier}`
80
+ : `Numbered list item`;
81
+ return {
82
+ object: "block",
83
+ type: "numbered_list_item",
84
+ numbered_list_item: {
85
+ rich_text: [
86
+ {
87
+ type: "text",
88
+ text: { content: content },
89
+ annotations: {
90
+ code: false,
91
+ },
92
+ plain_text: content,
93
+ },
94
+ ],
95
+ },
96
+ };
97
+ }
98
+ function getTestParagraphBlock(identifier) {
99
+ const content = identifier ? `my paragraph ${identifier}` : `my paragraph`;
100
+ return {
101
+ object: "block",
102
+ type: "paragraph",
103
+ paragraph: {
104
+ rich_text: [
105
+ {
106
+ type: "text",
107
+ text: {
108
+ content: content,
109
+ },
110
+ annotations: {
111
+ code: false,
112
+ },
113
+ plain_text: content,
114
+ },
115
+ ],
116
+ },
117
+ };
118
+ }
@@ -14,7 +14,6 @@ const pluginTestRun_1 = require("./pluginTestRun");
14
14
  const CalloutTransformer_1 = require("./CalloutTransformer");
15
15
  const externalLinks_1 = require("./externalLinks");
16
16
  const internalLinks_1 = require("./internalLinks");
17
- const NumberedListTransformer_1 = require("./NumberedListTransformer");
18
17
  test("urls that show up as raw text get left that way", () => __awaiter(void 0, void 0, void 0, function* () {
19
18
  const results = yield getMarkdown({
20
19
  type: "paragraph",
@@ -523,7 +522,6 @@ function getMarkdown(block, targetPage) {
523
522
  const config = {
524
523
  plugins: [
525
524
  CalloutTransformer_1.standardCalloutTransformer,
526
- NumberedListTransformer_1.standardNumberedListTransformer,
527
525
  internalLinks_1.standardInternalLinkConversion,
528
526
  externalLinks_1.standardExternalLinkConversion,
529
527
  ],
@@ -1,7 +1,7 @@
1
1
  import { NotionPage } from "../NotionPage";
2
2
  import { IDocuNotionConfig } from "../config/configuration";
3
3
  import { NotionBlock } from "../types";
4
- export declare function blocksToMarkdown(config: IDocuNotionConfig, blocks: NotionBlock[], pages?: NotionPage[]): Promise<string>;
4
+ export declare function blocksToMarkdown(config: IDocuNotionConfig, blocks: NotionBlock[], pages?: NotionPage[], children?: NotionBlock[], validApiKey?: string): Promise<string>;
5
5
  export declare function makeSamplePageObject(options: {
6
6
  slug?: string;
7
7
  name?: string;
@@ -16,9 +16,17 @@ const HierarchicalNamedLayoutStrategy_1 = require("../HierarchicalNamedLayoutStr
16
16
  const NotionPage_1 = require("../NotionPage");
17
17
  const transform_1 = require("../transform");
18
18
  const internalLinks_1 = require("./internalLinks");
19
- function blocksToMarkdown(config, blocks, pages) {
19
+ const pull_1 = require("../pull");
20
+ function blocksToMarkdown(config, blocks, pages,
21
+ // Notes on children:
22
+ // - These children will apply to each block in blocks. (could enhance but not needed yet)
23
+ // - If you are passing in children, it is probably because your parent block has has_children=true.
24
+ // In that case, notion-to-md will make an API call... you'll need to set any validApiKey.
25
+ children, validApiKey) {
20
26
  return __awaiter(this, void 0, void 0, function* () {
21
- const notionClient = new client_1.Client({ auth: "unused" });
27
+ const notionClient = new client_1.Client({
28
+ auth: validApiKey || "unused",
29
+ });
22
30
  const notionToMD = new notion_to_md_1.NotionToMarkdown({
23
31
  notionClient,
24
32
  });
@@ -28,12 +36,12 @@ function blocksToMarkdown(config, blocks, pages) {
28
36
  // }
29
37
  const docunotionContext = {
30
38
  notionToMarkdown: notionToMD,
31
- // TODO when does this actually need to do get some children?
32
- // We can add a children argument to this method, but for the tests
33
- // I have so far, it's not needed.
34
39
  getBlockChildren: (id) => {
40
+ // We call numberChildrenIfNumberedList here because the real getBlockChildren does
41
+ if (children)
42
+ (0, pull_1.numberChildrenIfNumberedList)(children);
35
43
  return new Promise((resolve, reject) => {
36
- resolve([]);
44
+ resolve(children !== null && children !== void 0 ? children : []);
37
45
  });
38
46
  },
39
47
  convertNotionLinkToLocalDocusaurusLink: (url) => {
package/dist/pull.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Client } from "@notionhq/client";
2
+ import { ListBlockChildrenResponseResults } from "notion-to-md/build/types";
2
3
  export type DocuNotionOptions = {
3
4
  notionToken: string;
4
5
  rootPage: string;
@@ -11,3 +12,4 @@ export type DocuNotionOptions = {
11
12
  export declare function notionPull(options: DocuNotionOptions): Promise<void>;
12
13
  export declare function executeWithRateLimitAndRetries<T>(label: string, asyncFunction: () => Promise<T>): Promise<T>;
13
14
  export declare function initNotionClient(notionToken: string): Client;
15
+ export declare function numberChildrenIfNumberedList(blocks: ListBlockChildrenResponseResults): void;
package/dist/pull.js CHANGED
@@ -32,7 +32,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
32
32
  });
33
33
  };
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
- exports.initNotionClient = exports.executeWithRateLimitAndRetries = exports.notionPull = void 0;
35
+ exports.numberChildrenIfNumberedList = exports.initNotionClient = exports.executeWithRateLimitAndRetries = exports.notionPull = void 0;
36
36
  const fs = __importStar(require("fs-extra"));
37
37
  const notion_to_md_1 = require("notion-to-md");
38
38
  const HierarchicalNamedLayoutStrategy_1 = require("./HierarchicalNamedLayoutStrategy");
@@ -265,7 +265,9 @@ function getBlockChildren(id) {
265
265
  (0, log_1.error)(`The Notion API returned some blocks that were not full blocks. docu-notion does not handle this yet. Please report it.`);
266
266
  (0, process_1.exit)(1);
267
267
  }
268
- return (_b = overallResult === null || overallResult === void 0 ? void 0 : overallResult.results) !== null && _b !== void 0 ? _b : [];
268
+ const result = (_b = overallResult === null || overallResult === void 0 ? void 0 : overallResult.results) !== null && _b !== void 0 ? _b : [];
269
+ numberChildrenIfNumberedList(result);
270
+ return result;
269
271
  });
270
272
  }
271
273
  function initNotionClient(notionToken) {
@@ -288,3 +290,20 @@ function fromPageId(context, pageId, order, foundDirectlyInOutline) {
288
290
  });
289
291
  });
290
292
  }
293
+ // This function is copied (and renamed from modifyNumberedListObject) from notion-to-md.
294
+ // They always run it on the results of their getBlockChildren.
295
+ // When we use our own getBlockChildren, we need to run it too.
296
+ function numberChildrenIfNumberedList(blocks) {
297
+ let numberedListIndex = 0;
298
+ for (const block of blocks) {
299
+ if ("type" in block && block.type === "numbered_list_item") {
300
+ // add numbers
301
+ // @ts-ignore
302
+ block.numbered_list_item.number = ++numberedListIndex;
303
+ }
304
+ else {
305
+ numberedListIndex = 0;
306
+ }
307
+ }
308
+ }
309
+ exports.numberChildrenIfNumberedList = numberChildrenIfNumberedList;
package/dist/transform.js CHANGED
@@ -128,7 +128,7 @@ function doTransformsOnMarkdown(context, config, input) {
128
128
  function doNotionToMarkdown(docunotionContext, blocks) {
129
129
  return __awaiter(this, void 0, void 0, function* () {
130
130
  const mdBlocks = yield docunotionContext.notionToMarkdown.blocksToMarkdown(blocks);
131
- const markdown = docunotionContext.notionToMarkdown.toMarkdownString(mdBlocks);
131
+ const markdown = docunotionContext.notionToMarkdown.toMarkdownString(mdBlocks).parent || "";
132
132
  return markdown;
133
133
  });
134
134
  }
package/package.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "markdown-table": "^2.0.0",
36
36
  "node-fetch": "2.6.6",
37
37
  "notion-client": "^4",
38
- "notion-to-md": "2.5.5",
38
+ "notion-to-md": "3.1.1",
39
39
  "path": "^0.12.7",
40
40
  "ts-node": "^10.2.1",
41
41
  "sanitize-filename": "^1.6.3"
@@ -90,5 +90,5 @@
90
90
  "volta": {
91
91
  "node": "18.16.0"
92
92
  },
93
- "version": "0.14.0-alpha.2"
93
+ "version": "0.14.0-alpha.4"
94
94
  }
@@ -1,2 +0,0 @@
1
- import { IPlugin } from "./pluginTypes";
2
- export declare const standardNumberedListTransformer: IPlugin;
@@ -1,55 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.standardNumberedListTransformer = void 0;
4
- // This is mostly what notion-to-markdown would normally do with a block of type
5
- // numbered_list_item. A patch is documented at the end.
6
- function numberedListTransformer(notionToMarkdown, block) {
7
- var _a, _b, _c;
8
- //console.log("got numbered list block " + JSON.stringify(block));
9
- // In this case typescript is not able to index the types properly, hence ignoring the error
10
- // @ts-ignore
11
- const blockContent =
12
- // @ts-ignore
13
- ((_a = block.numbered_list_item) === null || _a === void 0 ? void 0 : _a.text) || ((_b = block.numbered_list_item) === null || _b === void 0 ? void 0 : _b.rich_text) || [];
14
- let parsedData = "";
15
- blockContent.map((content) => {
16
- const annotations = content.annotations;
17
- let plain_text = content.plain_text;
18
- plain_text = notionToMarkdown.annotatePlainText(plain_text, annotations);
19
- if (content["href"]) {
20
- plain_text = `[${plain_text}](${content["href"]})`;
21
- }
22
- parsedData += plain_text;
23
- });
24
- // There is code in notion-to-md which attempts to set an incrementing number
25
- // on each of these. Somehow it fails; in my testing, block.numbered_list_item never
26
- // has a field 'number'. But we don't actually need incrementing numbers;
27
- // markdown will do the numbering if we just make something that looks like
28
- // a member of a numbered list by starting with number followed by period and space.
29
- // I'm keeping the original code in case notion-to-md gets fixed and there is actually
30
- // some reason to use incrementing numbers (it would at least make the markdown more
31
- // human-readable); but this at least works.
32
- // A problem is that in notion, a numbered list may continue after some intermediate
33
- // content. To achieve this in markdown, we'd need to indent the intermediate content
34
- // by a tab. Not only is it difficult to do this, but there appears to be no way to
35
- // know whether we should. The data we get from notion doesn't include the item number,
36
- // and its parent is the page rather than a particular list. So there is no way I can
37
- // see to distinguish a list continuation from a new list. The code here will leave
38
- // it up to markdown to decide whether to start a new list; I believe it will do so
39
- // if it sees any intervening lines that are not list items.
40
- let num = (_c = block.numbered_list_item) === null || _c === void 0 ? void 0 : _c.number;
41
- //console.log("got number " + num?.toString());
42
- if (!num) {
43
- num = 1;
44
- }
45
- return Promise.resolve(`${num}. ${parsedData.trim()}`);
46
- }
47
- exports.standardNumberedListTransformer = {
48
- name: "standardNumberedListTransformer",
49
- notionToMarkdownTransforms: [
50
- {
51
- type: "numbered_list_item",
52
- getStringFromBlock: (context, block) => numberedListTransformer(context.notionToMarkdown, block),
53
- },
54
- ],
55
- };
@@ -1,86 +0,0 @@
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 pluginTestRun_1 = require("./pluginTestRun");
13
- const NumberedListTransformer_1 = require("./NumberedListTransformer");
14
- let block;
15
- beforeEach(() => {
16
- block = {
17
- has_children: false,
18
- archived: false,
19
- type: "callout",
20
- callout: {
21
- rich_text: [
22
- {
23
- type: "text",
24
- text: { content: "This is information callout", link: null },
25
- annotations: {
26
- bold: false,
27
- italic: false,
28
- strikethrough: false,
29
- underline: false,
30
- code: false,
31
- color: "default",
32
- },
33
- plain_text: "This is the callout",
34
- href: null,
35
- },
36
- ],
37
- icon: { type: "emoji", emoji: "ℹ️" },
38
- color: "gray_background",
39
- },
40
- };
41
- });
42
- test("external link inside numbered list, italic preserved", () => __awaiter(void 0, void 0, void 0, function* () {
43
- const config = { plugins: [NumberedListTransformer_1.standardNumberedListTransformer] };
44
- const results = yield (0, pluginTestRun_1.blocksToMarkdown)(config, [
45
- {
46
- type: "numbered_list_item",
47
- numbered_list_item: {
48
- rich_text: [
49
- {
50
- type: "text",
51
- text: { content: "link ", link: null },
52
- annotations: {
53
- bold: false,
54
- italic: false,
55
- strikethrough: false,
56
- underline: false,
57
- code: false,
58
- color: "default",
59
- },
60
- plain_text: "link ",
61
- href: null,
62
- },
63
- {
64
- type: "text",
65
- text: {
66
- content: "github",
67
- link: { url: "https://github.com" },
68
- },
69
- annotations: {
70
- bold: false,
71
- italic: true,
72
- strikethrough: false,
73
- underline: false,
74
- code: false,
75
- color: "default",
76
- },
77
- plain_text: "github",
78
- href: "https://github.com",
79
- },
80
- ],
81
- color: "default",
82
- },
83
- },
84
- ]);
85
- expect(results.trim()).toBe(`1. link [_github_](https://github.com)`);
86
- }));