rich-text-editor
Version:
Rich text editor
123 lines (122 loc) • 5.46 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.sanitize = sanitize;
exports.sanitizeText = sanitizeText;
const sanitize_html_1 = __importDefault(require("sanitize-html"));
const sanitizeOpts = {
allowedTags: ['img', 'br', 'span'],
allowedAttributes: {
img: ['src', 'alt'],
},
allowedSchemes: ['data'],
};
function sanitize(html, opts) {
let isInsideHeadTag = false;
return [
(v) => (0, sanitize_html_1.default)(v, Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, sanitizeOpts), { allowedTags: [...sanitizeOpts.allowedTags, 'div', 'p'], allowedSchemes: ['data', 'http', 'https'] }), opts), {
// We need to track if we are inside a head tag, because we don't want to add <br>s inside head tags
onOpenTag: (tag) => (tag === 'head' ? (isInsideHeadTag = true) : undefined), onCloseTag: (tag) => (tag === 'head' ? (isInsideHeadTag = false) : undefined), textFilter: (text, tagName) => {
if (isInsideHeadTag) {
return text;
}
// If the text is preformatted, make sure the line breaks in it are kept
const cleanedText = tagName === 'pre' ? preserveLineBreaks(text) : text;
if (cleanedText === '<br>') {
return '';
}
return cleanedText;
} }), opts)),
(v) => convertLinksToRelative(v),
(v) => stripBlockElements(v),
(v) => preserveIndentation(v),
(v) => preserveTabs(v),
].reduce((value, fn) => fn(value), html);
}
function convertLinksToRelative(html) {
return html.replace(new RegExp(document.location.origin, 'g'), '');
}
function isBlockElement(node) {
return node.nodeName === 'DIV' || node.nodeName === 'P';
}
// This is copied pretty much as-is from the legacy jQuery version;
// it's difficult to say *what exactly* it does but it attempts to
// change block-elements (namely `div` and `p`) into `br`s.
function stripBlockElements(html) {
const parent = document.createElement('div');
parent.innerHTML = html.trim();
do {
for (let i = 0; i < parent.childNodes.length; i++) {
const node = parent.childNodes[i];
if (isBlockElement(node)) {
// Preserve the line break that the block element represents by inserting a <br>
// before it, unless the previous sibling already provides one (or there is no
// content before it that needs separating).
if (needsBrBeforeBlock(node.previousSibling)) {
parent.insertBefore(document.createElement('br'), node);
}
// if this node has a last child that is not a br, add a br
if (node.lastChild && node.lastChild.nodeName !== 'BR') {
node.insertBefore(document.createElement('br'), null);
}
while (node.childNodes.length && node.firstChild !== null) {
parent.insertBefore(node.firstChild, node);
}
parent.removeChild(node);
}
}
} while (Array.prototype.some.call(parent.childNodes, (node) => isBlockElement(node)));
return parent.innerHTML;
}
function needsBrBeforeBlock(prevSibling) {
var _a;
if (prevSibling === null)
return false;
if (prevSibling.nodeName === 'BR')
return false;
// A preceding block will be unwrapped and contribute its own trailing <br>
if (isBlockElement(prevSibling))
return false;
// Whitespace-only text nodes and spans don't represent content that needs a line break
if ((prevSibling.nodeType === Node.TEXT_NODE || prevSibling.nodeName === 'SPAN') &&
!/\S/.test((_a = prevSibling.textContent) !== null && _a !== void 0 ? _a : '')) {
return false;
}
return true;
}
function preserveTabs(html) {
return html.replace(/\t/g, ' ');
}
function sanitizeText(text) {
return [escapeHtml, preserveLineBreaks, preserveIndentation].reduce((value, fn) => fn(value), text);
}
function preserveIndentation(html) {
const parent = document.createElement('div');
parent.innerHTML = html;
Array.from(parent.childNodes).forEach((node) => {
var _a, _b, _c, _d;
if (node.textContent) {
const nodeIsPrecededByBR = ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.nodeName) === 'BR';
if (nodeIsPrecededByBR) {
const spaces = (_c = (_b = node.textContent) === null || _b === void 0 ? void 0 : _b.match(/^(\s+)/)) === null || _c === void 0 ? void 0 : _c[0];
if (spaces) {
node.textContent = (_d = node.textContent) === null || _d === void 0 ? void 0 : _d.replace(spaces, spaces.replaceAll(' ', '\u00A0'));
}
}
}
});
return parent.innerHTML;
}
function preserveLineBreaks(text) {
return text.replaceAll(/\r/g, '').replaceAll(/\n/g, '<br>');
}
function escapeHtml(text) {
return String(text)
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}