Skip to content
This repository has been archived by the owner on Apr 12, 2023. It is now read-only.

Commit

Permalink
Initial prototype without lists
Browse files Browse the repository at this point in the history
  • Loading branch information
rexxars committed Sep 21, 2017
1 parent bad70a5 commit b193b46
Show file tree
Hide file tree
Showing 37 changed files with 769 additions and 1,035 deletions.
5 changes: 4 additions & 1 deletion .eslintrc
Expand Up @@ -3,5 +3,8 @@
"node": true,
"browser": true
},
"extends": ["sanity", "prettier"]
"extends": ["sanity", "prettier"],
"rules": {
"id-length": ["warn", {"exceptions": ["i", "j", "x", "y", "z", "h"], "min": 2}]
}
}
9 changes: 5 additions & 4 deletions package.json
Expand Up @@ -2,10 +2,10 @@
"name": "@sanity/block-content-to-react",
"description": "React component for transforming Sanity block content to React components",
"version": "0.3.0",
"main": "lib/index.js",
"main": "lib/BlockContent.js",
"umd": "umd/index.min.js",
"scripts": {
"browserify": "NODE_ENV=production BROWSERIFY_ENV=build DEBUG='' browserify -t envify -g uglifyify lib/index.js -o umd/index.js --standalone=BlockContentAdapter --no-bundle-external",
"browserify": "NODE_ENV=production BROWSERIFY_ENV=build DEBUG='' browserify -t envify -g uglifyify lib/index.js -o umd/index.js --standalone=BlockContent --no-bundle-external",
"build": "npm run clean && npm run compile && npm run browserify && npm run minify",
"watch": "npm run compile -- --watch",
"clean": "rimraf lib coverage .nyc_output umd/*.js",
Expand All @@ -23,7 +23,8 @@
"dependencies": {
"@sanity/block-content-to-tree": "^0.3.0",
"in-publish": "^2.0.0",
"object-assign": "^4.1.1"
"object-assign": "^4.1.1",
"prop-types": "^15.5.10"
},
"devDependencies": {
"babel-cli": "^6.26.0",
Expand All @@ -37,7 +38,7 @@
"eslint-plugin-import": "^2.7.0",
"jest": "^21.0.2",
"nyc": "^11.2.1",
"prettier": "^1.6.1",
"prettier": "^1.7.0",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"rimraf": "^2.6.2",
Expand Down
78 changes: 78 additions & 0 deletions src/BlockContent.js
@@ -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
107 changes: 107 additions & 0 deletions src/buildMarksTree.js
@@ -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
70 changes: 0 additions & 70 deletions src/index.js

This file was deleted.

0 comments on commit b193b46

Please sign in to comment.