diff --git a/src/utils/enhance-content.js b/src/utils/enhance-content.js index 417c03c2..152df8a8 100644 --- a/src/utils/enhance-content.js +++ b/src/utils/enhance-content.js @@ -29,20 +29,6 @@ function enhanceContent(content, opts = {}) { node.replaceWith(...nodes); }); - // INLINE CODE - // =========== - // Convert `code` to code - textNodes = extractTextNodes(dom); - textNodes.forEach((node) => { - let html = node.nodeValue.replace(//g, '>'); - if (/`[^`]+`/g.test(html)) { - html = html.replaceAll(/(`[^]+?`)/g, '$1'); - } - fauxDiv.innerHTML = html; - const nodes = Array.from(fauxDiv.childNodes); - node.replaceWith(...nodes); - }); - // CODE BLOCKS // =========== // Convert ```code``` to
code
@@ -57,10 +43,26 @@ function enhanceContent(content, opts = {}) { block.replaceWith(pre); }); + // INLINE CODE + // =========== + // Convert `code` to code + textNodes = extractTextNodes(dom); + textNodes.forEach((node) => { + let html = node.nodeValue.replace(//g, '>'); + if (/`[^`]+`/g.test(html)) { + html = html.replaceAll(/(`[^]+?`)/g, '$1'); + } + fauxDiv.innerHTML = html; + const nodes = Array.from(fauxDiv.childNodes); + node.replaceWith(...nodes); + }); + // TWITTER USERNAMES // ================= // Convert @username@twitter.com to @username@twitter.com - textNodes = extractTextNodes(dom); + textNodes = extractTextNodes(dom, { + rejectFilter: ['A'], + }); textNodes.forEach((node) => { let html = node.nodeValue.replace(//g, '>'); if (/@[a-zA-Z0-9_]+@twitter\.com/g.test(html)) { @@ -83,12 +85,60 @@ function enhanceContent(content, opts = {}) { return enhancedContent; } -function extractTextNodes(dom) { +const defaultRejectFilter = [ + // Document metadata + 'STYLE', + // Image and multimedia + 'IMG', + 'VIDEO', + 'AUDIO', + 'AREA', + 'MAP', + 'TRACK', + // Embedded content + 'EMBED', + 'IFRAME', + 'OBJECT', + 'PICTURE', + 'PORTAL', + 'SOURCE', + // SVG and MathML + 'SVG', + 'MATH', + // Scripting + 'CANVAS', + 'NOSCRIPT', + 'SCRIPT', + // Forms + 'INPUT', + 'OPTION', + 'TEXTAREA', + // Web Components + 'SLOT', + 'TEMPLATE', +]; +const defaultRejectFilterMap = Object.fromEntries( + defaultRejectFilter.map((nodeName) => [nodeName, true]), +); +function extractTextNodes(dom, opts = {}) { const textNodes = []; const walk = document.createTreeWalker( dom, NodeFilter.SHOW_TEXT, - null, + { + acceptNode(node) { + if (defaultRejectFilterMap[node.parentNode.nodeName]) { + return NodeFilter.FILTER_REJECT; + } + if ( + opts.rejectFilter && + opts.rejectFilter.includes(node.parentNode.nodeName) + ) { + return NodeFilter.FILTER_REJECT; + } + return NodeFilter.FILTER_ACCEPT; + }, + }, false, ); let node;