UNPKG

draftjs-md-converter-support-video

Version:

Converter for converting Draft.js state into Markdown and vice versa

257 lines (227 loc) 6.61 kB
'use strict'; const utils = require('../utils/utils'); const parse = require('@textlint/markdown-to-ast').parse; const defaultInlineStyles = { Strong: { type: 'BOLD', symbol: '__' }, Emphasis: { type: 'ITALIC', symbol: '*' } }; const defaultBlockStyles = { List: 'unordered-list-item', Header1: 'header-one', Header2: 'header-two', Header3: 'header-three', Header4: 'header-four', Header5: 'header-five', Header6: 'header-six', CodeBlock: 'code-block', BlockQuote: 'blockquote' }; const getBlockStyleForMd = (node, blockStyles) => { const style = node.type; const ordered = node.ordered; const depth = node.depth; if (style === 'List' && ordered) { return 'ordered-list-item'; } else if (style === 'Header') { return blockStyles[`${style}${depth}`]; } else if ( node.type === 'Paragraph' && node.children && node.children[0] && node.children[0].type === 'Image' ) { return 'atomic'; } else if (node.type === 'Paragraph' && node.raw && utils.testEmbed(node.raw)) { return 'atomic'; } return blockStyles[style]; }; const joinCodeBlocks = splitMd => { const opening = splitMd.indexOf('```'); const closing = splitMd.indexOf('```', opening + 1); if (opening >= 0 && closing >= 0) { const codeBlock = splitMd.slice(opening, closing + 1); const codeBlockJoined = codeBlock.join('\n'); const updatedSplitMarkdown = [ [].concat.apply([], splitMd).slice(0, opening), // ...splitMd.slice(0, opening), codeBlockJoined, // ...splitMd.slice(closing + 1) [].concat.apply([], splitMd).slice(0, closing + 1) ]; return joinCodeBlocks(updatedSplitMarkdown); } return splitMd; }; const splitMdBlocks = md => { const splitMd = md.split('\n'); // Process the split markdown include the // one syntax where there's an block level opening // and closing symbol with content in the middle. const splitMdWithCodeBlocks = joinCodeBlocks(splitMd); return splitMdWithCodeBlocks; }; // function mergeObj2(obj1, obj2) { // var obj3 = {}; // for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; } // for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; } // return obj3; // } const parseMdLine = (line, existingEntities, extraStyles = {}) => { const inlineStyles = { ...defaultInlineStyles, ...extraStyles.inlineStyles }; const blockStyles = { ...defaultBlockStyles, ...extraStyles.blockStyles }; const astString = parse(line); let text = ''; const inlineStyleRanges = []; const entityRanges = []; const entityMap = existingEntities; const addInlineStyleRange = (offset, length, style) => { inlineStyleRanges.push({ offset, length, style }); }; const getRawLength = children => children.reduce((prev, current) => prev + (current.value ? current.value.length : 0), 0); const addLink = child => { const entityKey = Object.keys(entityMap).length; entityMap[entityKey] = { type: 'LINK', mutability: 'MUTABLE', data: { url: child.url } }; entityRanges.push({ key: entityKey, length: getRawLength(child.children), offset: text.length }); }; const addImage = child => { const entityKey = Object.keys(entityMap).length; entityMap[entityKey] = { type: 'IMAGE', mutability: 'IMMUTABLE', data: { url: child.url, src: child.url, fileName: child.alt || '' } }; entityRanges.push({ key: entityKey, length: 1, offset: text.length }); }; const addVideo = child => { const string = child.raw; const url = utils.buildEmbeddedUrl(string); const entityKey = Object.keys(entityMap).length; entityMap[entityKey] = { type: 'EMBEDDED_LINK', mutability: 'IMMUTABLE', data: { src: url } }; entityRanges.push({ key: entityKey, length: 1, offset: text.length }); }; const parseChildren = (child, style) => { switch (child.type) { case 'Link': addLink(child); break; case 'Image': addImage(child); break; case 'Paragraph': if (utils.testEmbed(child.raw)) { addVideo(child); } break; default: } if (!utils.testEmbed(child.raw) && child.children && style) { const rawLength = getRawLength(child.children); addInlineStyleRange(text.length, rawLength, style.type); const newStyle = inlineStyles[child.type]; child.children.forEach(grandChild => { parseChildren(grandChild, newStyle); }); } else if (!utils.testEmbed(child.raw) && child.children) { const newStyle = inlineStyles[child.type]; child.children.forEach(grandChild => { parseChildren(grandChild, newStyle); }); } else { if (style) { addInlineStyleRange(text.length, child.value.length, style.type); } if (inlineStyles[child.type]) { addInlineStyleRange(text.length, child.value.length, inlineStyles[child.type].type); } text = `${text}${child.type === 'Image' || utils.testEmbed(child.raw) ? ' ' : child.value}`; } }; astString.children.forEach(child => { const style = inlineStyles[child.type]; parseChildren(child, style); }); // add block style if it exists let blockStyle = 'unstyled'; if (astString.children[0]) { const style = getBlockStyleForMd(astString.children[0], blockStyles); if (style) { blockStyle = style; } } return { text, inlineStyleRanges, entityRanges, blockStyle, entityMap }; }; function mdToDraftjs(mdString, extraStyles) { const paragraphs = splitMdBlocks(mdString); const blocks = []; let entityMap = {}; paragraphs.forEach(paragraph => { const result = parseMdLine(paragraph, entityMap, extraStyles); blocks.push({ text: result.text, type: result.blockStyle, depth: 0, inlineStyleRanges: result.inlineStyleRanges, entityRanges: result.entityRanges }); entityMap = result.entityMap; }); // add a default value // not sure why that's needed but Draftjs convertToRaw fails without it if (Object.keys(entityMap).length === 0) { entityMap = { data: '', mutability: '', type: '' }; } return { blocks, entityMap }; } module.exports.mdToDraftjs = mdToDraftjs; module.exports.buildEmbeddedUrl = utils.buildEmbeddedUrl; module.exports.getId = utils.getId; module.exports.getEmbedProvider = utils.getEmbedProvider;