Skip to content

Commit

Permalink
Merge pull request #33 from kmlbgn/master
Browse files Browse the repository at this point in the history
feat: renaming to Nocusaurus
  • Loading branch information
kmlbgn authored Mar 7, 2024
2 parents 4e627f8 + 756d4a7 commit df600a1
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 504 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/conversion-problem.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A clear and concise description of what the bug is.

** Reproduction Steps **

URL of a source Notion page that, when docu-notion accesses or converts it, shows the problem:
URL of a source Notion page that, when Nocusaurus accesses or converts it, shows the problem:

At least one of these:
1) URL of a docusuarus site showing the resulting page
Expand Down
4 changes: 2 additions & 2 deletions docu-notion.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* This file is only used when testing docu-notion itself, not when it is used as a library.
E.g., if you run `npm run pull-test-tagged`, docu-notion will read this file and use it to configure itself,
/* This file is only used when testing Nocusaurus itself, not when it is used as a library.
E.g., if you run `npm run pull-test-tagged`, Nocusaurus will read this file and use it to configure itself,
using these example plugins.
*/

Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"vite": "^4.2.1",
"vitest": "^0.30.1"
},
"name": "docu-notion-kira",
"name": "nocusaurus",
"description": "Download Notion pages as markdown and image files, preserving hierarchy and enabling workflow properties. Works with Docusaurus.",
"license": "MIT",
"author": {
Expand All @@ -82,9 +82,9 @@
"documentation"
],
"bugs": {
"url": "https://github.com/KiraCore/docu-notion-kira/issues"
"url": "https://github.com/KiraCore/nocusaurus/issues"
},
"homepage": "https://github.com/KiraCore/docu-notion-kira#readme",
"homepage": "https://github.com/KiraCore/nocusaurus#readme",
"main": "./dist/index.js",
"bin": "dist/index.js",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion src/NotionPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { parseLinkId } from "./plugins/internalLinks";
import { ListBlockChildrenResponseResults } from "notion-to-md/build/types";

// Notion has 2 kinds of pages: a normal one which is just content, and what I'm calling a "database page", which has whatever properties you put on it.
// docu-notion supports the later via links from outline pages. That is, you put the database pages in a database, then separately, in the outline, you
// Nocusaurus supports the later via links from outline pages. That is, you put the database pages in a database, then separately, in the outline, you
// create pages for each node of the outline and then add links from those to the database pages. In this way, we get the benefits of database
// pages (metadata, workflow, etc) and also normal pages (order, position in the outline).
export enum PageType {
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Plugins (Advanced & Experimental)

If your project needs some processing that docu-notion doesn't already provide, you can provide a plugin that does it. If there is call for it, we'll add more documentation in the future. But for now, here's the steps:
If your project needs some processing that Nocusaurus doesn't already provide, you can provide a plugin that does it. If there is call for it, we'll add more documentation in the future. But for now, here's the steps:

1. Add a `docu-notion.config.ts` to the root level of your project directory.
1. Add something like this:
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/VideoTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const standardVideoTransformer: IPlugin = {
warning(
`[standardVideoTransformer] Found Notion "video" block with type ${JSON.stringify(
(video as any).type
)}. The best docu-notion can do for now is ignore it.`
)}. The best Nocusaurus can do for now is ignore it.`
);
return "";
break;
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/externalLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const standardExternalLinkConversion: IPlugin = {
if (label === "bookmark") {
const replacement = `[${url}](${url})`;
warning(
`Link parsing: [ExternalLinkPlugin] Found Notion "Bookmark" link. In Notion this would show as an embed. The best docu-notion can do at the moment is replace "Bookmark" with the actual URL: ${replacement}`
`Link parsing: [ExternalLinkPlugin] Found Notion "Bookmark" link. In Notion this would show as an embed. The best Nocusaurus can do at the moment is replace "Bookmark" with the actual URL: ${replacement}`
);
return replacement;
}
Expand Down
184 changes: 89 additions & 95 deletions src/plugins/internalLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,130 +2,124 @@ import { IDocuNotionContext, IPlugin } from "./pluginTypes";
import { warning, verbose } from "../log";
import { NotionPage } from "../NotionPage";

// converts a url to a local link, if it is a link to a page in the Notion site
// only here for plugins, notion won't normally be giving us raw urls.
// If it finds a URL but can't find the page it points to, it will return undefined.
// If it doesn't find a match at all, it returns undefined.
export function convertInternalUrl(
context: IDocuNotionContext,
url: string
): string | undefined {
/**
* Converts an external URL to a local link if it's a link to a page on the Notion site.
* Intended for plugin use; normally, Notion doesn't provide raw URLs.
* Returns undefined if the page cannot be found or if no match is found.
*/
export function convertInternalUrl(context: IDocuNotionContext, url: string): string | undefined {
const kGetIDFromNotionURL = /https:\/\/www\.notion\.so\/([a-z0-9]+).*/;
const match = kGetIDFromNotionURL.exec(url);
if (match === null) {
warning(
`[standardInternalLinkConversion] Could not parse link ${url} as a Notion URL`
);
warning(`Could not parse link ${url} as a Notion URL`);
return undefined;
}

const id = match[1];
const allTabsPages = context.allTabsPages;
// find the page where pageId matches hrefFromNotion
const targetPage = allTabsPages.find(p => {
return p.matchesLinkId(id);
});
let targetPage: { page: NotionPage | undefined, tab: string | undefined } = { page: undefined, tab: undefined }; for (const tab in context.allTabsPages) {
const foundPage = context.allTabsPages[tab].find(p => p.matchesLinkId(id));
if (foundPage) {
targetPage = { page: foundPage, tab: tab };
break; // Exit the loop once a matching page is found
}
}

if (!targetPage) {
// About this situation. See https://github.com/sillsdev/docu-notion/issues/9
warning(
`[standardInternalLinkConversion] Could not find the target of this link. Note that links to outline sections are not supported. ${url}. https://github.com/sillsdev/docu-notion/issues/9`
);
return undefined;
if (targetPage.page && targetPage.tab) {
return convertLinkHref(context, targetPage.tab, targetPage.page, url);
} else {
warning(`Could not find the target of this link. Links to outline sections are not supported. ${url}.`);
return `${id}[broken link]`;
}
// warning(
// `[standardInternalLinkConversion] Found the target for ${id}, passing ${url}`
// );
return convertLinkHref(context, targetPage, url);
}

// handles the whole markdown link, including the label
function convertInternalLink(
context: IDocuNotionContext,
markdownLink: string
): string {
/**
* Converts a markdown link to a local link if it's a Notion page link.
* Skips conversion for image links and links that cannot be resolved to local targets.
* Returns the original markdown link if it cannot be parsed or converted.
*/
function convertInternalLink(context: IDocuNotionContext, markdownLink: string): string {
const linkRegExp = /\[([^\]]+)?\]\((?!mailto:)(https:\/\/www\.notion\.so\/[^)]+|\/[^),]+)\)/g;
const match = linkRegExp.exec(markdownLink);
if (match === null) {
warning(
`[InternalLinkPlugin] Could not parse link ${markdownLink}`
);
warning(`Could not parse link ${markdownLink}`);
return markdownLink;
}

const labelFromNotion = match[1] || "";
let hrefFromNotion = match[2];
const labelFromNotion = match[1] || "";
const imageFileExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
const isImageLink = imageFileExtensions.some(ext => hrefFromNotion.endsWith(ext));
if (isImageLink) {
verbose(`${hrefFromNotion} is an internal image link and will be skipped.`);
return markdownLink;
}


// TODO: This is a hotfix to dodge internal image links parsing
const imageFileExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.svg'];
const isImageLink = imageFileExtensions.some(ext => hrefFromNotion.endsWith(ext));
if (isImageLink) {
verbose(`Link parsing: [InternalLinkPlugin] ${hrefFromNotion} is an internal image link and will be skipped. Make sure it exists !`);
return markdownLink;
}

// Find the last occurrence of either '-' or '/' and take everything to the right to extract id and fragment
const lastSpecialCharIndex = Math.max(hrefFromNotion.lastIndexOf('-'), hrefFromNotion.lastIndexOf('/'));
if (lastSpecialCharIndex !== -1) {
hrefFromNotion = hrefFromNotion.substring(lastSpecialCharIndex + 1);
}

const allTabsPages = context.allTabsPages;
// find the page where pageId matches hrefFromNotion
const targetPage = allTabsPages.find(p => {
return p.matchesLinkId(hrefFromNotion);
});

if (!targetPage) {
// About this situation. See https://github.com/sillsdev/docu-notion/issues/9
warning(
`Link parsing: [InternalLinkPlugin] Could not find a local target for ${hrefFromNotion}. Note that links to other notions pages or outline sections are not supported > https://github.com/sillsdev/docu-notion/issues/9`
);
return `${labelFromNotion}†`;
let targetPage: { page: NotionPage | undefined, tab: string | undefined } = { page: undefined, tab: undefined }; for (const tab in context.allTabsPages) {
const foundPage = context.allTabsPages[tab].find(p => p.matchesLinkId(hrefFromNotion));
if (foundPage) {
targetPage = { page: foundPage, tab: tab };
break;
}
}

const label = convertLinkLabel(targetPage, labelFromNotion);
const url = convertLinkHref(context, targetPage, hrefFromNotion);
return `[${label}](${url})`;
if (targetPage.page && targetPage.tab) {
const label = convertLinkLabel(targetPage.page, labelFromNotion);
const url = convertLinkHref(context, targetPage.tab, targetPage.page, hrefFromNotion);
return `[${label}](${url})`;
} else {
warning(`Could not find a local target for ${hrefFromNotion}.`);
return `${labelFromNotion}[broken link]`;
}
}

/**
* Fixes the link label if it's a "mention" to display the page name or title instead.
*/
function convertLinkLabel(targetPage: NotionPage, text: string): string {
// In Notion, if you just add a link to a page without linking it to any text, then in Notion
// you see the name of the page as the text of the link. But when Notion gives us that same
// link, it uses "mention" as the text. So we have to look up the name of the page in
// order to fix that.;
if (text !== "mention") return text;
else return targetPage.nameOrTitle;
return text === "mention" ? targetPage.nameOrTitle : text;
}

/**
* Converts the URL to a local link format based on the context of the current tab.
* Appends fragment identifiers to the link if they exist.
* Note: The official Notion API does not include links to headings unless they are part of an inline link.
*/
function convertLinkHref(
context: IDocuNotionContext,
targetPage: NotionPage,
url: string
context: IDocuNotionContext,
tab: string,
page: NotionPage,
url: string
): string {
let convertedLink = context.layoutStrategy.getLinkPathForPage(targetPage);

/*****************************
NOTE: as of this writing, the official Notion API completely drops links
to headings, unless they are part of a inline link.
*******************************/
let convertedLink: string;
// If the link is from the current tab, use the direct link path to the page
if (tab == context.currentTab) {
convertedLink = context.layoutStrategy.getLinkPathForPage(page);
} else {
// If the link is from a different tab, prepend '/' and the tab name to the link path
convertedLink = "/" + tab + context.layoutStrategy.getLinkPathForPage(page);
}

// Include the fragment (# and after) if it exists
const { fragmentId } = parseLinkId(url);
// Log only if fragmentId is not an empty string
if (fragmentId !== "") {
verbose(`Link parsing: [InternalLinkPlugin] Parsed ${url} and got Fragment ID: ${fragmentId}`);
}
convertedLink += fragmentId;
// Extract the fragment identifier from the URL, if it exists
const { fragmentId } = parseLinkId(url);
if (fragmentId !== "") {
verbose(`[InternalLinkPlugin] Extracted Fragment ID from ${url}: ${fragmentId}`);
}
convertedLink += fragmentId;

//verbose(`Converting Link ${url} --> ${convertedLink}`);
return convertedLink;
return convertedLink;
}
// Parse the link ID to replace the base page ID (before the #) with its slug if exists, and replace the fragment (# and after) if exists.
export function parseLinkId(fullLinkId: string): {
baseLinkId: string; // before the #
fragmentId: string; // # and after
} {
const iHash: number = fullLinkId.indexOf("#");


/**
* Extracts the base link ID and fragment identifier from a full link ID.
*/
export function parseLinkId(fullLinkId: string): { baseLinkId: string; fragmentId: string } {
const iHash = fullLinkId.indexOf("#");
if (iHash >= 0) {
return {
baseLinkId: fullLinkId.substring(0, iHash),
Expand All @@ -135,14 +129,14 @@ export function parseLinkId(fullLinkId: string): {
return { baseLinkId: fullLinkId, fragmentId: "" };
}

/**
* Plugin for converting internal links to local links within Notion pages.
* Handles both "raw" and "inline" link formats from the Notion or notion-md source.
*/
export const standardInternalLinkConversion: IPlugin = {
name: "InternalLinkPlugin",
linkModifier: {
// from notion (or notion-md?) we get slightly different hrefs depending on whether the links is "inline"
// (has some other text that's been turned into a link) or "raw".
// Raw links come in without a leading slash, e.g. [mention](4a6de8c0-b90b-444b-8a7b-d534d6ec71a4)
// Inline links come in with a leading slash, e.g. [pointer to the introduction](/4a6de8c0b90b444b8a7bd534d6ec71a4)
match: /\[([^\]]+)?\]\((?!mailto:)(https:\/\/www\.notion\.so\/[^)]+|\/[^),]+)\)/,
convert: convertInternalLink,
},
};
};
Loading

0 comments on commit df600a1

Please sign in to comment.