Skip to content

feat: Make heading links work (#20) #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"scripts": {
"test": "jest",
"build": "yarn test && tsc && cp ./src/css/*.css dist/",
"build-only": "tsc && cp ./src/css/*.css dist/",
"clean": "rm -rf ./dist/",
"semantic-release": "semantic-release",
"typecheck": "tsc --noEmit",
Expand Down
33 changes: 33 additions & 0 deletions src/CustomTranformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,39 @@ export function setupCustomTransformers(
numberedListTransformer(notionToMarkdown, notionClient, block)
);

const headingCustomTransformer = async (
block: ListBlockChildrenResponseResult
) => {
// This is the other half of the horrible hack in pull.ts which sets the type
// of every heading_n to my_heading_n. We have to do this because if
// we simply set a custom transformer to heading_n, it will keep
// recursively calling this code, with blockToMarkdown using the custom transformer
// over and over. Instead, we want blockToMarkdown to give us the normal
// result, to which we will append the block ID to enable heading links.
(block as any).type = (block as any).type.replace("my_", "");

const unmodifiedMarkdown = await notionToMarkdown.blockToMarkdown(block);
// For some reason, inline links come in without the dashes, so we have to strip
// dashes here to match them.
const blockIdSansDashes = block.id.replaceAll("-", "");
// To make heading links work in docusaurus, you make them look like:
// ### Hello World {#my-explicit-id}
// See https://docusaurus.io/docs/markdown-features/toc#heading-ids.
return `${unmodifiedMarkdown} {#${blockIdSansDashes}}`;
};
notionToMarkdown.setCustomTransformer(
"my_heading_1",
headingCustomTransformer
);
notionToMarkdown.setCustomTransformer(
"my_heading_2",
headingCustomTransformer
);
notionToMarkdown.setCustomTransformer(
"my_heading_3",
headingCustomTransformer
);

// Note: Pull.ts also adds an image transformer, but has to do that for each
// page so we don't do it here.
}
Expand Down
9 changes: 6 additions & 3 deletions src/NotionPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { RateLimiter } from "limiter";
import { Client } from "@notionhq/client";
import { logDebug } from "./log";
import { parseLinkId } from "./links";
import { info } from "console";

const notionLimiter = new RateLimiter({
Expand Down Expand Up @@ -75,9 +76,11 @@ export class NotionPage {
}

public matchesLinkId(id: string): boolean {
const { baseLinkId } = parseLinkId(id);

const match =
id === this.pageId || // from a link_to_page.pageId, which still has the dashes
id === this.pageId.replaceAll("-", ""); // from inline links, which are lacking the dashes
baseLinkId === this.pageId || // from a link_to_page.pageId, which still has the dashes
baseLinkId === this.pageId.replaceAll("-", ""); // from inline links, which are lacking the dashes

logDebug(
`matchedLinkId`,
Expand All @@ -95,7 +98,7 @@ export class NotionPage {
or
"type": "database_id",
...
},
},
*/
return (this.metadata as any).parent.type === "database_id"
? PageType.DatabasePage
Expand Down
31 changes: 25 additions & 6 deletions src/links.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@ export function convertInternalLinks(
layoutStrategy: LayoutStrategy
): string {
const convertHref = (url: string) => {
const p = pages.find(p => {
const page = pages.find(p => {
return p.matchesLinkId(url);
});
if (p) {
verbose(
`Converting Link ${url} --> ${layoutStrategy.getLinkPathForPage(p)}`
);
return layoutStrategy.getLinkPathForPage(p);
if (page) {
let convertedLink = layoutStrategy.getLinkPathForPage(page);

// Include the fragment (# and after) if it exists
const { fragmentId } = parseLinkId(url);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you don't need baseLinkId here too, just to keep the destructuring happy?

convertedLink += fragmentId;

verbose(`Converting Link ${url} --> ${convertedLink}`);
return convertedLink;
}

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

return output;
}

// Parse the link ID to get the base (before the #) and the fragment (# and after).
export function parseLinkId(fullLinkId: string): {
baseLinkId: string; // before the #
fragmentId: string; // # and after
} {
const iHash: number = fullLinkId.indexOf("#");
if (iHash >= 0) {
return {
baseLinkId: fullLinkId.substring(0, iHash),
fragmentId: fullLinkId.substring(iHash),
};
}
return { baseLinkId: fullLinkId, fragmentId: "" };
}
11 changes: 10 additions & 1 deletion src/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async function getPagesRecursively(

// 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.
// 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
// 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
// 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
// it has both? Well then we assume it's a content page.
if (pageInfo.linksPageIdsAndOrder?.length) {
warning(
Expand Down Expand Up @@ -233,6 +233,15 @@ async function outputPage(page: NotionPage) {
relativePathToFolderContainingPage
)
);

// One half of a horrible hack to make heading links work.
// See the other half and explanation in CustomTransformers.ts => headingCustomTransformer.
for (const block_t of blocks) {
const block = block_t as any;
if (block.type.startsWith("heading"))
block.type = block.type.replace("heading", "my_heading");
}

const mdBlocks = await notionToMarkdown.blocksToMarkdown(blocks);

// if (page.nameOrTitle.startsWith("Embed")) {
Expand Down