Skip to content

Commit cc93d95

Browse files
authored
Merge pull request #22 from sillsdev/HeadingLinks
feat: Make heading links work (#20)
2 parents 5c7f420 + 088e6a6 commit cc93d95

File tree

5 files changed

+75
-10
lines changed

5 files changed

+75
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"scripts": {
1010
"test": "jest",
1111
"build": "yarn test && tsc && cp ./src/css/*.css dist/",
12+
"build-only": "tsc && cp ./src/css/*.css dist/",
1213
"clean": "rm -rf ./dist/",
1314
"semantic-release": "semantic-release",
1415
"typecheck": "tsc --noEmit",

src/CustomTranformers.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,39 @@ export function setupCustomTransformers(
9797
numberedListTransformer(notionToMarkdown, notionClient, block)
9898
);
9999

100+
const headingCustomTransformer = async (
101+
block: ListBlockChildrenResponseResult
102+
) => {
103+
// This is the other half of the horrible hack in pull.ts which sets the type
104+
// of every heading_n to my_heading_n. We have to do this because if
105+
// we simply set a custom transformer to heading_n, it will keep
106+
// recursively calling this code, with blockToMarkdown using the custom transformer
107+
// over and over. Instead, we want blockToMarkdown to give us the normal
108+
// result, to which we will append the block ID to enable heading links.
109+
(block as any).type = (block as any).type.replace("my_", "");
110+
111+
const unmodifiedMarkdown = await notionToMarkdown.blockToMarkdown(block);
112+
// For some reason, inline links come in without the dashes, so we have to strip
113+
// dashes here to match them.
114+
const blockIdSansDashes = block.id.replaceAll("-", "");
115+
// To make heading links work in docusaurus, you make them look like:
116+
// ### Hello World {#my-explicit-id}
117+
// See https://docusaurus.io/docs/markdown-features/toc#heading-ids.
118+
return `${unmodifiedMarkdown} {#${blockIdSansDashes}}`;
119+
};
120+
notionToMarkdown.setCustomTransformer(
121+
"my_heading_1",
122+
headingCustomTransformer
123+
);
124+
notionToMarkdown.setCustomTransformer(
125+
"my_heading_2",
126+
headingCustomTransformer
127+
);
128+
notionToMarkdown.setCustomTransformer(
129+
"my_heading_3",
130+
headingCustomTransformer
131+
);
132+
100133
// Note: Pull.ts also adds an image transformer, but has to do that for each
101134
// page so we don't do it here.
102135
}

src/NotionPage.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
import { RateLimiter } from "limiter";
88
import { Client } from "@notionhq/client";
99
import { logDebug } from "./log";
10+
import { parseLinkId } from "./links";
1011
import { info } from "console";
1112

1213
const notionLimiter = new RateLimiter({
@@ -75,9 +76,11 @@ export class NotionPage {
7576
}
7677

7778
public matchesLinkId(id: string): boolean {
79+
const { baseLinkId } = parseLinkId(id);
80+
7881
const match =
79-
id === this.pageId || // from a link_to_page.pageId, which still has the dashes
80-
id === this.pageId.replaceAll("-", ""); // from inline links, which are lacking the dashes
82+
baseLinkId === this.pageId || // from a link_to_page.pageId, which still has the dashes
83+
baseLinkId === this.pageId.replaceAll("-", ""); // from inline links, which are lacking the dashes
8184

8285
logDebug(
8386
`matchedLinkId`,
@@ -95,7 +98,7 @@ export class NotionPage {
9598
or
9699
"type": "database_id",
97100
...
98-
},
101+
},
99102
*/
100103
return (this.metadata as any).parent.type === "database_id"
101104
? PageType.DatabasePage

src/links.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ export function convertInternalLinks(
88
layoutStrategy: LayoutStrategy
99
): string {
1010
const convertHref = (url: string) => {
11-
const p = pages.find(p => {
11+
const page = pages.find(p => {
1212
return p.matchesLinkId(url);
1313
});
14-
if (p) {
15-
verbose(
16-
`Converting Link ${url} --> ${layoutStrategy.getLinkPathForPage(p)}`
17-
);
18-
return layoutStrategy.getLinkPathForPage(p);
14+
if (page) {
15+
let convertedLink = layoutStrategy.getLinkPathForPage(page);
16+
17+
// Include the fragment (# and after) if it exists
18+
const { fragmentId } = parseLinkId(url);
19+
convertedLink += fragmentId;
20+
21+
verbose(`Converting Link ${url} --> ${convertedLink}`);
22+
return convertedLink;
1923
}
2024

2125
// About this situation. See https://github.com/sillsdev/docu-notion/issues/9
@@ -101,3 +105,18 @@ function transformLinks(
101105

102106
return output;
103107
}
108+
109+
// Parse the link ID to get the base (before the #) and the fragment (# and after).
110+
export function parseLinkId(fullLinkId: string): {
111+
baseLinkId: string; // before the #
112+
fragmentId: string; // # and after
113+
} {
114+
const iHash: number = fullLinkId.indexOf("#");
115+
if (iHash >= 0) {
116+
return {
117+
baseLinkId: fullLinkId.substring(0, iHash),
118+
fragmentId: fullLinkId.substring(iHash),
119+
};
120+
}
121+
return { baseLinkId: fullLinkId, fragmentId: "" };
122+
}

src/pull.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async function getPagesRecursively(
134134

135135
// The best practice is to keep content pages in the "database" (e.g. kanban board), but we do allow people to make pages in the outline directly.
136136
// So how can we tell the difference between a page that is supposed to be content and one that is meant to form the sidebar? If it
137-
// have just links, then it's a page for forming the sidebar. If it has contents and no links, then it's a content page. But what if
137+
// has only links, then it's a page for forming the sidebar. If it has contents and no links, then it's a content page. But what if
138138
// it has both? Well then we assume it's a content page.
139139
if (pageInfo.linksPageIdsAndOrder?.length) {
140140
warning(
@@ -233,6 +233,15 @@ async function outputPage(page: NotionPage) {
233233
relativePathToFolderContainingPage
234234
)
235235
);
236+
237+
// One half of a horrible hack to make heading links work.
238+
// See the other half and explanation in CustomTransformers.ts => headingCustomTransformer.
239+
for (const block_t of blocks) {
240+
const block = block_t as any;
241+
if (block.type.startsWith("heading"))
242+
block.type = block.type.replace("heading", "my_heading");
243+
}
244+
236245
const mdBlocks = await notionToMarkdown.blocksToMarkdown(blocks);
237246

238247
// if (page.nameOrTitle.startsWith("Embed")) {

0 commit comments

Comments
 (0)