UNPKG

@shelterzoom/draft-js-import-element

Version:
674 lines (540 loc) 19.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.stateFromElement = stateFromElement; exports["default"] = void 0; var _replaceTextWithMeta3 = _interopRequireDefault(require("./lib/replaceTextWithMeta")); var _draftJs = require("draft-js"); var _immutable = require("immutable"); var _draftJsUtils = require("draft-js-utils"); var _syntheticDom = require("synthetic-dom"); var _Constants = require("./lib/Constants"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var DATA_URL = /^data:/i; var NO_STYLE = (0, _immutable.OrderedSet)(); var NO_ENTITY = null; var EMPTY_BLOCK = new _draftJs.ContentBlock({ key: (0, _draftJs.genKey)(), text: '', type: _draftJsUtils.BLOCK_TYPE.UNSTYLED, characterList: (0, _immutable.List)(), depth: 0 }); var LINE_BREAKS = /(\r\n|\r|\n)/g; // We use `\r` because that character is always stripped from source (normalized // to `\n`), so it's safe to assume it will only appear in the text content when // we put it there as a placeholder. var SOFT_BREAK_PLACEHOLDER = '\r'; var ZERO_WIDTH_SPACE = "\u200B"; var DATA_ATTRIBUTE = /^data-([a-z0-9-]+)$/; // Map element attributes to entity data. var ELEM_ATTR_MAP = { a: { href: 'url', rel: 'rel', target: 'target', title: 'title' }, img: { src: 'src', alt: 'alt', width: 'width', height: 'height' } }; var getEntityData = function getEntityData(tagName, element) { var data = {}; if (ELEM_ATTR_MAP.hasOwnProperty(tagName)) { var attrMap = ELEM_ATTR_MAP[tagName]; for (var i = 0; i < element.attributes.length; i++) { var _element$attributes$i = element.attributes[i], name = _element$attributes$i.name, value = _element$attributes$i.value; if (typeof value === 'string') { var strVal = value; if (attrMap.hasOwnProperty(name)) { var newName = attrMap[name]; data[newName] = strVal; } else if (DATA_ATTRIBUTE.test(name)) { data[name] = strVal; } } } } return data; }; // Functions to create entities from elements. var ElementToEntity = { a: function a(generator, tagName, element) { var data = getEntityData(tagName, element); // Don't add `<a>` elements with invalid href. if (isAllowedHref(data.url)) { return generator.createEntity(_draftJsUtils.ENTITY_TYPE.LINK, data); } }, img: function img(generator, tagName, element) { var data = getEntityData(tagName, element); // Don't add `<img>` elements with no src. if (data.src != null) { return generator.createEntity(_draftJsUtils.ENTITY_TYPE.IMAGE, data); } } }; var ContentGenerator = /*#__PURE__*/ function () { // This will be passed to the customInlineFn to allow it // to return a Style() or Entity(). function ContentGenerator() { var _this = this; var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; _classCallCheck(this, ContentGenerator); _defineProperty(this, "contentStateForEntities", void 0); _defineProperty(this, "blockStack", void 0); _defineProperty(this, "blockList", void 0); _defineProperty(this, "depth", void 0); _defineProperty(this, "options", void 0); _defineProperty(this, "inlineCreators", { Style: function (_Style) { function Style(_x) { return _Style.apply(this, arguments); } Style.toString = function () { return _Style.toString(); }; return Style; }(function (style) { return { type: 'STYLE', style: style }; }), Entity: function Entity(type, data) { var mutability = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'MUTABLE'; return { type: 'ENTITY', entityKey: _this.createEntity(type, toStringMap(data), mutability) }; } }); this.options = options; this.contentStateForEntities = _draftJs.ContentState.createFromBlockArray([]); // This represents the hierarchy as we traverse nested elements; for // example [body, ul, li] where we must know li's parent type (ul or ol). this.blockStack = []; // This is a linear list of blocks that will form the output; for example // [p, li, li, blockquote]. this.blockList = []; this.depth = 0; } _createClass(ContentGenerator, [{ key: "process", value: function process(element) { this.processBlockElement(element); var contentBlocks = []; this.blockList.forEach(function (block) { var _concatFragments = concatFragments(block.textFragments), text = _concatFragments.text, characterMeta = _concatFragments.characterMeta; var includeEmptyBlock = false; // If the block contains only a soft break then don't discard the block, // but discard the soft break. if (text === SOFT_BREAK_PLACEHOLDER) { includeEmptyBlock = true; text = ''; } if (block.tagName === 'pre') { var _trimLeadingNewline = trimLeadingNewline(text, characterMeta); text = _trimLeadingNewline.text; characterMeta = _trimLeadingNewline.characterMeta; } else { var _collapseWhiteSpace = collapseWhiteSpace(text, characterMeta); text = _collapseWhiteSpace.text; characterMeta = _collapseWhiteSpace.characterMeta; } // Previously we were using a placeholder for soft breaks. Now that we // have collapsed whitespace we can change it back to normal line breaks. text = text.split(SOFT_BREAK_PLACEHOLDER).join('\n'); // Discard empty blocks (unless otherwise specified). if (text.length || includeEmptyBlock) { contentBlocks.push(new _draftJs.ContentBlock({ key: (0, _draftJs.genKey)(), text: text, type: block.type, characterList: characterMeta.toList(), depth: block.depth, data: block.data ? (0, _immutable.Map)(block.data) : (0, _immutable.Map)() })); } }); if (!contentBlocks.length) { contentBlocks = [EMPTY_BLOCK]; } return _draftJs.ContentState.createFromBlockArray(contentBlocks, this.contentStateForEntities.getEntityMap()); } }, { key: "getBlockTypeFromTagName", value: function getBlockTypeFromTagName(tagName) { var blockTypes = this.options.blockTypes; if (blockTypes && blockTypes[tagName]) { return blockTypes[tagName]; } switch (tagName) { case 'li': { var parent = this.blockStack.slice(-1)[0]; return parent.tagName === 'ol' ? _draftJsUtils.BLOCK_TYPE.ORDERED_LIST_ITEM : _draftJsUtils.BLOCK_TYPE.UNORDERED_LIST_ITEM; } case 'blockquote': { return _draftJsUtils.BLOCK_TYPE.BLOCKQUOTE; } case 'h1': { return _draftJsUtils.BLOCK_TYPE.HEADER_ONE; } case 'h2': { return _draftJsUtils.BLOCK_TYPE.HEADER_TWO; } case 'h3': { return _draftJsUtils.BLOCK_TYPE.HEADER_THREE; } case 'h4': { return _draftJsUtils.BLOCK_TYPE.HEADER_FOUR; } case 'h5': { return _draftJsUtils.BLOCK_TYPE.HEADER_FIVE; } case 'h6': { return _draftJsUtils.BLOCK_TYPE.HEADER_SIX; } case 'pre': { return _draftJsUtils.BLOCK_TYPE.CODE; } case 'figure': { return _draftJsUtils.BLOCK_TYPE.ATOMIC; } default: { return _draftJsUtils.BLOCK_TYPE.UNSTYLED; } } } }, { key: "processBlockElement", value: function processBlockElement(element) { if (!element) { return; } var customBlockFn = this.options.customBlockFn; var tagName = element.nodeName.toLowerCase(); var type; var data; if (customBlockFn) { var customBlock = customBlockFn(element); if (customBlock != null) { type = customBlock.type; data = customBlock.data; } } var isCustomType = true; if (type == null) { isCustomType = false; type = this.getBlockTypeFromTagName(tagName); } if (type === _draftJsUtils.BLOCK_TYPE.CODE) { var language = element.getAttribute('data-language'); if (language) { data = _objectSpread({}, data, { language: language }); } } var hasDepth = canHaveDepth(type); var allowRender = !_Constants.SPECIAL_ELEMENTS.hasOwnProperty(tagName); if (!isCustomType && !hasSemanticMeaning(type)) { var parent = this.blockStack.slice(-1)[0]; if (parent) { type = parent.type; } } var block = { tagName: tagName, textFragments: [], type: type, styleStack: [NO_STYLE], entityStack: [NO_ENTITY], depth: hasDepth ? this.depth : 0, data: data }; if (allowRender) { this.blockList.push(block); if (hasDepth) { this.depth += 1; } } this.blockStack.push(block); if (element.childNodes != null) { Array.from(element.childNodes).forEach(this.processNode, this); } this.blockStack.pop(); if (allowRender && hasDepth) { this.depth -= 1; } } }, { key: "processInlineElement", value: function processInlineElement(element) { var tagName = element.nodeName.toLowerCase(); if (tagName === 'br') { this.processText(SOFT_BREAK_PLACEHOLDER); return; } var block = this.blockStack.slice(-1)[0]; var style = block.styleStack.slice(-1)[0]; var entityKey = block.entityStack.slice(-1)[0]; var customInlineFn = this.options.customInlineFn; var customInline = customInlineFn ? customInlineFn(element, this.inlineCreators) : null; if (customInline != null) { switch (customInline.type) { case 'STYLE': { [].concat(customInline.style).forEach(function (customStyle) { return style = style.add(customStyle); }); break; } case 'ENTITY': { entityKey = customInline.entityKey; break; } } } else { style = addStyleFromTagName(style, tagName, this.options.elementStyles); if (ElementToEntity.hasOwnProperty(tagName)) { // If the to-entity function returns nothing, use the existing entity. entityKey = ElementToEntity[tagName](this, tagName, element) || entityKey; } } block.styleStack.push(style); block.entityStack.push(entityKey); if (element.childNodes != null) { Array.from(element.childNodes).forEach(this.processNode, this); } if (_Constants.SELF_CLOSING_ELEMENTS.hasOwnProperty(tagName)) { this.processText("\xA0"); } block.entityStack.pop(); block.styleStack.pop(); } }, { key: "processTextNode", value: function processTextNode(node) { var text = node.nodeValue; // This is important because we will use \r as a placeholder for a soft break. text = text.replace(LINE_BREAKS, '\n'); // Replace zero-width space (we use it as a placeholder in markdown) with a // soft break. // TODO: The import-markdown package should correctly turn breaks into <br> // elements so we don't need to include this hack. text = text.split(ZERO_WIDTH_SPACE).join(SOFT_BREAK_PLACEHOLDER); this.processText(text); } }, { key: "processText", value: function processText(text) { var block = this.blockStack.slice(-1)[0]; var style = block.styleStack.slice(-1)[0]; var entity = block.entityStack.slice(-1)[0]; var charMetadata = _draftJs.CharacterMetadata.create({ style: style, entity: entity }); var seq = (0, _immutable.Repeat)(charMetadata, text.length); block.textFragments.push({ text: text, characterMeta: seq }); } }, { key: "processNode", value: function processNode(node) { if (node.nodeType === _syntheticDom.NODE_TYPE_ELEMENT) { // $FlowIssue var _element = node; var _tagName = _element.nodeName.toLowerCase(); if (_Constants.INLINE_ELEMENTS.hasOwnProperty(_tagName)) { this.processInlineElement(_element); } else { this.processBlockElement(_element); } } else if (node.nodeType === _syntheticDom.NODE_TYPE_TEXT) { this.processTextNode(node); } } }, { key: "createEntity", value: function createEntity(type, data) { var mutability = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'MUTABLE'; this.contentStateForEntities = this.contentStateForEntities.createEntity(type, mutability, data); return this.contentStateForEntities.getLastCreatedEntityKey(); } }]); return ContentGenerator; }(); function trimLeadingNewline(text, characterMeta) { if (text.charAt(0) === '\n') { text = text.slice(1); characterMeta = characterMeta.slice(1); } return { text: text, characterMeta: characterMeta }; } function trimLeadingSpace(text, characterMeta) { while (text.charAt(0) === ' ') { text = text.slice(1); characterMeta = characterMeta.slice(1); } return { text: text, characterMeta: characterMeta }; } function trimTrailingSpace(text, characterMeta) { while (text.slice(-1) === ' ') { text = text.slice(0, -1); characterMeta = characterMeta.slice(0, -1); } return { text: text, characterMeta: characterMeta }; } function collapseWhiteSpace(text, characterMeta) { text = text.replace(/[ \t\n]/g, ' '); var _trimLeadingSpace = trimLeadingSpace(text, characterMeta); text = _trimLeadingSpace.text; characterMeta = _trimLeadingSpace.characterMeta; var _trimTrailingSpace = trimTrailingSpace(text, characterMeta); text = _trimTrailingSpace.text; characterMeta = _trimTrailingSpace.characterMeta; var i = text.length; while (i--) { if (text.charAt(i) === ' ' && text.charAt(i - 1) === ' ') { text = text.slice(0, i) + text.slice(i + 1); characterMeta = characterMeta.slice(0, i).concat(characterMeta.slice(i + 1)); } } // There could still be one space on either side of a softbreak. var _replaceTextWithMeta = (0, _replaceTextWithMeta3["default"])({ text: text, characterMeta: characterMeta }, SOFT_BREAK_PLACEHOLDER + ' ', SOFT_BREAK_PLACEHOLDER); text = _replaceTextWithMeta.text; characterMeta = _replaceTextWithMeta.characterMeta; var _replaceTextWithMeta2 = (0, _replaceTextWithMeta3["default"])({ text: text, characterMeta: characterMeta }, ' ' + SOFT_BREAK_PLACEHOLDER, SOFT_BREAK_PLACEHOLDER); text = _replaceTextWithMeta2.text; characterMeta = _replaceTextWithMeta2.characterMeta; return { text: text, characterMeta: characterMeta }; } function canHaveDepth(blockType) { switch (blockType) { case _draftJsUtils.BLOCK_TYPE.UNORDERED_LIST_ITEM: case _draftJsUtils.BLOCK_TYPE.ORDERED_LIST_ITEM: { return true; } default: { return false; } } } function concatFragments(fragments) { var text = ''; var characterMeta = (0, _immutable.Seq)(); fragments.forEach(function (textFragment) { text = text + textFragment.text; characterMeta = characterMeta.concat(textFragment.characterMeta); }); return { text: text, characterMeta: characterMeta }; } function addStyleFromTagName(styleSet, tagName, elementStyles) { switch (tagName) { case 'b': case 'strong': { return styleSet.add(_draftJsUtils.INLINE_STYLE.BOLD); } case 'i': case 'em': { return styleSet.add(_draftJsUtils.INLINE_STYLE.ITALIC); } case 'u': case 'ins': { return styleSet.add(_draftJsUtils.INLINE_STYLE.UNDERLINE); } case 'code': { return styleSet.add(_draftJsUtils.INLINE_STYLE.CODE); } case 's': case 'del': { return styleSet.add(_draftJsUtils.INLINE_STYLE.STRIKETHROUGH); } default: { // Allow custom styles to be provided. if (elementStyles && elementStyles[tagName]) { return styleSet.add(elementStyles[tagName]); } return styleSet; } } } function hasSemanticMeaning(blockType) { return blockType !== _draftJsUtils.BLOCK_TYPE.UNSTYLED; } function toStringMap(input) { var result = {}; if (input !== null && _typeof(input) === 'object' && !Array.isArray(input)) { var obj = input; for (var _i = 0, _Object$keys = Object.keys(obj); _i < _Object$keys.length; _i++) { var _key = _Object$keys[_i]; var value = obj[_key]; if (typeof value === 'string') { result[_key] = value; } } } return result; } function isAllowedHref(input) { if (input == null || input.match(DATA_URL)) { return false; } else { return true; } } function stateFromElement(element, options) { return new ContentGenerator(options).process(element); } var _default = stateFromElement; exports["default"] = _default;