$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;