upfront-editable
Version:
Friendly contenteditable API
209 lines (168 loc) • 7.91 kB
JavaScript
;
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;
}