UNPKG

@20minutes/draft-convert

Version:

Extensibly serialize & deserialize Draft.js ContentState

87 lines (86 loc) 4.57 kB
import DraftJS from 'draft-js'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; import invariant from 'tiny-invariant'; import blockEntities from './blockEntities.mjs'; import blockInlineStyles from './blockInlineStyles.mjs'; import defaultBlockHTML from './default/defaultBlockHTML.mjs'; import encodeBlock from './encodeBlock.mjs'; import accumulateFunction from './util/accumulateFunction.mjs'; import blockTypeObjectFunction from './util/blockTypeObjectFunction.mjs'; import getBlockTags from './util/getBlockTags.mjs'; import getNestedBlockTags from './util/getNestedBlockTags.mjs'; const { convertToRaw } = DraftJS; const defaultEntityToHTML = (_entity, originalText)=>originalText; const defaultValidateHTML = (_html)=>true; const convertToHTML = ({ styleToHTML = {}, blockToHTML = {}, entityToHTML = defaultEntityToHTML, validateHTML = defaultValidateHTML })=>(contentState)=>{ invariant(contentState !== null && contentState !== undefined, 'Expected contentState to be non-null'); let getBlockHTML; if (blockToHTML.__isMiddleware === true) { getBlockHTML = blockToHTML(blockTypeObjectFunction(defaultBlockHTML)); } else { getBlockHTML = accumulateFunction(blockTypeObjectFunction(blockToHTML), blockTypeObjectFunction(defaultBlockHTML)); } const rawState = convertToRaw(contentState); let listStack = []; let result = rawState.blocks.map((block)=>{ const { type, depth } = block; let closeNestTags = ''; let openNestTags = ''; const blockHTMLResult = getBlockHTML(block); if (!blockHTMLResult) { throw new Error(`convertToHTML: missing HTML definition for block with type ${block.type}`); } if (!blockHTMLResult.nest) { // this block can't be nested, so reset all nesting if necessary closeNestTags = listStack.reduceRight((string, nestedBlock)=>string + getNestedBlockTags(getBlockHTML(nestedBlock), depth).nestEnd, ''); listStack = []; } else { while(depth + 1 !== listStack.length || type !== listStack[depth].type){ if (depth + 1 === listStack.length) { // depth is right but doesn't match type const blockToClose = listStack[depth]; closeNestTags += getNestedBlockTags(getBlockHTML(blockToClose), depth).nestEnd; openNestTags += getNestedBlockTags(getBlockHTML(block), depth).nestStart; listStack[depth] = block; } else if (depth + 1 < listStack.length) { const blockToClose = listStack[listStack.length - 1]; closeNestTags += getNestedBlockTags(getBlockHTML(blockToClose), depth).nestEnd; listStack = listStack.slice(0, -1); } else { openNestTags += getNestedBlockTags(getBlockHTML(block), depth).nestStart; listStack.push(block); } } } const innerHTML = blockInlineStyles(blockEntities(encodeBlock(block), rawState.entityMap, entityToHTML), styleToHTML); const blockHTML = getBlockTags(getBlockHTML(block)); let html; if (typeof blockHTML === 'string') { html = blockHTML; } else { html = blockHTML.start + innerHTML + blockHTML.end; } if (innerHTML.length === 0 && Object.hasOwn(blockHTML, 'empty')) { if (/*#__PURE__*/ React.isValidElement(blockHTML.empty)) { html = ReactDOMServer.renderToStaticMarkup(blockHTML.empty); } else { html = blockHTML.empty; } } const finalHtml = closeNestTags + openNestTags + html; if (!validateHTML(finalHtml)) { return ''; } return finalHtml; }).join(''); result = listStack.reduce((res, nestBlock)=>res + getNestedBlockTags(getBlockHTML(nestBlock), nestBlock.depth).nestEnd, result); return result; }; export default ((...args)=>{ if (args.length === 1 && Object.hasOwn(args[0], '_map') && args[0].getBlockMap != null) { // skip higher-order function and use defaults return convertToHTML({})(...args); } return convertToHTML(...args); });