Last active
July 8, 2016 18:10
-
-
Save nsfmc/ac3ab1cc8b8525185a094e845a9fce21 to your computer and use it in GitHub Desktop.
a barely working node module that uses markdown-it (and lodash) to build draft content states
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
import MarkdownIt from 'markdown-it'; | |
import {convertFromRaw} from 'draft-js'; | |
import util from 'util'; | |
import _ from 'lodash'; | |
const flog = (whatever) => { | |
console.log(util.inspect(whatever, {depth: null, colors: true})); | |
}; | |
function parseToken(token, struct, tree, treeIndex) { | |
if (token.type === 'paragraph_open') { | |
struct.blocks.push({type: 'unstyled', depth: 0}); | |
} | |
if (token.type === 'heading_open') { | |
// do this because our editor doesn't support < h2 | |
let style = token.tag === 'h1' ? 'header-one' : 'header-two'; | |
struct.blocks.push({type: style, depth: 0}); | |
} | |
// When the parser encounters an inline token, it needs to run this | |
// parser over all its children | |
if (token.type === 'inline') { | |
for (let j = 0; j < token.children.length; j += 1) { | |
let inlineToken = token.children[j]; | |
struct = parseToken(inlineToken, struct); | |
} | |
} | |
if (token.type === 'text') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
lastBlock.text = lastBlock.text || ''; | |
lastBlock.text += token.content; | |
} | |
if (token.type === 'em_open') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
lastBlock.inlineStyleRanges = lastBlock.inlineStyleRanges || []; | |
lastBlock.inlineStyleRanges.push({ | |
style: 'ITALIC', | |
offset: lastBlock.text.length, | |
}); | |
} | |
if (token.type === 'em_close') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
lastBlock.inlineStyleRanges = lastBlock.inlineStyleRanges.map(styleRange => { | |
let lengthObject = {}; | |
if (styleRange.style === 'ITALIC') { | |
let length = lastBlock.text.length - styleRange.offset; | |
lengthObject.length = length; | |
} | |
let newRange = Object.assign({}, styleRange, lengthObject); | |
return newRange; | |
}); | |
} | |
if (token.type === 'strong_open') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
lastBlock.inlineStyleRanges = lastBlock.inlineStyleRanges || []; | |
lastBlock.inlineStyleRanges.push({ | |
style: 'BOLD', | |
offset: lastBlock.text.length, | |
}); | |
// how do we inform the next text block that it appends to this inlineStyleRange? | |
} | |
if (token.type === 'strong_close') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
lastBlock.inlineStyleRanges = lastBlock.inlineStyleRanges.map(styleRange => { | |
let lengthObject = {}; | |
if (styleRange.style === 'BOLD') { | |
let length = lastBlock.text.length - styleRange.offset; | |
lengthObject.length = length; | |
} | |
let newRange = Object.assign({}, styleRange, lengthObject); | |
return newRange; | |
}); | |
} | |
if (token.type === 'image') { | |
let attrs = _.fromPairs(token.attrs); | |
let entityKey = Object.keys(struct.entityMap).map(key => parseInt(key, 10)).sort((a,b) => a - b)[0] + 1 || 0; | |
struct.entityMap[entityKey] = { | |
type: 'image', | |
mutability: 'IMMUTABLE', | |
data: { | |
src: attrs.src, | |
}, | |
}; | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
const atomicBlock = Object.assign({}, lastBlock); | |
atomicBlock.text = ' '; | |
atomicBlock.type = 'atomic'; | |
atomicBlock.entityRanges = [ | |
{ | |
offset: 0, | |
length: 1, | |
key: entityKey, // replace this to find new max entity key | |
}, | |
]; | |
if (lastBlock.text && _.trim(lastBlock.text.length) > 0) { | |
// if the last block had actual text, end it and add this | |
// image as a new block | |
struct.blocks.push(atomicBlock); | |
} else { | |
// otherwise, replace empty blocks | |
struct.blocks[struct.blocks.length - 1] = atomicBlock; | |
} | |
} | |
if (token.type === 'link_open') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
let entityKey = Object.keys(struct.entityMap).map(key => parseInt(key, 10)).sort((a,b) => a - b)[0] + 1 || 0; | |
let attrs = _.fromPairs(token.attrs); | |
lastBlock.entityRanges = lastBlock.entityRanges || []; | |
struct.entityMap[entityKey] = { | |
type: 'LINK', | |
mutability: 'MUTABLE', | |
data: { | |
url: attrs.href, | |
}, | |
}; | |
lastBlock.entityRanges.push({ | |
offset: lastBlock.text && lastBlock.text.length || 0, | |
key: String(entityKey), | |
}); | |
// how do we inform the next text block that it appends to this inlineStyleRange? | |
} | |
if (token.type === 'link_close') { | |
let lastBlock = struct.blocks[struct.blocks.length - 1]; | |
let currentEntity = lastBlock.entityRanges[lastBlock.entityRanges.length - 1]; | |
let linkLength = lastBlock.text.length - currentEntity.offset; | |
currentEntity = Object.assign(currentEntity, {length: linkLength}); | |
} | |
return struct; | |
} | |
export function parseMarkdown(markdownText) { | |
const md = new MarkdownIt(); | |
let structure = {blocks: [], entityMap: {}}; | |
const tree = md.parse(markdownText); | |
for (let i = 0; i < tree.length; i += 1) { | |
let token = tree[i]; | |
structure = parseToken(token, structure, tree, i); | |
} | |
return structure; | |
} | |
export function convertFromMarkdown(markdownText) { | |
return convertFromRaw(parseMarkdown(markdownText)); | |
} |
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
import {expect} from 'chai'; | |
import MarkdownIt from 'markdown-it'; | |
import {parseMarkdown} from './convert-from-markdown'; | |
import {convertFromRaw} from 'draft-js'; | |
import util from 'util'; | |
const flog = (whatever) => { | |
console.log(util.inspect(whatever, {depth: null, colors: true})); | |
}; | |
describe('markdownit -> draft', function () { | |
it('makes a link', function () { | |
const markdown = '[foo](bar)'; | |
const raw = parseMarkdown(markdown); | |
// one entity | |
expect(Object.keys(raw.entityMap).length).to.equal(1); | |
// one block | |
expect(raw.blocks.length).to.equal(1); | |
// it's a link | |
expect(raw.entityMap['0'].type).to.equal('LINK'); | |
// goes to 'bar' | |
expect(raw.entityMap['0'].data.url).to.equal('bar'); | |
}); | |
it('makes a link in a paragraph', function () { | |
const markdown = 'oh [foo](bar)...'; | |
const raw = parseMarkdown(markdown); | |
// one entity | |
expect(Object.keys(raw.entityMap).length).to.equal(1); | |
// one block | |
expect(raw.blocks.length).to.equal(1); | |
// it's a link | |
expect(raw.entityMap['0'].type).to.equal('LINK'); | |
// goes to 'bar' | |
expect(raw.entityMap['0'].data.url).to.equal('bar'); | |
// still 3 chars long | |
expect(raw.blocks[0].entityRanges[0]['length']).to.equal(3); | |
// offset by 3 chars | |
expect(raw.blocks[0].entityRanges[0].offset).to.equal(3); | |
}); | |
it('makes an image, no alt text', function () { | |
const markdown = '![](imgsrc)'; | |
const raw = parseMarkdown(markdown); | |
// one entity | |
expect(Object.keys(raw.entityMap).length).to.equal(1); | |
// one block | |
expect(raw.blocks.length).to.equal(1); | |
// atomic | |
expect(raw.blocks[0].type).to.equal('atomic'); | |
// has image | |
expect(raw.entityMap['0'].type).to.equal('image'); | |
// with correct src | |
expect(raw.entityMap['0'].data.src).to.equal('imgsrc'); | |
}); | |
// it('makes an image, with alt text', function () { | |
// const markdown = '![alt](imgsrc)'; | |
// const raw = parseMarkdown(markdown); | |
// // with correct src | |
// expect(raw.entityMap['0'].data.alt).to.equal('alt'); | |
// }); | |
it('makes two blocks when finding an inline image', function () { | |
const markdown = 'here\'s some text ![alt](imgsrc)'; | |
const raw = parseMarkdown(markdown); | |
// one entity | |
expect(Object.keys(raw.entityMap).length).to.equal(1); | |
// twe blocks | |
expect(raw.blocks.length).to.equal(2); | |
}); | |
it('parses h1s', function () { | |
const markdown = '# foo'; | |
const raw = parseMarkdown(markdown); | |
// one block | |
expect(raw.blocks.length).to.equal(1); | |
// of type h1 | |
expect(raw.blocks[0].type).to.equal('header-one'); | |
}); | |
it('parses h2s', function () { | |
const markdown = '# foo'; | |
const raw = parseMarkdown(markdown); | |
// one block | |
expect(raw.blocks.length).to.equal(1); | |
// of type h1 | |
expect(raw.blocks[0].type).to.equal('header-one'); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment