Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

Commit

Permalink
perf: Loop over blocks only once
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Jun 23, 2019
1 parent 9925b44 commit 7cfbf73
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 172 deletions.
176 changes: 4 additions & 172 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,177 +1,9 @@
import { BLOCKS, MARKS, NOTION_BLOCKS, NOTION_MARKS } from './constants';
import isEmpty from './utils/is-empty';
import notionBlocksToRichTextNodes from './notion-blocks-to-rich-text-nodes';
import get from './utils/get';

const markMap = {
[NOTION_MARKS.BOLD]: MARKS.BOLD,
[NOTION_MARKS.ITALIC]: MARKS.ITALIC,
[NOTION_MARKS.CODE]: MARKS.CODE,
[NOTION_MARKS.HIGHLIGHT]: MARKS.HIGHLIGHT
};

function toMark(mark = []) {
const [typeId, data] = mark;
return {
type: markMap[typeId],
data
};
}

function toText(text = []) {
const [value, marks = []] = text;
return {
nodeType: 'text',
value,
marks: marks.map(toMark)
};
}

function toHeading(level) {
return block => {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: `heading-${level}`,
content: content.map(toText)
};
};
}

function toParagraph(block) {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: BLOCKS.PARAGRAPH,
content: content.map(toText)
};
}

function toQuote(block) {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: BLOCKS.QUOTE,
content: content.map(toText)
};
}

function toListItem(listType) {
return block => ({
listType,
nodeType: BLOCKS.LIST_ITEM,
content: [toParagraph(block)]
});
}

function toImage(block) {
const url = get(block, 'value.format.display_source');
const isHostedByNotion = url.startsWith('/images/');
const src = isHostedByNotion ? `https://notion.so${url}` : url;
const caption = get(block, 'value.properties.caption[0]', []);
return {
nodeType: BLOCKS.EMBEDDED_ENTRY,
data: {
type: 'image',
src,
alt: caption[0],
caption: toText(caption)
},
content: []
};
}

function toHorizontalRule() {
return {
content: [],
nodeType: BLOCKS.HR
};
}

const transformerMap = {
[NOTION_BLOCKS.HEADER]: toHeading(2),
[NOTION_BLOCKS.SUB_HEADER]: toHeading(3),
[NOTION_BLOCKS.SUB_SUB_HEADER]: toHeading(4),
[NOTION_BLOCKS.TEXT]: toParagraph,
[NOTION_BLOCKS.QUOTE]: toQuote,
[NOTION_BLOCKS.BULLETED_LIST]: toListItem(BLOCKS.UL_LIST),
[NOTION_BLOCKS.NUMBERED_LIST]: toListItem(BLOCKS.OL_LIST),
[NOTION_BLOCKS.IMAGE]: toImage,
[NOTION_BLOCKS.DIVIDER]: toHorizontalRule
};

function cleanSections(sections) {
const nodes = [];

let tmpListType = null;
let tmpListContent = null;

/* eslint-disable no-restricted-syntax, no-continue */
for (const section of sections) {
if (!section) {
continue;
}

const { listType, ...node } = section;

const isEndOfList = tmpListContent && tmpListType !== listType;

if (isEndOfList) {
nodes.push({
nodeType: tmpListType,
content: tmpListContent
});
tmpListType = null;
tmpListContent = null;
}

const isList = !!listType;

if (isList) {
const isStartOfList = !tmpListType && !tmpListContent;

if (isStartOfList) {
tmpListType = listType;
tmpListContent = [node];
} else {
tmpListContent.push(node);
}

continue;
}

nodes.push(node);
}

return nodes;
}
/* eslint-enable no-restricted-syntax, no-continue */

export default function richTextFromNotion(blocks = {}) {
const sections = Object.values(blocks).map(block => {
const type = get(block, 'value.type');
const transformerFn = transformerMap[type];

if (!transformerFn) {
return null;
}

return transformerFn(block);
});

const content = cleanSections(sections);

export default function richTextFromNotion(page) {
const blocks = get(page, 'recordMap.block', {});
const content = notionBlocksToRichTextNodes(blocks);
return {
nodeType: 'document',
data: {},
Expand Down
168 changes: 168 additions & 0 deletions src/notion-blocks-to-rich-text-nodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { BLOCKS, MARKS, NOTION_BLOCKS, NOTION_MARKS } from './constants';
import isEmpty from './utils/is-empty';
import get from './utils/get';

const markMap = {
[NOTION_MARKS.BOLD]: MARKS.BOLD,
[NOTION_MARKS.ITALIC]: MARKS.ITALIC,
[NOTION_MARKS.CODE]: MARKS.CODE,
[NOTION_MARKS.HIGHLIGHT]: MARKS.HIGHLIGHT
};

export function toMark(mark = []) {
const [typeId, data] = mark;
return {
type: markMap[typeId],
data
};
}

export function toText(text = []) {
const [value, marks = []] = text;
return {
nodeType: 'text',
value,
marks: marks.map(toMark)
};
}

export function toHeading(level) {
return block => {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: `heading-${level}`,
content: content.map(toText)
};
};
}

export function toParagraph(block) {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: BLOCKS.PARAGRAPH,
content: content.map(toText)
};
}

export function toQuote(block) {
const content = get(block, 'value.properties.title', []);

if (isEmpty(content)) {
return null;
}

return {
nodeType: BLOCKS.QUOTE,
content: content.map(toText)
};
}

export function toListItem(listType) {
return block => ({
listType,
nodeType: BLOCKS.LIST_ITEM,
content: [toParagraph(block)]
});
}

export function toImage(block) {
const url = get(block, 'value.format.display_source');
const isHostedByNotion = url.startsWith('/images/');
const src = isHostedByNotion ? `https://notion.so${url}` : url;
const caption = get(block, 'value.properties.caption[0]', []);
return {
nodeType: BLOCKS.EMBEDDED_ENTRY,
data: {
type: 'image',
src,
alt: caption[0],
caption: toText(caption)
},
content: []
};
}

export function toHorizontalRule() {
return {
content: [],
nodeType: BLOCKS.HR
};
}

const transformerMap = {
[NOTION_BLOCKS.HEADER]: toHeading(2),
[NOTION_BLOCKS.SUB_HEADER]: toHeading(3),
[NOTION_BLOCKS.SUB_SUB_HEADER]: toHeading(4),
[NOTION_BLOCKS.TEXT]: toParagraph,
[NOTION_BLOCKS.QUOTE]: toQuote,
[NOTION_BLOCKS.BULLETED_LIST]: toListItem(BLOCKS.UL_LIST),
[NOTION_BLOCKS.NUMBERED_LIST]: toListItem(BLOCKS.OL_LIST),
[NOTION_BLOCKS.IMAGE]: toImage,
[NOTION_BLOCKS.DIVIDER]: toHorizontalRule
};

export default function notionBlocksToRichTextNodes(blocks = {}) {
const nodes = [];

let tmpListType = null;
let tmpListContent = null;

/* eslint-disable no-restricted-syntax, no-continue */
for (const block of Object.values(blocks)) {
const type = get(block, 'value.type');
const transformerFn = transformerMap[type];

if (!transformerFn) {
continue;
}

const node = transformerFn(block);

if (!node) {
continue;
}

const { listType, ...rest } = node;

const isEndOfList = tmpListContent && tmpListType !== listType;

if (isEndOfList) {
nodes.push({
nodeType: tmpListType,
content: tmpListContent
});
tmpListType = null;
tmpListContent = null;
}

const isList = !!listType;

if (isList) {
const isStartOfList = !tmpListType && !tmpListContent;

if (isStartOfList) {
tmpListType = listType;
tmpListContent = [rest];
} else {
tmpListContent.push(rest);
}

continue;
}

nodes.push(rest);
}
/* eslint-enable no-restricted-syntax, no-continue */

return nodes;
}

0 comments on commit 7cfbf73

Please sign in to comment.