UNPKG

upfront-editable

Version:
215 lines (172 loc) 6.78 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _from = require('babel-runtime/core-js/array/from'); var _from2 = _interopRequireDefault(_from); exports.updateConfig = updateConfig; exports.paste = paste; exports.injectPasteholder = injectPasteholder; exports.parseContent = parseContent; exports.filterHtmlElements = filterHtmlElements; exports.conditionalNodeWrap = conditionalNodeWrap; exports.filterAttributes = filterAttributes; exports.transformNodeName = transformNodeName; exports.hasRequiredAttributes = hasRequiredAttributes; exports.shouldKeepNode = shouldKeepNode; exports.cleanWhitespace = cleanWhitespace; var _jquery = require('jquery'); var _jquery2 = _interopRequireDefault(_jquery); var _config = require('./config'); var config = _interopRequireWildcard(_config); var _string = require('./util/string'); var string = _interopRequireWildcard(_string); var _nodeType = require('./node-type'); var nodeType = _interopRequireWildcard(_nodeType); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var allowedElements = void 0, requiredAttributes = void 0, transformElements = void 0, blockLevelElements = void 0; var splitIntoBlocks = void 0, blacklistedElements = void 0; var whitespaceOnly = /^\s*$/; var blockPlaceholder = '<!-- BLOCK -->'; var keepInternalRelativeLinks = void 0; updateConfig(config); function updateConfig(config) { var rules = config.pastedHtmlRules; allowedElements = rules.allowedElements || {}; requiredAttributes = rules.requiredAttributes || {}; transformElements = rules.transformElements || {}; blacklistedElements = rules.blacklistedElements || []; keepInternalRelativeLinks = rules.keepInternalRelativeLinks || false; blockLevelElements = {}; rules.blockLevelElements.forEach(function (name) { blockLevelElements[name] = true; }); splitIntoBlocks = {}; rules.splitIntoBlocks.forEach(function (name) { splitIntoBlocks[name] = true; }); } function paste(element, cursor, callback) { var document = element.ownerDocument; element.setAttribute(config.pastingAttribute, true); if (cursor.isSelection) cursor = cursor.deleteContent(); // Create a placeholder and set the focus to the pasteholder // to redirect the browser pasting into the pasteholder. cursor.save(); var pasteHolder = injectPasteholder(document); pasteHolder.focus(); // Use a timeout to give the browser some time to paste the content. // After that grab the pasted content, filter it and restore the focus. setTimeout(function () { var blocks = parseContent(pasteHolder); (0, _jquery2.default)(pasteHolder).remove(); element.removeAttribute(config.pastingAttribute); cursor.restore(); callback(blocks, cursor); }, 0); } function injectPasteholder(document) { var pasteHolder = (0, _jquery2.default)('<div>').attr('contenteditable', true).css({ position: 'fixed', right: '5px', top: '50%', width: '1px', height: '1px', overflow: 'hidden', outline: 'none' }); (0, _jquery2.default)(document.body).append(pasteHolder); return pasteHolder.get(0); } /** * - Parse pasted content * - Split it up into blocks * - clean and normalize every block * - optionally strip the host location an anchorTag-href * www.livindocs.io/internalLink -> /internalLink * * @param {DOM node} A container where the pasted content is located. * @returns {Array of Strings} An array of cleaned innerHTML like strings. */ function parseContent(element) { // Filter pasted content return filterHtmlElements(element) // Handle Blocks .split(blockPlaceholder).map(function (entry) { return string.trim(cleanWhitespace(entry)); }).filter(function (entry) { return !whitespaceOnly.test(entry); }); } function filterHtmlElements(elem) { return (0, _from2.default)(elem.childNodes).reduce(function (content, child) { if (blacklistedElements.indexOf(child.nodeName.toLowerCase()) !== -1) { return ''; } // Keep internal relative links relative (on paste). if (keepInternalRelativeLinks && child.nodeName === 'A' && child.href) { var stripInternalHost = child.getAttribute('href').replace(window.location.origin, ''); child.setAttribute('href', stripInternalHost); } if (child.nodeType === nodeType.elementNode) { var childContent = filterHtmlElements(child); return content + conditionalNodeWrap(child, childContent); } // Escape HTML characters <, > and & if (child.nodeType === nodeType.textNode) return content + string.escapeHtml(child.nodeValue); return content; }, ''); } function conditionalNodeWrap(child, content) { var nodeName = child.nodeName.toLowerCase(); nodeName = transformNodeName(nodeName); if (shouldKeepNode(nodeName, child)) { var attributes = filterAttributes(nodeName, child); if (nodeName === 'br') return '<' + (nodeName + attributes) + '>'; if (!whitespaceOnly.test(content)) { return '<' + (nodeName + attributes) + '>' + content + '</' + nodeName + '>'; } return content; } if (splitIntoBlocks[nodeName]) { return blockPlaceholder + content + blockPlaceholder; } // prevent missing whitespace between text when block-level // elements are removed. if (blockLevelElements[nodeName]) return content + ' '; return content; } // returns string of concatenated attributes e.g. 'target="_blank" rel="nofollow" href="/test.com"' function filterAttributes(nodeName, node) { return (0, _from2.default)(node.attributes).reduce(function (attributes, _ref) { var name = _ref.name, value = _ref.value; if (allowedElements[nodeName][name] && value) { return attributes + ' ' + name + '="' + value + '"'; } return attributes; }, ''); } function transformNodeName(nodeName) { return transformElements[nodeName] || nodeName; } function hasRequiredAttributes(nodeName, node) { var requiredAttrs = requiredAttributes[nodeName]; if (!requiredAttrs) return true; return !requiredAttrs.some(function (name) { return !node.getAttribute(name); }); } function shouldKeepNode(nodeName, node) { return allowedElements[nodeName] && hasRequiredAttributes(nodeName, node); } function cleanWhitespace(str) { return str.replace(/\n/g, ' ').replace(/ {2,}/g, ' ').replace(/(.)\u00A0/g, function (match, group) { return group + (/[\u0020]/.test(group) ? '\xA0' : ' '); }); }