This repository has been archived by the owner on Jan 16, 2024. It is now read-only.
generated from connor-baer/package-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a35e5dd
commit e3c3cc4
Showing
9 changed files
with
366 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { BLOCKS, MARKS as CONTENTFUL_MARKS } from '@contentful/rich-text-types'; | ||
|
||
export { BLOCKS }; | ||
|
||
export const MARKS = { | ||
...CONTENTFUL_MARKS, | ||
HIGHLIGHT: 'highlight' | ||
}; | ||
|
||
export const NOTION_BLOCKS = { | ||
PAGE: 'page', | ||
HEADER: 'header', | ||
SUB_HEADER: 'sub_header', | ||
SUB_SUB_HEADER: 'sub_sub_header', | ||
TEXT: 'text', | ||
QUOTE: 'quote', | ||
BULLETED_LIST: 'bulleted_list', | ||
NUMBERED_LIST: 'numbered_list', | ||
IMAGE: 'image', | ||
DIVIDER: 'divider', | ||
COLLECTION_VIEW: 'collection_view' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,180 @@ | ||
export default function sayHello(name) { | ||
return `Hello ${name}`; | ||
import { BLOCKS, MARKS, NOTION_BLOCKS } from './constants'; | ||
import isEmpty from './utils/is-empty'; | ||
import get from './utils/get'; | ||
|
||
const markMap = { | ||
b: MARKS.BOLD, | ||
i: MARKS.ITALIC, | ||
c: MARKS.CODE, | ||
h: 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); | ||
|
||
return { | ||
nodeType: 'document', | ||
data: {}, | ||
content | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
export default function get(obj, path, defaultValue) { | ||
if (!obj || !path) { | ||
return obj; | ||
} | ||
// Get the path as an array | ||
const segments = typeof path !== 'string' ? path : stringToSegments(path); | ||
|
||
// Cache the current object | ||
let current = obj; | ||
|
||
// For each item in the path, dig into the object | ||
for (let i = 0; i < segments.length; i += 1) { | ||
// If the item isn't found, return the default (or undefined) | ||
if (!current[segments[i]]) { | ||
return defaultValue; | ||
} | ||
|
||
// Otherwise, update the current value | ||
current = current[segments[i]]; | ||
} | ||
|
||
return current; | ||
} | ||
|
||
function stringToSegments(path) { | ||
// Split to an array from dot notation | ||
return path.split('.').reduce((allSegments, item) => { | ||
// Split to an array from bracket notation | ||
item.split(/\[([^}]+)\]/g).forEach(key => { | ||
// Push to the new array | ||
if (key.length > 0) { | ||
allSegments.push(key); | ||
} | ||
}); | ||
return allSegments; | ||
}, []); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import get from './get'; | ||
|
||
describe('get', () => { | ||
const obj = { | ||
foo: { | ||
bar: 1, | ||
baz: [ | ||
{ | ||
fizz: 'buzz' | ||
} | ||
] | ||
} | ||
}; | ||
|
||
it('should accept the path as an array', () => { | ||
const actual = get(obj, ['foo', 'bar']); | ||
expect(actual).toBe(1); | ||
}); | ||
|
||
it('should accept the path as a string', () => { | ||
const actual = get(obj, 'foo.bar'); | ||
expect(actual).toBe(1); | ||
}); | ||
|
||
it('should accept the path as a string with bracket notation', () => { | ||
const actual = get(obj, 'foo.baz[0].fizz'); | ||
expect(actual).toBe('buzz'); | ||
}); | ||
|
||
it('should return the whole object if no path is passed', () => { | ||
const actual = get(obj); | ||
expect(actual).toBe(obj); | ||
}); | ||
|
||
it('should return undefined if no object is passed', () => { | ||
const actual = get(); | ||
expect(actual).toBeUndefined(); | ||
}); | ||
|
||
it('should return undefined if the path does not exist', () => { | ||
const actual = get(obj, 'fizz.buzz'); | ||
expect(actual).toBeUndefined(); | ||
}); | ||
|
||
it('should return the default value if the path does not exist', () => { | ||
const actual = get(obj, 'fizz.buzz', 'default'); | ||
expect(actual).toBe('default'); | ||
}); | ||
}); |
Oops, something went wrong.