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

Commit

Permalink
feat: Optionally preserve column layout
Browse files Browse the repository at this point in the history
  • Loading branch information
connor-baer committed Jun 29, 2019
1 parent 46eafaa commit e24d9e5
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 23 deletions.
16 changes: 13 additions & 3 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import {
BLOCKS,
BLOCKS as CONTENTFUL_BLOCKS,
MARKS as CONTENTFUL_MARKS,
INLINES
} from '@contentful/rich-text-types';

export { BLOCKS, INLINES };
export { INLINES };

export const BLOCKS = {
...CONTENTFUL_BLOCKS,
CALLOUT: 'callout',
COLUMN_LIST: 'column-list',
COLUMN: 'column'
};

export const MARKS = {
...CONTENTFUL_MARKS,
Expand All @@ -22,7 +29,10 @@ export const NOTION_BLOCKS = {
NUMBERED_LIST: 'numbered_list',
IMAGE: 'image',
DIVIDER: 'divider',
COLLECTION_VIEW: 'collection_view'
COLLECTION_VIEW: 'collection_view',
CALLOUT: 'callout',
COLUMN_LIST: 'column_list',
COLUMN: 'column'
};

export const NOTION_MARKS = {
Expand Down
19 changes: 10 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import notionBlocksToRichTextNodes from './notion-blocks-to-rich-text-nodes';
import notionBlocksToRichTextNodes, {
toDocument
} from './notion-blocks-to-rich-text-nodes';
import get from './utils/get';

export {
Expand All @@ -11,18 +13,17 @@ export {

const defaultOptions = {
transformFns: {},
fallbackTransformFn: () => null
defaultTransformFn: () => null,
preserveLayout: false
};

export function richTextFromNotion(page, options) {
const blocks = get(page, 'recordMap.block', {});
export function richTextFromNotion(data, options = {}) {
const blocks = get(data, 'recordMap.block', {});
const page = Object.values(blocks)[0];
const content = notionBlocksToRichTextNodes(blocks, {
...defaultOptions,
...options
});
return {
nodeType: 'document',
data: {},
content
};

return toDocument(page, content);
}
131 changes: 120 additions & 11 deletions src/notion-blocks-to-rich-text-nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ const markMap = {
[NOTION_MARKS.HIGHLIGHT]: MARKS.HIGHLIGHT
};

function getIcon(block) {
return get(block, 'value.format.page_icon');
}

function getImageSrc(url) {
const isHostedByNotion = url.startsWith('/images/');
return isHostedByNotion ? `https://notion.so${url}` : url;
}

export function toDocument(page, content) {
const icon = getIcon(page);
const url = get(page, 'value.format.page_cover');
const src = getImageSrc(url);
const position = get(page, 'value.format.page_cover_position');
return {
nodeType: 'document',
data: {
icon,
image: {
src,
position
}
},
content
};
}

export function toMark(mark = []) {
const [typeId, data] = mark;
return {
Expand Down Expand Up @@ -100,8 +127,7 @@ export function toListItem(listType) {

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 src = getImageSrc(url);
const caption = get(block, 'value.properties.caption[0]', []);
return {
nodeType: BLOCKS.EMBEDDED_ENTRY,
Expand All @@ -122,38 +148,120 @@ export function toHorizontalRule() {
};
}

export function toCallout(block) {
const content = get(block, 'value.properties.title', []);
const icon = getIcon(block);
const color = get(block, 'value.format.block_color');

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

return {
nodeType: BLOCKS.CALLOUT,
data: { icon, color },
content: content.map(toText)
};
}

export function toColumn(block, blocks, config) {
const ratio = get(block, 'value.format.column_ratio');
const contentIds = get(block, 'value.content');
const contentBlocks = contentIds.reduce(
(allBlocks, id) => ({ ...allBlocks, [id]: blocks[id] }),
{}
);
const content = notionBlocksToRichTextNodes(contentBlocks, config);
return {
nodeType: BLOCKS.COLUMN,
data: {
ratio,
length: contentIds.length
},
content,
ids: contentIds
};
}

export function toColumnList(block, blocks, config) {
const columnIds = get(block, 'value.content');
const result = columnIds.reduce(
(allColumns, id) => {
const columnBlock = blocks[id];
const column = toColumn(columnBlock, blocks, config);
const { ids, ...rest } = column;
allColumns.ids.push(...ids);
allColumns.columns.push(rest);
return allColumns;
},
{ ids: [], columns: [] }
);
return {
nodeType: BLOCKS.COLUMN_LIST,
data: {
length: columnIds.length
},
content: result.columns,
ids: [...columnIds, ...result.ids]
};
}

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

export default function notionBlocksToRichTextNodes(blocks = {}, options = {}) {
const { transformFns: customTransformFns, fallbackTransformFn } = options;
const {
transformFns: customTransformFns,
defaultTransformFn,
preserveLayout
} = options;
const transformFns = { ...defaultTransformerFns, ...customTransformFns };

if (!preserveLayout) {
transformFns[NOTION_BLOCKS.COLUMN_LIST] = null;
}

const config = { transformFns, defaultTransformFn };
const entries = Object.entries(blocks);
const completedIds = [];
const nodes = [];

let tmpListType = null;
let tmpListContent = null;

/* eslint-disable no-restricted-syntax, no-continue */
for (const block of Object.values(blocks)) {
for (const entry of entries) {
const [id, block] = entry;

if (completedIds.includes(id)) {
continue;
}

const type = get(block, 'value.type');
const transformerFn = transformFns[type] || fallbackTransformFn;
const transformerFn = transformFns[type] || defaultTransformFn;

const node = transformerFn(block);
const node = transformerFn(block, blocks, config);

if (!node) {
continue;
}

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

if (ids) {
completedIds.push(...ids);
}

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

Expand Down Expand Up @@ -181,6 +289,7 @@ export default function notionBlocksToRichTextNodes(blocks = {}, options = {}) {
continue;
}

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

0 comments on commit e24d9e5

Please sign in to comment.