This repository has been archived by the owner on Apr 12, 2023. It is now read-only.
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
Showing
37 changed files
with
769 additions
and
1,035 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,78 @@ | ||
const React = require('react') | ||
const PropTypes = require('prop-types') | ||
const objectAssign = require('object-assign') | ||
const buildMarksTree = require('./buildMarksTree') | ||
const {defaultSerializers, serializeSpan} = require('./serializers') | ||
|
||
const h = React.createElement | ||
|
||
function BlockContent(props) { | ||
const {blocks, className} = props | ||
const serializers = getSerializers(props.serializers) | ||
|
||
const serializeBlock = block => { | ||
const tree = buildMarksTree(block) | ||
const children = tree.map(span => serializeSpan(span, serializers)) | ||
const blockProps = { | ||
key: block._key, | ||
node: block, | ||
serializers | ||
} | ||
|
||
return h(serializers.block, blockProps, children) | ||
} | ||
|
||
return Array.isArray(blocks) | ||
? h('div', {className}, blocks.map(serializeBlock)) | ||
: serializeBlock(blocks) | ||
} | ||
|
||
// Recursively merge/replace default serializers with user-specified serializers | ||
function getSerializers(userSerializers) { | ||
return Object.keys(defaultSerializers).reduce((acc, key) => { | ||
if (typeof defaultSerializers[key] === 'function') { | ||
acc[key] = userSerializers[key] || defaultSerializers[key] | ||
} else { | ||
acc[key] = objectAssign({}, defaultSerializers[key], userSerializers[key]) | ||
} | ||
return acc | ||
}, {}) | ||
} | ||
|
||
// Expose default serializers to the user | ||
BlockContent.defaultSerializers = defaultSerializers | ||
|
||
BlockContent.propTypes = { | ||
className: PropTypes.string, | ||
|
||
serializers: PropTypes.shape({ | ||
// Common overrides | ||
types: PropTypes.object, | ||
marks: PropTypes.object, | ||
|
||
// Less common overrides | ||
list: PropTypes.func, | ||
listItem: PropTypes.func, | ||
|
||
// Low-level serializers | ||
block: PropTypes.func, | ||
span: PropTypes.func | ||
}), | ||
|
||
blocks: PropTypes.oneOfType([ | ||
PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
_type: PropTypes.string.isRequired | ||
}) | ||
), | ||
PropTypes.shape({ | ||
_type: PropTypes.string.isRequired | ||
}) | ||
]).isRequired | ||
} | ||
|
||
BlockContent.defaultProps = { | ||
serializers: BlockContent.defaultSerializers | ||
} | ||
|
||
module.exports = BlockContent |
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,107 @@ | ||
const defaultMarks = ['strong', 'em', 'code', 'underline', 'strike-through'] | ||
|
||
const buildMarksTree = block => { | ||
const {children, markDefs} = block | ||
const sortedMarks = children.map(sortMarksByOccurences) | ||
const rootNode = {children: []} | ||
let nodeStack = [rootNode] | ||
|
||
children.forEach((span, i) => { | ||
const marksNeeded = sortedMarks[i] | ||
|
||
let pos = 1 | ||
|
||
// Start at position one. Root is always plain and should never be removed. (?) | ||
if (nodeStack.length > 1) { | ||
for (pos; pos < nodeStack.length; pos++) { | ||
const mark = nodeStack[pos].markKey | ||
// eslint-disable-next-line max-depth | ||
if (!marksNeeded.includes(mark)) { | ||
break | ||
} | ||
|
||
const index = marksNeeded.indexOf(mark) | ||
marksNeeded.splice(index, 1) | ||
} | ||
} | ||
|
||
// Keep from beginning to first miss | ||
nodeStack = nodeStack.slice(0, pos) | ||
|
||
// Add needed nodes | ||
let currentNode = nodeStack[nodeStack.length - 1] | ||
marksNeeded.forEach(mark => { | ||
const node = { | ||
_type: 'span', | ||
_key: span._key, | ||
children: [], | ||
mark: markDefs.find(def => def._key === mark) || mark, | ||
markKey: mark | ||
} | ||
|
||
currentNode.children.push(node) | ||
nodeStack.push(node) | ||
currentNode = node | ||
}) | ||
|
||
currentNode.children.push(span.text) | ||
}) | ||
|
||
return rootNode.children | ||
} | ||
|
||
// We want to sort all the marks of all the spans in the following order: | ||
// 1. Marks that are shared amongst the most adjacent siblings | ||
// 2. Non-default marks (links, custom metadata) | ||
// 3. Built-in, plain marks (bold, emphasis, code etc) | ||
function sortMarksByOccurences(span, i, spans) { | ||
if (!span.marks || span.marks.length === 0) { | ||
return span.marks | ||
} | ||
|
||
const markOccurences = span.marks.reduce((occurences, mark) => { | ||
occurences[mark] = occurences[mark] ? occurences[mark] + 1 : 1 | ||
|
||
for (let siblingIndex = i + 1; siblingIndex < spans.length; siblingIndex++) { | ||
const sibling = spans[siblingIndex] | ||
if (sibling.marks.includes(mark)) { | ||
occurences[mark]++ | ||
} else { | ||
break | ||
} | ||
} | ||
|
||
return occurences | ||
}, {}) | ||
|
||
const sortByOccurence = sortMarks.bind(null, markOccurences) | ||
return span.marks.sort(sortByOccurence) | ||
} | ||
|
||
function sortMarks(occurences, markA, markB) { | ||
const aOccurences = occurences[markA] || 0 | ||
const bOccurences = occurences[markB] || 0 | ||
|
||
if (aOccurences !== bOccurences) { | ||
return bOccurences - aOccurences | ||
} | ||
|
||
const aDefaultPos = defaultMarks.indexOf(markA) | ||
const bDefaultPos = defaultMarks.indexOf(markB) | ||
|
||
// Sort default marks last | ||
if (aDefaultPos !== bDefaultPos) { | ||
return aDefaultPos - bDefaultPos | ||
} | ||
|
||
// Sort other marks simply by key | ||
if (markA < markB) { | ||
return -1 | ||
} else if (markA > markB) { | ||
return 1 | ||
} | ||
|
||
return 0 | ||
} | ||
|
||
module.exports = buildMarksTree |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.