UNPKG

upfront-editable

Version:
209 lines (168 loc) 7.91 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); 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; exports.replaceAllQuotes = replaceAllQuotes; var _config = _interopRequireDefault(require("./config")); var string = _interopRequireWildcard(require("./util/string")); var nodeType = _interopRequireWildcard(require("./node-type")); var quotes = _interopRequireWildcard(require("./quotes")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } var allowedElements, requiredAttributes, transformElements, blockLevelElements, replaceQuotes; var splitIntoBlocks, blacklistedElements; var whitespaceOnly = /^\s*$/; var blockPlaceholder = '<!-- BLOCK -->'; var keepInternalRelativeLinks; updateConfig(_config["default"]); function updateConfig(conf) { var rules = conf.pastedHtmlRules; allowedElements = rules.allowedElements || {}; requiredAttributes = rules.requiredAttributes || {}; transformElements = rules.transformElements || {}; blacklistedElements = rules.blacklistedElements || []; keepInternalRelativeLinks = rules.keepInternalRelativeLinks || false; replaceQuotes = rules.replaceQuotes || {}; 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["default"].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); pasteHolder.remove(); element.removeAttribute(_config["default"].pastingAttribute); cursor.restore(); callback(blocks, cursor); }, 0); } /** * @param { Document } document */ function injectPasteholder(document) { var pasteHolder = document.createElement('div'); pasteHolder.setAttribute('contenteditable', true); pasteHolder.style.position = 'fixed'; pasteHolder.style.right = '5px'; pasteHolder.style.top = '50%'; pasteHolder.style.width = '1px'; pasteHolder.style.height = '1px'; pasteHolder.style.overflow = 'hidden'; pasteHolder.style.outline = 'none'; document.body.appendChild(pasteHolder); return pasteHolder; } /** * - 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(replaceAllQuotes(entry))); }).filter(function (entry) { return !whitespaceOnly.test(entry); }); } function filterHtmlElements(elem) { return Array.from(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 "<".concat(nodeName + attributes, ">"); if (!whitespaceOnly.test(content)) { return "<".concat(nodeName + attributes, ">").concat(content, "</").concat(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 "".concat(content, " "); return content; } // returns string of concatenated attributes e.g. 'target="_blank" rel="nofollow" href="/test.com"' function filterAttributes(nodeName, node) { return Array.from(node.attributes).reduce(function (attributes, _ref) { var name = _ref.name, value = _ref.value; if (allowedElements[nodeName][name] && value) { return "".concat(attributes, " ").concat(name, "=\"").concat(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" : ' '); }); } function replaceAllQuotes(str) { if (replaceQuotes.quotes || replaceQuotes.singleQuotes || replaceQuotes.apostrophe) { return quotes.replaceAllQuotes(str, replaceQuotes); } return str; }