/* Shared Help center + tooltip module for the Work Package Suite. Included by the home page, the SOP wizard and the work package creator. It injects: - tooltip styles for the .help-tip (ⓘ) component and [data-tip] hovers - a searchable, multi-topic Help center modal opened via window.openHelp() - a floating "?" launcher on any page that doesn't already have a Help button API (unchanged + extended): openHelp() open the help center openHelp('topicId') open and jump to a topic (e.g. openHelp('constraints')) closeHelp() close it Add a Help button anywhere with onclick="openHelp()". */ (function (global) { 'use strict'; // ── the help-tip component (S8 / T9.5) ───────────────────────────────────── // Markup writes i; this upgrades // every one to a real ' + '' + '
' + '' + '
' + '

No matches. Try another word.

' + sections + '
' + '
' + ''; overlay.addEventListener('click', function (e) { if (e.target === overlay) closeHelp(); }); document.body.appendChild(overlay); // nav clicks + in-content cross-links jump to a section overlay.addEventListener('click', function (e) { var t = e.target.closest('[data-help-target],[data-help-jump]'); if (!t) return; e.preventDefault(); jumpTo(t.getAttribute('data-help-target') || t.getAttribute('data-help-jump')); }); // search var q = overlay.querySelector('#ui-help-q'); q.addEventListener('input', function () { runSearch(q.value); }); // highlight nav as you scroll var content = overlay.querySelector('#ui-help-content'); content.addEventListener('scroll', syncActiveNav, { passive: true }); } function jumpTo(id) { var sec = document.getElementById('ui-help-sec-' + id); if (!sec) return; // Clear any active search filter so the target is visible. var q = document.getElementById('ui-help-q'); if (q && q.value) { q.value = ''; runSearch(''); } sec.scrollIntoView({ block: 'start' }); setActiveNav(id); } function setActiveNav(id) { var nav = document.getElementById('ui-help-nav'); if (!nav) return; nav.querySelectorAll('a').forEach(function (a) { a.classList.toggle('active', a.getAttribute('data-help-target') === id); }); } function syncActiveNav() { var content = document.getElementById('ui-help-content'); if (!content) return; var top = content.scrollTop, best = null, bestDist = Infinity; TOPICS.forEach(function (t) { var sec = document.getElementById('ui-help-sec-' + t.id); if (!sec || sec.classList.contains('hide')) return; var d = Math.abs(sec.offsetTop - top); if (sec.offsetTop - top <= 40 && d < bestDist) { bestDist = d; best = t.id; } }); if (best) setActiveNav(best); } // ── search: filter sections + highlight matches ─────────────────────────── function clearMarks(root) { root.querySelectorAll('mark').forEach(function (m) { var txt = document.createTextNode(m.textContent); m.parentNode.replaceChild(txt, m); }); root.normalize(); } function markMatches(el, query) { var lower = query.toLowerCase(); var walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (!node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; var p = node.parentNode.nodeName; if (p === 'MARK' || p === 'STYLE' || p === 'SCRIPT') return NodeFilter.FILTER_REJECT; return node.nodeValue.toLowerCase().indexOf(lower) >= 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }); var nodes = [], n; while ((n = walker.nextNode())) nodes.push(n); nodes.forEach(function (node) { var val = node.nodeValue, low = val.toLowerCase(), frag = document.createDocumentFragment(), i = 0, idx; while ((idx = low.indexOf(lower, i)) >= 0) { if (idx > i) frag.appendChild(document.createTextNode(val.slice(i, idx))); var mk = document.createElement('mark'); mk.textContent = val.slice(idx, idx + query.length); frag.appendChild(mk); i = idx + query.length; } if (i < val.length) frag.appendChild(document.createTextNode(val.slice(i))); node.parentNode.replaceChild(frag, node); }); } function runSearch(query) { var content = document.getElementById('ui-help-content'); var nav = document.getElementById('ui-help-nav'); var noresult = document.getElementById('ui-help-noresult'); if (!content) return; query = (query || '').trim(); var hits = 0; TOPICS.forEach(function (t) { var sec = document.getElementById('ui-help-sec-' + t.id); var navItem = nav.querySelector('[data-help-target="' + t.id + '"]'); clearMarks(sec); var match = !query || sec.textContent.toLowerCase().indexOf(query.toLowerCase()) >= 0; sec.classList.toggle('hide', !match); if (navItem) navItem.classList.toggle('nohit', !!query && !match); if (match) { hits++; if (query) markMatches(sec, query); } }); noresult.style.display = (query && hits === 0) ? 'block' : 'none'; if (query) { content.scrollTop = 0; } else { syncActiveNav(); } } // ── public API ────────────────────────────────────────────────────────────── global.openHelp = function (topicId) { buildModal(); document.getElementById('ui-help-overlay').classList.add('open'); var q = document.getElementById('ui-help-q'); if (topicId && typeof topicId === 'string') jumpTo(topicId); else { setActiveNav(TOPICS[0].id); if (q) setTimeout(function () { q.focus(); }, 30); } }; global.closeHelp = function () { var o = document.getElementById('ui-help-overlay'); if (o) o.classList.remove('open'); }; document.addEventListener('keydown', function (e) { if (e.key === 'Escape') global.closeHelp(); }); // ── floating launcher on pages without their own Help button ──────────────── function maybeAddFab() { // Both tool pages set WP_HELP_NO_FAB and carry a Help button in the app bar // instead. The creator used to be covered by an iframe test rather than the // flag, which stopped working the moment it became a page (B7/T7.1) - so it // sets the flag now, and says so, rather than relying on where it is rendered. if (global.WP_HELP_NO_FAB) return; if (document.querySelector('[onclick*="openHelp"]')) return; // page already has a Help trigger if (document.getElementById('ui-help-fab')) return; var b = document.createElement('button'); b.id = 'ui-help-fab'; b.className = 'ui-help-fab'; b.type = 'button'; b.title = 'Help'; b.setAttribute('aria-label', 'Open help'); b.textContent = '?'; b.addEventListener('click', function () { global.openHelp(); }); document.body.appendChild(b); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', maybeAddFab); else maybeAddFab(); })(window);