$js = <<<'JS' (function() { if (!window.__WPGlossary) return; var glossary = window.__WPGlossary; var sectionExclusions = window.__WPSectionExclusions || []; var container = document.querySelector(".entry-content"); if (!container) return; var occurrenceCount = {}; var processedNodes = new WeakSet(); // Track processed nodes to avoid reprocessing function escapeRegExp(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } // Sort terms by length (longest first) to match multi-word terms before single words var terms = Object.keys(glossary).sort(function(a, b) { return b.length - a.length; }); if (!terms.length) return; // Build pattern with word boundaries - IMPROVED to avoid matching inside HTML tags var pattern = terms.map(escapeRegExp).join("|"); var regex; try { // Unicode-aware regex with negative lookbehind to avoid HTML tags regex = new RegExp("(?]*)(?<=^|[\\s\\p{P}])(" + pattern + ")(?=[\\s\\p{P}]|$)(?![^<]*>)", "giu"); } catch (e) { // Fallback for browsers without lookbehind support regex = new RegExp("(^|[\\s\\.,!?;:\\-()\"'“”‘’\\[\\]{}])(" + pattern + ")(?=[\\s\\.,!?;:\\-()\"'“”‘’\\[\\]{}]|$)", "gi"); } // Function to check if a node is inside a tooltip box or link function isInsideExcludedElement(node) { var parent = node.parentNode; var maxDepth = 10; // Limit search depth var depth = 0; while (parent && depth < maxDepth) { if (parent.nodeType === 1 && parent.classList) { if (parent.classList.contains('tooltip-box') || parent.classList.contains('tooltip-link') || parent.tagName === 'A') { return true; } } parent = parent.parentNode; depth++; } return false; } // Function to check if text is inside an HTML tag function isInsideHtmlTag(node, textIndex) { if (!node.parentNode) return false; var parent = node.parentNode; var html = parent.outerHTML || ''; var textPosition = 0; // Simple check: if the parent has HTML tags and the text node is inside attribute if (parent.hasAttributes && parent.hasAttributes()) { var attrs = parent.attributes; for (var i = 0; i < attrs.length; i++) { if (attrs[i].value && attrs[i].value.indexOf(node.nodeValue) !== -1) { return true; } } } return false; } function isExcluded(node, termLower) { // Check if node is inside tooltip elements if (isInsideExcludedElement(node)) { return true; } if (!sectionExclusions.length) return false; var parent = node.parentNode; if (!parent) return false; for (var i = 0; i < sectionExclusions.length; i++) { var exclusion = sectionExclusions[i]; if (exclusion.terms.indexOf(termLower) !== -1) { var headingText = exclusion.terms[0]; var h5Elements = document.querySelectorAll('.entry-content h5'); for (var j = 0; j < h5Elements.length && j < 50; j++) { // Limit search var h5 = h5Elements[j]; var h5Text = h5.textContent.trim().toLowerCase(); var headingTerm = headingText.toLowerCase(); if (h5Text === headingTerm || h5Text.indexOf(headingTerm) !== -1) { var nextH5 = getNextSiblingH5(h5); var isAfterH5 = isNodeAfter(node, h5); var isBeforeNextH5 = nextH5 ? !isNodeAfter(node, nextH5) : true; if (isAfterH5 && isBeforeNextH5) { return true; } } } } } return false; } function getNextSiblingH5(element) { var next = element.nextElementSibling; var maxSearch = 20; // Limit search var count = 0; while (next && count < maxSearch) { if (next.tagName === 'H5') { return next; } next = next.nextElementSibling; count++; } return null; } function isNodeAfter(nodeA, nodeB) { if (!nodeA || !nodeB) return false; var position = nodeA.compareDocumentPosition(nodeB); return (position & Node.DOCUMENT_POSITION_FOLLOWING) ? false : true; } function generateTabsContent(termKey) { var meanings = glossary[termKey]; if (!meanings || !meanings.length) return ''; if (meanings.length === 1) { return meanings[0].content; } // Generate tabs HTML for multiple meanings var tabsHtml = '
'; var contentHtml = '
'; meanings.forEach(function(meaning, index) { var tabId = 'tab-' + termKey.replace(/[^a-zA-Z0-9]/g, '_') + '-' + index; var isActive = index === 0; var tabTitle = meaning.title || meaning.post_title || 'Meaning ' + (index + 1); tabsHtml += ''; contentHtml += '
' + meaning.content + '
'; }); tabsHtml += '
'; contentHtml += '
'; return tabsHtml + contentHtml; } function escapeHtml(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function attachTooltip(span, termKey) { var box = span.querySelector(".tooltip-box"); if (!box) return; var close = box.querySelector(".tooltip-close"); var isTooltipOpen = false; function toggle(e) { if (e) e.stopPropagation(); if (span.classList.contains("show-tooltip")) { // Close the tooltip only if clicking on the same element // or if explicitly closing via the close button if (e && e.target && e.target.classList && e.target.classList.contains('tooltip-close')) { span.classList.remove("show-tooltip"); isTooltipOpen = false; } } else { // Close all other tooltips var allTooltips = document.querySelectorAll(".glossary-tooltip"); for (var i = 0; i < allTooltips.length; i++) { allTooltips[i].classList.remove("show-tooltip"); } span.classList.add("show-tooltip"); isTooltipOpen = true; position(); initTabs(box); } } function position() { var r = span.getBoundingClientRect(); var top = r.bottom + 10; var left = r.left; if (top + box.offsetHeight > window.innerHeight) top = r.top - box.offsetHeight - 10; if (top < 10) top = 10; if (left + box.offsetWidth > window.innerWidth) left = window.innerWidth - box.offsetWidth - 20; if (left < 10) left = 10; box.style.top = top + "px"; box.style.left = left + "px"; } function initTabs(boxElement) { var tabs = boxElement.querySelectorAll('.tooltip-tab'); if (!tabs.length) return; for (var i = 0; i < tabs.length; i++) { var tab = tabs[i]; // Remove existing event listener to avoid duplicates tab.removeEventListener('click', tabClickHandler); tab.addEventListener('click', tabClickHandler); } } function tabClickHandler(e) { e.stopPropagation(); var tab = e.currentTarget; var tabId = tab.getAttribute('data-tab'); var boxElement = tab.closest('.tooltip-box'); if (!boxElement) return; // Update active tab var allTabs = boxElement.querySelectorAll('.tooltip-tab'); for (var i = 0; i < allTabs.length; i++) { allTabs[i].classList.remove('active'); } tab.classList.add('active'); // Update active content var allContents = boxElement.querySelectorAll('.tooltip-tab-content'); for (var i = 0; i < allContents.length; i++) { allContents[i].classList.remove('active'); } var activeContent = boxElement.querySelector('#' + tabId); if (activeContent) { activeContent.classList.add('active'); } } // Click handler for the glossary term span.addEventListener("click", function(e) { e.stopPropagation(); if (span.classList.contains("show-tooltip")) { // If tooltip is already open, close it span.classList.remove("show-tooltip"); isTooltipOpen = false; } else { // Close all other tooltips var allTooltips = document.querySelectorAll(".glossary-tooltip"); for (var i = 0; i < allTooltips.length; i++) { allTooltips[i].classList.remove("show-tooltip"); } span.classList.add("show-tooltip"); isTooltipOpen = true; position(); initTabs(box); } }); // Close button handler if (close) { close.addEventListener("click", function(e) { e.stopPropagation(); span.classList.remove("show-tooltip"); isTooltipOpen = false; }); } // Prevent clicks inside tooltip from closing it box.addEventListener("click", function(e) { e.stopPropagation(); }); // REMOVED: The document click handler that was closing tooltips // Now tooltips only close when: // 1. Clicking the close (×) button // 2. Clicking the same glossary term again // 3. Clicking another glossary term (which closes all others) window.addEventListener("resize", position); } // Helper function to detect if text is bold function isElementBold(element) { if (!element) return false; // Check if element itself has bold styling var computedStyle = window.getComputedStyle(element); var isBold = computedStyle.fontWeight === 'bold' || computedStyle.fontWeight === '700' || computedStyle.fontWeight === '800' || computedStyle.fontWeight === '900'; if (isBold) return true; // Check if element is strong or b tag if (element.tagName === 'STRONG' || element.tagName === 'B') return true; // Check parent for bold var parent = element.parentElement; if (parent && parent !== element) { return isElementBold(parent); } return false; } // Helper to detect if text is italic function isElementItalic(element) { if (!element) return false; var computedStyle = window.getComputedStyle(element); var isItalic = computedStyle.fontStyle === 'italic' || computedStyle.fontStyle === 'oblique'; if (isItalic) return true; // Check if element is em or i tag if (element.tagName === 'EM' || element.tagName === 'I') return true; // Check parent for italic var parent = element.parentElement; if (parent && parent !== element) { return isElementItalic(parent); } return false; } function processTextNode(node) { if (node.nodeType !== 3) return; // Skip if already processed if (processedNodes.has(node)) return; var text = node.nodeValue; if (!text || !text.trim()) return; var parent = node.parentNode; if (!parent) return; // Skip if parent is already a tooltip or link if (parent.closest && (parent.closest(".glossary-tooltip") || parent.tagName === "A")) return; // Skip if this text node is inside an HTML attribute or tag if (isInsideHtmlTag(node, 0)) return; // Reset regex and find matches regex.lastIndex = 0; var matches = []; var match; // Collect all matches var matchCount = 0; while ((match = regex.exec(text)) !== null && matchCount < 100) { // Limit matches per text node var matchedTerm = match[1] || match[2]; if (matchedTerm) { var matchIndex = match.index; // For fallback regex that includes prefix if (match[1] && !match[2]) { // If the regex captured the prefix as first group matchedTerm = match[1]; } matches.push({ index: matchIndex + (match[1] === matchedTerm ? 0 : (match[1] ? match[1].length : 0)), length: matchedTerm.length, term: matchedTerm, fullMatch: match[0] }); } matchCount++; } if (!matches.length) return; // Mark as processed to avoid infinite loops processedNodes.add(node); // Detect parent formatting (bold/italic) before splitting var parentElement = parent; var isBold = isElementBold(parentElement); var isItalic = isElementItalic(parentElement); var frag = document.createDocumentFragment(); var lastIndex = 0; for (var m = 0; m < matches.length; m++) { var match = matches[m]; var matchStart = match.index; var matchedTerm = match.term; var termEnd = matchStart + matchedTerm.length; // Add text before the match if (matchStart > lastIndex) { frag.appendChild(document.createTextNode(text.slice(lastIndex, matchStart))); } var key = null; var lower = matchedTerm.toLowerCase(); // Find exact match in terms array for (var k = 0; k < terms.length; k++) { if (terms[k].toLowerCase() === lower) { key = terms[k]; break; } } if (!key) { // If no exact match found, add the text as is, preserving formatting var plainSpan = document.createElement("span"); plainSpan.style.fontWeight = isBold ? 'bold' : 'normal'; plainSpan.style.fontStyle = isItalic ? 'italic' : 'normal'; plainSpan.textContent = matchedTerm; frag.appendChild(plainSpan); } else { var keyLower = key.toLowerCase(); var shouldExclude = false; try { shouldExclude = isExcluded(node, keyLower); } catch(e) { shouldExclude = false; } if (shouldExclude) { var excludeSpan = document.createElement("span"); excludeSpan.style.fontWeight = isBold ? 'bold' : 'normal'; excludeSpan.style.fontStyle = isItalic ? 'italic' : 'normal'; excludeSpan.textContent = matchedTerm; frag.appendChild(excludeSpan); } else { if (!occurrenceCount[keyLower]) occurrenceCount[keyLower] = 0; occurrenceCount[keyLower]++; var span = document.createElement("span"); span.className = "glossary-tooltip"; // Apply original formatting to the tooltip text span.style.fontWeight = isBold ? 'bold' : 'normal'; span.style.fontStyle = isItalic ? 'italic' : 'normal'; // Preserve any additional inline styles from parent var parentComputedStyle = window.getComputedStyle(parentElement); if (parentComputedStyle.fontWeight === 'bold' || parentComputedStyle.fontWeight === '700') { span.style.fontWeight = 'bold'; } if (parentComputedStyle.fontStyle === 'italic') { span.style.fontStyle = 'italic'; } if (occurrenceCount[keyLower] > 1) { span.classList.add("glossary-tooltip-subsequent"); } // Generate tabs content if multiple meanings exist var tabsContent = generateTabsContent(keyLower); span.innerHTML = matchedTerm + '
' + '' + tabsContent + '
'; frag.appendChild(span); // Delay tooltip attachment to avoid recursion setTimeout(function(spanElem, keyLowerVal) { attachTooltip(spanElem, keyLowerVal); }, 0, span, keyLower); } } lastIndex = termEnd; } if (lastIndex < text.length) { frag.appendChild(document.createTextNode(text.slice(lastIndex))); } try { parent.replaceChild(frag, node); } catch(e) { // Fallback if replacement fails console.warn('Glossary tooltip replacement failed:', e); } } function walk(node) { if (!node || node.nodeType !== 1) return; var child = node.firstChild; var maxDepth = 500; // Limit recursion depth var depth = 0; function traverse(currentNode) { if (!currentNode || depth > maxDepth) return; depth++; var childNode = currentNode.firstChild; while (childNode) { var next = childNode.nextSibling; if (childNode.nodeType === 3) { processTextNode(childNode); } else if (childNode.nodeType === 1) { var tag = childNode.tagName; // Skip processing inside tooltip boxes, links, scripts, etc. if (tag !== 'A' && tag !== 'SCRIPT' && tag !== 'STYLE' && tag !== 'CODE' && tag !== 'PRE' && tag !== 'NOSCRIPT') { // Also skip if the element is inside a tooltip box if (!childNode.closest || !childNode.closest('.tooltip-box')) { traverse(childNode); } } } childNode = next; } depth--; } traverse(node); } // Use requestIdleCallback or setTimeout to avoid blocking page rendering if (window.requestIdleCallback) { window.requestIdleCallback(function() { walk(container); }); } else { setTimeout(function() { walk(container); }, 100); } })(); JS;