Skip to content
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

feat: link to page #619

Merged
merged 4 commits into from
Apr 24, 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
114 changes: 61 additions & 53 deletions server/lib/notion/BlockHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import BlockEquation from './blocks/BlockEquation';
import renderFront from './helpers/renderFront';
import perserveNewlinesIfApplicable from './helpers/perserveNewlinesIfApplicable';
import getDeckName from '../anki/getDeckname';
import LinkToPage from './blocks/LinkToPage';

class BlockHandler {
api: NotionAPIWrapper;
Expand All @@ -49,7 +50,11 @@ class BlockHandler {
useAll: boolean = false;
settings: Settings;

constructor(exporter: CustomExporter, api: NotionAPIWrapper, settings: Settings) {
constructor(
exporter: CustomExporter,
api: NotionAPIWrapper,
settings: Settings
) {
this.exporter = exporter;
this.api = api;
this.skip = [];
Expand Down Expand Up @@ -169,7 +174,7 @@ class BlockHandler {
back += await BlockChildPage(c, this);
break;
case 'to_do':
back += BlockTodoList(c, this);
back += await BlockTodoList(c, response, this);
break;
case 'callout':
back += BlockCallout(c, this);
Expand Down Expand Up @@ -198,6 +203,9 @@ class BlockHandler {
case 'equation':
back += BlockEquation(c);
break;
case 'link_to_page':
back += await LinkToPage(c, this);
break;
default:
/* @ts-ignore */
back += `unsupported: ${c.type}`;
Expand Down Expand Up @@ -257,62 +265,63 @@ class BlockHandler {

let counter = 0;
for (const block of flashcardBlocks) {
// Assume it's a basic card then check for children
const name = await renderFront(block, this);
let back: null | string = '';
if (isColumnList(block) && rules.useColums()) {
const secondColumn = await getColumn(block.id, this, 1);
if (secondColumn) {
back = await BlockColumn(secondColumn, this)
}
} else {
back = await this.getBackSide(block);
// Assume it's a basic card then check for children
const name = await renderFront(block, this);
let back: null | string = '';
if (isColumnList(block) && rules.useColums()) {
const secondColumn = await getColumn(block.id, this, 1);
if (secondColumn) {
back = await BlockColumn(secondColumn, this);
}
const ankiNote = new Note(name, back || '');
ankiNote.media = this.exporter.media;
let isBasicType = true;
// Look for cloze deletion cards
if (settings.isCloze) {
const clozeCard = await getClozeDeletionCard(rules, block);
if (clozeCard) {
isBasicType = false;
}
clozeCard && ankiNote.copyValues(clozeCard);
} else {
back = await this.getBackSide(block);
}
const ankiNote = new Note(name, back || '');
ankiNote.media = this.exporter.media;
let isBasicType = true;
// Look for cloze deletion cards
if (settings.isCloze) {
const clozeCard = await getClozeDeletionCard(rules, block);
if (clozeCard) {
isBasicType = false;
}
// Look for input cards
if (settings.useInput) {
const inputCard = await getInputCard(rules, block);
if (inputCard) {
isBasicType = false;
}
inputCard && ankiNote.copyValues(inputCard);
clozeCard && ankiNote.copyValues(clozeCard);
}
// Look for input cards
if (settings.useInput) {
const inputCard = await getInputCard(rules, block);
if (inputCard) {
isBasicType = false;
}
inputCard && ankiNote.copyValues(inputCard);
}

ankiNote.back = back!;
ankiNote.notionLink = this.__notionLink(block.id, notionBaseLink);
if (settings.addNotionLink) {
ankiNote.back += RenderNotionLink(ankiNote.notionLink!, this);
}
ankiNote.notionId = settings.useNotionId ? block.id : undefined;
ankiNote.media = this.exporter.media;
this.exporter.media = [];
ankiNote.back = back!;
ankiNote.notionLink = this.__notionLink(block.id, notionBaseLink);
if (settings.addNotionLink) {
ankiNote.back += RenderNotionLink(ankiNote.notionLink!, this);
}
ankiNote.notionId = settings.useNotionId ? block.id : undefined;
ankiNote.media = this.exporter.media;
this.exporter.media = [];

const tr = TagRegistry.getInstance();
ankiNote.tags =
rules.TAGS === 'heading' ? tr.headings : tr.strikethroughs;
ankiNote.number = counter++;
const tr = TagRegistry.getInstance();
ankiNote.tags =
rules.TAGS === 'heading' ? tr.headings : tr.strikethroughs;
ankiNote.number = counter++;

ankiNote.name = perserveNewlinesIfApplicable(ankiNote.name, settings);
ankiNote.back = perserveNewlinesIfApplicable(ankiNote.back, settings);
ankiNote.name = perserveNewlinesIfApplicable(ankiNote.name, settings);
ankiNote.back = perserveNewlinesIfApplicable(ankiNote.back, settings);

cards.push(ankiNote);
if (
!settings.isCherry &&
(settings.basicReversed || ankiNote.hasRefreshIcon())
&& isBasicType) {
cards.push(ankiNote.reversed(ankiNote));
}
tr.clear();
cards.push(ankiNote);
if (
!settings.isCherry &&
(settings.basicReversed || ankiNote.hasRefreshIcon()) &&
isBasicType
) {
cards.push(ankiNote.reversed(ankiNote));
}
tr.clear();
}

if (settings.isCherry) {
Expand All @@ -335,7 +344,6 @@ class BlockHandler {
return cards; // .filter((c) => !c.isValid());
}


async findFlashcards(
topLevelId: string,
rules: ParserRules,
Expand Down Expand Up @@ -412,7 +420,7 @@ class BlockHandler {
if (settings.isAll) {
/* @ts-ignore */
const subDecks = blocks.filter((b) => b.type === rules.SUB_DECKS);
for (const sd of subDecks) {
for (const sd of subDecks) {
const subPage = await this.api.getPage(sd.id);
if (subPage) {
const nested = await this.findFlashcardsFromPage(
Expand Down
15 changes: 4 additions & 11 deletions server/lib/notion/blocks/BlockChildPage.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { GetBlockResponse } from "@notionhq/client/build/src/api-endpoints";
import ReactDOMServer from "react-dom/server";
import BlockHandler from "../BlockHandler";
import renderLink from "../helpers/renderLink";

export const BlockChildPage = (
export const BlockChildPage = async (
block: GetBlockResponse,
handler: BlockHandler
) => {
/* @ts-ignore */
const childPage = block.child_page;
const api = handler.api;
const page = api.getPage(block.id) || {};
const page = await api.getPage(block.id);
/* @ts-ignore */
const icon = page.icon;

if (handler.settings?.isTextOnlyBack && childPage) {
return childPage.title;
}

return ReactDOMServer.renderToStaticMarkup(
<a id={block.id} href={`https://notion.so/${block.id.replace(/\-/g, "")}`}>
{icon && icon.type === "emoji" && (
<span className="icon">{icon.emoji}</span>
)}
{childPage.title}
</a>
);
return renderLink(childPage.title, block, icon);
};
16 changes: 16 additions & 0 deletions server/lib/notion/blocks/LinkToPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { GetBlockResponse } from '@notionhq/client/build/src/api-endpoints';
import BlockHandler from '../BlockHandler';
import renderLink from '../helpers/renderLink';

export default async function LinkToPage(
block: GetBlockResponse,
handler: BlockHandler
) {
/* @ts-ignore */
const linkToPage = block.link_to_page;
const page = await handler.api.getPage(linkToPage.page_id);
const title = await handler.api.getPageTitle(page, handler.settings);
/* @ts-ignore */
const icon = page.icon;
return renderLink(title, block, icon);
}
47 changes: 17 additions & 30 deletions server/lib/notion/blocks/lists/BlockTodoList.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,26 @@
import { GetBlockResponse } from '@notionhq/client/build/src/api-endpoints';
import { GetBlockResponse, ListBlockChildrenResponse } from '@notionhq/client/build/src/api-endpoints';
import ReactDOMServer from 'react-dom/server';
import BlockHandler from '../../BlockHandler';
import { styleWithColors } from '../../NotionColors';
import HandleBlockAnnotations from '../utils';
import { convert } from "html-to-text"
import getListItems from '../../helpers/getListItems';

export const BlockTodoList = (
export const BlockTodoList = async (
block: GetBlockResponse,
response: ListBlockChildrenResponse,
handler: BlockHandler
) => {
/* @ts-ignore */
const todo = block.to_do;
const text = todo.text;

const markup = ReactDOMServer.renderToStaticMarkup(
<ul id={block.id} className={`to-do-list${styleWithColors(todo.color)}`}>
{text.map((t: GetBlockResponse) => {
/* @ts-ignore */
const annotations = t.annotations;
/* @ts-ignore */
return (
<li>
<div
/* @ts-ignore */
className={`checkbox checkbox-${t.checked ? 'on' : 'off'}`}
></div>
{/* @ts-ignore */}
{HandleBlockAnnotations(annotations, t.text)}
</li>
);
})}
</ul>
);
if (handler.settings?.isTextOnlyBack) {
return convert(markup);
}
return markup;
/* @ts-ignore */
const list = block.to_do;
const items = await getListItems(response, handler, "to_do");
const listItems = items.filter(Boolean);
const markup = ReactDOMServer.renderToStaticMarkup(
<ul id={block.id} className={`to-do-list${styleWithColors(list.color)}`}>
{listItems}
</ul>
);
if (handler.settings?.isTextOnlyBack) {
return convert(markup);
}
return markup;
};
7 changes: 7 additions & 0 deletions server/lib/notion/blocks/media/BlockEmbed.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { GetBlockResponse } from "@notionhq/client/build/src/api-endpoints";
import { renderToStaticMarkup } from "react-dom/server";
import getYouTubeEmbedLink from "../../../parser/helpers/getYouTubeEmbedLink";
import getYouTubeID from "../../../parser/helpers/getYouTubeID";
import BlockHandler from "../../BlockHandler";

export const BlockEmbed = (c: GetBlockResponse, handler: BlockHandler) => {
Expand All @@ -18,6 +20,11 @@ export const BlockEmbed = (c: GetBlockResponse, handler: BlockHandler) => {
<a href={url}>{url}</a>
</div>
);
}

const yt = getYouTubeID(url);
if (yt) {
url = getYouTubeEmbedLink(yt);
}
}
return renderToStaticMarkup(
Expand Down
7 changes: 5 additions & 2 deletions server/lib/notion/blocks/media/BlockVideo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { GetBlockResponse } from "@notionhq/client/build/src/api-endpoints";
import { renderToStaticMarkup } from "react-dom/server";
import getYouTubeEmbedLink from "../../../parser/helpers/getYouTubeEmbedLink";
import getYouTubeID from "../../../parser/helpers/getYouTubeID";
import BlockHandler from "../../BlockHandler";

export const BlockVideo = (c: GetBlockResponse, handler: BlockHandler) => {
Expand All @@ -10,8 +12,9 @@ export const BlockVideo = (c: GetBlockResponse, handler: BlockHandler) => {
const video = c.video;
let url = video.external.url;
if (url) {
if (url.match("youtube.com/watch")) {
url = url.replace("watch?v=", "embed/");
const yt = getYouTubeID(url);
if (yt) {
url = getYouTubeEmbedLink(yt);
} else if (url.match("vimeo.com")) {
url = url.replace("vimeo.com/", "player.vimeo.com/video/");
const videoId = url.split("/").pop().split("?")[0];
Expand Down
9 changes: 9 additions & 0 deletions server/lib/notion/helpers/getBlockId.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import getBlockId from './getBlockId';

describe('getBlockId', () => {
test('should return the block id', () => {
expect(
getBlockId({ object: 'block', id: '1590db54-99fe-467c-a656-be319fe6ca8b' })
).toBe('1590db5499fe467ca656be319fe6ca8b');
});
});
7 changes: 7 additions & 0 deletions server/lib/notion/helpers/getBlockId.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { GetBlockResponse } from "@notionhq/client/build/src/api-endpoints";

export default function getBlockId(
block: GetBlockResponse
): string {
return block.id.replace(/-/g, '');
}
22 changes: 16 additions & 6 deletions server/lib/notion/helpers/getListItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { styleWithColors } from '../NotionColors';
import BlockHandler from '../BlockHandler';
import getChildren from './getChildren';

type ListType = "numbered_list_item" | "bulleted_list_item";
type ListType = 'numbered_list_item' | 'bulleted_list_item' | 'to_do';

export default async function getListItems(
response: ListBlockChildrenResponse,
Expand All @@ -21,14 +21,24 @@ export default async function getListItems(
}
const backSide = await getChildren(result, handler);
handler.skip.push(result.id);
const isTodo = type === 'to_do';
const checked = isTodo && list.checked ? 'to-do-children-checked' : 'to-do-children-unchecked'
const checkedClass = isTodo ? checked : '';

return (
<li
id={result.id}
className={`numbered-list${styleWithColors(list.color)}`}
>
<li id={result.id} className={`${styleWithColors(list.color)}`}>
{isTodo && (
<div
/* @ts-ignore */
className={`checkbox checkbox-${list.checked ? 'on' : 'off'}`}
></div>
)}
{renderTextChildren(list.text, handler.settings)}
{backSide && (
<div dangerouslySetInnerHTML={{ __html: backSide }}></div>
<div
className={`${checkedClass}`}
dangerouslySetInnerHTML={{ __html: backSide }}
></div>
)}
</li>
);
Expand Down
17 changes: 17 additions & 0 deletions server/lib/notion/helpers/renderLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GetBlockResponse } from "@notionhq/client/build/src/api-endpoints";
import ReactDOMServer from "react-dom/server";

export default function renderLink(
title: string,
block: GetBlockResponse,
icon?: {type: string, emoji: string},
) {
return ReactDOMServer.renderToStaticMarkup(
<a id={block.id} href={`https://notion.so/${block.id.replace(/\-/g, "")}`}>
{icon && icon.type === "emoji" && (
<span className="icon">{icon.emoji}</span>
)}
{title}
</a>
);
}
Loading