UNPKG

@20minutes/draft-convert

Version:

Extensibly serialize & deserialize Draft.js ContentState

76 lines (75 loc) 4.31 kB
import invariant from 'tiny-invariant'; import defaultInlineHTML from './default/defaultInlineHTML.mjs'; import accumulateFunction from './util/accumulateFunction.mjs'; import getElementHTML from './util/getElementHTML.mjs'; import rangeSort from './util/rangeSort.mjs'; import styleObjectFunction from './util/styleObjectFunction.mjs'; const subtractStyles = (original, toRemove)=>original.filter((el)=>!toRemove.some((elToRemove)=>elToRemove.style === el.style)); const popEndingStyles = (styleStack, endingStyles)=>endingStyles.reduceRight((stack, style)=>{ const styleToRemove = stack[stack.length - 1]; invariant(styleToRemove.style === style.style, `Style ${styleToRemove.style} to be removed doesn't match expected ${style.style}`); return stack.slice(0, -1); }, styleStack); const characterStyles = (offset, ranges)=>ranges.filter((range)=>offset >= range.offset && offset < range.offset + range.length); const rangeIsSubset = (firstRange, secondRange)=>{ // returns true if the second range is a subset of the first const secondStartWithinFirst = firstRange.offset <= secondRange.offset; const secondEndWithinFirst = firstRange.offset + firstRange.length >= secondRange.offset + secondRange.length; return secondStartWithinFirst && secondEndWithinFirst; }; const latestStyleLast = (s1, s2)=>{ // make sure longer-lasting styles are added first const s2endIndex = s2.offset + s2.length; const s1endIndex = s1.offset + s1.length; return s2endIndex - s1endIndex; }; const getStylesToReset = (remainingStyles, newStyles)=>{ let i = 0; while(i < remainingStyles.length){ if (newStyles.every(rangeIsSubset.bind(null, remainingStyles[i]))) { i += 1; } else { return remainingStyles.slice(i); } } return []; }; const appendStartMarkup = (inlineHTML, string, styleRange)=>string + getElementHTML(inlineHTML(styleRange.style)).start; const prependEndMarkup = (inlineHTML, string, styleRange)=>getElementHTML(inlineHTML(styleRange.style)).end + string; const defaultCustomInlineHTML = (next)=>(style)=>next(style); defaultCustomInlineHTML.__isMiddleware = true; export default ((rawBlock, customInlineHTML = defaultCustomInlineHTML)=>{ invariant(rawBlock !== null && rawBlock !== undefined, 'Expected raw block to be non-null'); let inlineHTML; if (customInlineHTML.__isMiddleware === true) { inlineHTML = customInlineHTML(defaultInlineHTML); } else { inlineHTML = accumulateFunction(styleObjectFunction(customInlineHTML), styleObjectFunction(defaultInlineHTML)); } let result = ''; let styleStack = []; const sortedRanges = rawBlock.inlineStyleRanges.sort(rangeSort); const originalTextArray = [ ...rawBlock.text ]; for(let i = 0; i < originalTextArray.length; i += 1){ const styles = characterStyles(i, sortedRanges); const endingStyles = subtractStyles(styleStack, styles); const newStyles = subtractStyles(styles, styleStack); const remainingStyles = subtractStyles(styleStack, endingStyles); // reset styles: look for any already existing styles that will need to // end before styles that are being added on this character. to solve this // close out those current tags and all nested children, // then open new ones nested within the new styles. const resetStyles = getStylesToReset(remainingStyles, newStyles); const openingStyles = resetStyles.concat(newStyles).sort(latestStyleLast); const openingStyleTags = openingStyles.reduce(appendStartMarkup.bind(null, inlineHTML), ''); const endingStyleTags = endingStyles.concat(resetStyles).reduce(prependEndMarkup.bind(null, inlineHTML), ''); result += endingStyleTags + openingStyleTags + originalTextArray[i]; styleStack = popEndingStyles(styleStack, resetStyles.concat(endingStyles)); styleStack = styleStack.concat(openingStyles); invariant(styleStack.length === styles.length, `Character ${i}: ${styleStack.length - styles.length} styles left on stack that should no longer be there`); } result = styleStack.reduceRight((res, openStyle)=>res + getElementHTML(inlineHTML(openStyle.style)).end, result); return result; });