97 lines
3.3 KiB
JavaScript
97 lines
3.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
/**
|
||
|
* Removes any <code> wrappers around replacement strings {foo}
|
||
|
* Example input: <code><span>{foo}</span></code>
|
||
|
* Example output: <span>{foo}</span>
|
||
|
* @param {string} html
|
||
|
* @returns {string}
|
||
|
*/
|
||
|
function removeCodeWrappers(html) {
|
||
|
return html.replace(/<code\b[^>]*>((.*?){.*?}(.*?))<\/code>/gi, '$1');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parses an html string and returns a cleaned version
|
||
|
* @param {string} html
|
||
|
* @param {Object} _options
|
||
|
* @param {boolean} [_options.allowBr] - if true, <br> tags will be kept
|
||
|
* @param {boolean} [_options.firstChildInnerContent] - if true, only the innerHTML of the first element will be returned
|
||
|
* @param {boolean} [_options.removeCodeWrappers] - if true, <code> wrappers around replacement strings {foo} will be removed
|
||
|
* @returns {string}
|
||
|
*/
|
||
|
function cleanBasicHtml(html = '', _options = {}) {
|
||
|
const defaults = {};
|
||
|
const options = Object.assign({}, defaults, _options);
|
||
|
|
||
|
if (!options.createDocument) {
|
||
|
const Parser = (typeof DOMParser !== 'undefined' && DOMParser) || (typeof window !== 'undefined' && window.DOMParser);
|
||
|
|
||
|
if (!Parser) {
|
||
|
// eslint-disable-next-line ghost/ghost-custom/no-native-error
|
||
|
throw new Error('cleanBasicHtml() must be passed a `createDocument` function as an option when used in a non-browser environment');
|
||
|
}
|
||
|
|
||
|
options.createDocument = function (docHtml) {
|
||
|
const parser = new Parser();
|
||
|
return parser.parseFromString(docHtml, 'text/html');
|
||
|
};
|
||
|
}
|
||
|
|
||
|
let cleanHtml = html;
|
||
|
|
||
|
if (!options.allowBr || cleanHtml === '<br>') {
|
||
|
cleanHtml = cleanHtml
|
||
|
.replace(/<br\s?\/?>/g, ' ');
|
||
|
}
|
||
|
|
||
|
if (options.removeCodeWrappers) {
|
||
|
cleanHtml = removeCodeWrappers(cleanHtml);
|
||
|
}
|
||
|
|
||
|
cleanHtml = cleanHtml
|
||
|
.replace(/(\s| ){2,}/g, ' ')
|
||
|
.trim()
|
||
|
.replace(/^ | $/g, '')
|
||
|
.trim();
|
||
|
|
||
|
// remove any elements that have a blank textContent
|
||
|
if (cleanHtml) {
|
||
|
let doc = options.createDocument(cleanHtml);
|
||
|
|
||
|
// don't analyze the document if it's empty (can result in storing <br> tags if allowed)
|
||
|
if (doc.body.textContent === '') {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
doc.body.querySelectorAll('*').forEach((element) => {
|
||
|
// Treat Zero Width Non-Joiner characters as spaces
|
||
|
if (!element.textContent.trim().replace(/\u200c+/g, '')) {
|
||
|
if (options.allowBr && element.tagName === 'BR') {
|
||
|
// keep it
|
||
|
return;
|
||
|
}
|
||
|
if (options.allowBr && element.querySelector('br')) {
|
||
|
return element.replaceWith(doc.createElement('br'));
|
||
|
}
|
||
|
if (element.textContent.length > 0) {
|
||
|
// keep a single space to avoid collapsing spaces
|
||
|
let space = doc.createTextNode(' ');
|
||
|
return element.replaceWith(space);
|
||
|
}
|
||
|
return element.remove();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
if (options.firstChildInnerContent && doc.body.firstElementChild) {
|
||
|
cleanHtml = doc.body.firstElementChild.innerHTML.trim();
|
||
|
} else {
|
||
|
cleanHtml = doc.body.innerHTML.trim();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return cleanHtml;
|
||
|
}
|
||
|
|
||
|
module.exports = cleanBasicHtml;
|