|
| 1 | +// Doxygen 1.9.x renders function-group pages with one <h2 class="memtitle"> |
| 2 | +// heading per overload — `read() [1/10]`, `read() [2/10]`, ... — producing a |
| 3 | +// long vertical stack on overload-heavy free-function groups like the |
| 4 | +// `datasets` and `attribute-io` groups in h5cpp. |
| 5 | +// |
| 6 | +// This script collapses each contiguous run of same-name overloads into a |
| 7 | +// single visible heading; the [2..N] overloads' detail blocks become |
| 8 | +// <details> sections, collapsed by default and labelled with each overload's |
| 9 | +// own [k/N] tag. Anchors are preserved so deep-links from the rest of the |
| 10 | +// doc tree still scroll to the exact overload. |
| 11 | +// |
| 12 | +// Look-and-feel intent: each function name appears as ONE entry on the group |
| 13 | +// page, mirroring how a class member page renders overloads under one |
| 14 | +// heading. The detail body for [1/N] is shown inline (so the most common |
| 15 | +// signature reads at a glance); the rest fold under disclosure widgets. |
| 16 | + |
| 17 | +(function () { |
| 18 | + function nameKey(h2) { |
| 19 | + // h2.memtitle text is "fnname() [k/N]" (or just "fnname()" when not |
| 20 | + // an overload). Strip the [k/N] and trailing whitespace to get the |
| 21 | + // group key. |
| 22 | + const txt = h2.textContent || ""; |
| 23 | + // Remove the diamond glyph + non-breaking space the permalink span |
| 24 | + // injects ("◆ "), then drop the [k/N] suffix. |
| 25 | + return txt.replace(/◆\s*/, "").replace(/\s*\[\d+\/\d+\]\s*$/, "").trim(); |
| 26 | + } |
| 27 | + |
| 28 | + function overloadIndex(h2) { |
| 29 | + const m = (h2.textContent || "").match(/\[(\d+)\/(\d+)\]\s*$/); |
| 30 | + return m ? { k: parseInt(m[1], 10), n: parseInt(m[2], 10) } : null; |
| 31 | + } |
| 32 | + |
| 33 | + function detailBlockAfter(h2) { |
| 34 | + // The detail block for a memtitle is the immediately-following |
| 35 | + // <div class="memitem"> (which itself contains the mlabels table |
| 36 | + // with the rendered signature plus the memdoc). Doxygen always |
| 37 | + // emits exactly one memitem per memtitle. |
| 38 | + let node = h2.nextElementSibling; |
| 39 | + while (node && !(node.tagName === "DIV" && node.classList.contains("memitem"))) { |
| 40 | + node = node.nextElementSibling; |
| 41 | + } |
| 42 | + return node; |
| 43 | + } |
| 44 | + |
| 45 | + function collapseOverloads(root) { |
| 46 | + const headings = Array.from(root.querySelectorAll("h2.memtitle")); |
| 47 | + if (!headings.length) return; |
| 48 | + |
| 49 | + // Group consecutive headings sharing the same name key. |
| 50 | + const groups = []; |
| 51 | + let cur = null; |
| 52 | + for (const h of headings) { |
| 53 | + const key = nameKey(h); |
| 54 | + if (cur && cur.key === key) { |
| 55 | + cur.items.push(h); |
| 56 | + } else { |
| 57 | + if (cur) groups.push(cur); |
| 58 | + cur = { key, items: [h] }; |
| 59 | + } |
| 60 | + } |
| 61 | + if (cur) groups.push(cur); |
| 62 | + |
| 63 | + for (const g of groups) { |
| 64 | + if (g.items.length < 2) continue; |
| 65 | + |
| 66 | + // Strip the [k/N] suffix from the first (visible) heading and |
| 67 | + // append a compact overload index that links to each one. |
| 68 | + const first = g.items[0]; |
| 69 | + const firstIdx = overloadIndex(first); |
| 70 | + if (firstIdx) { |
| 71 | + first.innerHTML = first.innerHTML.replace( |
| 72 | + /<span class="overload">\[\d+\/\d+\]<\/span>/, |
| 73 | + "" |
| 74 | + ); |
| 75 | + } |
| 76 | + |
| 77 | + // Build the overload jump-strip and insert it right after the |
| 78 | + // first heading, before its detail body. |
| 79 | + const strip = document.createElement("div"); |
| 80 | + strip.className = "overload-jump-strip"; |
| 81 | + strip.innerHTML = |
| 82 | + '<span class="overload-jump-label">overloads:</span> ' + |
| 83 | + g.items |
| 84 | + .map((h, i) => { |
| 85 | + // Each h2 is preceded by an <a id="..."> anchor or |
| 86 | + // contains one inside the permalink span. We pull the |
| 87 | + // id from the preceding anchor. |
| 88 | + let id = null; |
| 89 | + let prev = h.previousElementSibling; |
| 90 | + while (prev) { |
| 91 | + if (prev.tagName === "A" && prev.id) { id = prev.id; break; } |
| 92 | + if (prev.tagName === "H2") break; |
| 93 | + prev = prev.previousElementSibling; |
| 94 | + } |
| 95 | + return id |
| 96 | + ? `<a class="overload-jump-link" href="#${id}">[${i + 1}/${g.items.length}]</a>` |
| 97 | + : `<span class="overload-jump-link">[${i + 1}/${g.items.length}]</span>`; |
| 98 | + }) |
| 99 | + .join(" "); |
| 100 | + first.insertAdjacentElement("afterend", strip); |
| 101 | + |
| 102 | + // Fold the [2..N] overloads into <details> elements. The detail |
| 103 | + // body sits inside the <details>; the heading becomes the |
| 104 | + // <summary>. The preceding <a id="..."> anchor moves INTO the |
| 105 | + // <details> so deep-link resolution (openOnHash) can walk up |
| 106 | + // from the anchor's parent chain and open the right <details>. |
| 107 | + for (let i = 1; i < g.items.length; i++) { |
| 108 | + const h = g.items[i]; |
| 109 | + const detail = detailBlockAfter(h); |
| 110 | + if (!detail) continue; |
| 111 | + |
| 112 | + const details = document.createElement("details"); |
| 113 | + details.className = "overload-fold"; |
| 114 | + const summary = document.createElement("summary"); |
| 115 | + summary.className = "overload-summary"; |
| 116 | + // Strip the diamond glyph from summary text — the disclosure |
| 117 | + // triangle replaces it visually. |
| 118 | + summary.innerHTML = h.innerHTML.replace(/◆\s*/, ""); |
| 119 | + details.appendChild(summary); |
| 120 | + |
| 121 | + // Collect the preceding <a id="..."> anchor (if any) so it |
| 122 | + // moves into the <details> alongside the detail body. |
| 123 | + const anchor = |
| 124 | + h.previousElementSibling && |
| 125 | + h.previousElementSibling.tagName === "A" && |
| 126 | + h.previousElementSibling.id |
| 127 | + ? h.previousElementSibling |
| 128 | + : null; |
| 129 | + |
| 130 | + // Replace the original heading + detail with the <details> |
| 131 | + // wrapper containing the anchor + detail body. |
| 132 | + h.replaceWith(details); |
| 133 | + detail.parentNode.removeChild(detail); |
| 134 | + if (anchor) { |
| 135 | + anchor.parentNode.removeChild(anchor); |
| 136 | + details.insertBefore(anchor, summary.nextSibling); |
| 137 | + } |
| 138 | + details.appendChild(detail); |
| 139 | + } |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + function openOnHash() { |
| 144 | + // If the URL fragment targets an anchor inside a collapsed |
| 145 | + // <details>, open it so the deep-link actually scrolls into view. |
| 146 | + if (!window.location.hash) return; |
| 147 | + const id = window.location.hash.slice(1); |
| 148 | + const target = document.getElementById(id); |
| 149 | + if (!target) return; |
| 150 | + let node = target.parentElement; |
| 151 | + while (node) { |
| 152 | + if (node.tagName === "DETAILS") node.open = true; |
| 153 | + node = node.parentElement; |
| 154 | + } |
| 155 | + // Re-trigger the scroll after the layout reflows. |
| 156 | + target.scrollIntoView(); |
| 157 | + } |
| 158 | + |
| 159 | + if (document.readyState === "loading") { |
| 160 | + document.addEventListener("DOMContentLoaded", () => { |
| 161 | + collapseOverloads(document); |
| 162 | + openOnHash(); |
| 163 | + }); |
| 164 | + } else { |
| 165 | + collapseOverloads(document); |
| 166 | + openOnHash(); |
| 167 | + } |
| 168 | + window.addEventListener("hashchange", openOnHash); |
| 169 | +})(); |
0 commit comments