Changes for page Alder Support Center scripts
Last modified by wiki admin on 2026/08/26 01:04
From version 1.1
edited by wiki admin
on 2026/08/24 21:04
on 2026/08/24 21:04
Change comment:
There is no comment for this version
To version 10.1
edited by wiki admin
on 2026/08/26 01:04
on 2026/08/26 01:04
Change comment:
There is no comment for this version
Summary
-
Page properties (2 modified, 0 added, 0 removed)
-
Objects (0 modified, 1 added, 0 removed)
Details
- Page properties
-
- Hidden
-
... ... @@ -1,1 +1,1 @@ 1 - false1 +true - Content
-
... ... @@ -1,1 +1,1 @@ 1 -Article accordion for the customer-facing design. Edit the object, not this text.1 +Article accordion. Edit the object, not this text.
- XWiki.JavaScriptExtension[0]
-
- Parse content
-
... ... @@ -1,0 +1,1 @@ 1 +No - Code
-
... ... @@ -1,0 +1,260 @@ 1 +/* =========================================================================== 2 + Alder Support Center - article accordion 3 + Goes in an XWiki.JavaScriptExtension object on AlderTheme.Scripts (alder wiki). 4 + 5 + Turns each top-level H2 inside #xwikicontent into a collapsible section, so a 6 + long omnibus article becomes a short list of section titles. No article needs 7 + editing: the sections come from headings the content already has. 8 + 9 + Deliberate behaviours, all reversible by disabling this object: 10 + * Sections open independently. Opening one does not close another. 11 + * The first section starts open, so the page never looks empty. 12 + * Fewer than two H2s: nothing happens, the article renders normally. 13 + * Edit/preview modes are skipped, or authors would be editing generated 14 + markup. 15 + * A link to a heading inside a collapsed section opens that section and 16 + scrolls to it, including on first load and on later hash changes. 17 + =========================================================================== */ 18 +(function () { 19 + 'use strict'; 20 + 21 + var MIN_SECTIONS = 2; 22 + 23 + function ready(fn) { 24 + if (document.readyState === 'loading') { 25 + document.addEventListener('DOMContentLoaded', fn); 26 + } else { 27 + fn(); 28 + } 29 + } 30 + 31 + function chevron() { 32 + var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 33 + svg.setAttribute('class', 'alder-chev'); 34 + svg.setAttribute('viewBox', '0 0 24 24'); 35 + var path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 36 + path.setAttribute('d', 'M6 9l6 6 6-6'); 37 + svg.appendChild(path); 38 + return svg; 39 + } 40 + 41 + function sectionRoot(content) { 42 + // Content is sometimes wrapped in one container (an {{html}} macro's div, 43 + // for instance). Descend through single-child wrappers so the section 44 + // blocks are found, but only a couple of levels -- deeper than that and we 45 + // are guessing. 46 + var el = content; 47 + for (var depth = 0; depth < 3; depth++) { 48 + var kids = el.children; 49 + if (kids.length === 1 && kids[0].tagName === 'DIV' && kids[0].children.length > 1) { 50 + el = kids[0]; 51 + } else { 52 + break; 53 + } 54 + } 55 + return el; 56 + } 57 + 58 + function isWikiHome() { 59 + // The employees home page has several H2s (departments, article status, 60 + // publishing workflow). Collapsing a landing page into rows hides the 61 + // thing people came for, so skip it. Matches both URL schemes this 62 + // deployment serves: /bin/view/Main/ and /wiki/<name>/view/Main/. 63 + var path = (window.location.pathname || '').replace(/\/+$/, ''); 64 + return /\/view\/Main(\/WebHome)?$/.test(path); 65 + } 66 + 67 + function build() { 68 + var body = document.body; 69 + if (!body || body.className.indexOf('viewbody') === -1) { 70 + return; // edit, preview, or anything that is not a rendered page 71 + } 72 + if (isWikiHome()) { return; } 73 + var content = document.getElementById('xwikicontent'); 74 + if (!content) { return; } 75 + 76 + // XWiki renders each section as its own <div class="f-accordion-panel">, 77 + // with the section heading as the first child and everything else after it: 78 + // 79 + // <div class="f-accordion-panel" data-controller="accordion"> 80 + // <h2>Arm/Disarm</h2> <- section heading 81 + // <h1>Arm</h1><p>...</p> <- content, including Helpjuice's inverted 82 + // </div> h1 "subsections" 83 + // 84 + // So the section container already exists and we only need to make it 85 + // collapsible. Two earlier versions of this script tried to rebuild sections 86 + // by grouping sibling headings -- first from content.children (headings are 87 + // nested, so it found none), then by grouping headings that share a parent 88 + // (each heading has its OWN panel, so every group had one item and the 89 + // script bailed silently). Both looked fine on a hand-written preview page. 90 + var made = []; 91 + 92 + // Helpjuice exported three different widget wrappers -- f-accordion-panel 93 + // (25 articles), helpjuice-tab (71) and helpjuice-accordion (68) -- and the 94 + // migration carried all of them across as raw HTML. Rather than hardcode 95 + // class names that would miss the next variant, look for the shape they 96 + // share: a wrapper element whose FIRST element is a heading. That is a 97 + // section container regardless of what it is called. 98 + var root = sectionRoot(content); 99 + var eligible = []; 100 + var kids = root.children; 101 + for (var p = 0; p < kids.length; p++) { 102 + var first = kids[p].firstElementChild; 103 + if (first && /^H[1-4]$/.test(first.tagName) && 104 + (first.textContent || '').trim()) { 105 + eligible.push({ panel: kids[p], heading: first }); 106 + } 107 + } 108 + 109 + if (eligible.length >= MIN_SECTIONS) { 110 + eligible.forEach(function (item, index) { 111 + var panel = item.panel; 112 + var heading = item.heading; 113 + 114 + var details = document.createElement('details'); 115 + details.className = 'alder-sec'; 116 + if (index === 0) { details.open = true; } 117 + if (heading.id) { details.id = heading.id; heading.removeAttribute('id'); } 118 + 119 + var summary = document.createElement('summary'); 120 + var title = document.createElement('span'); 121 + title.className = 'alder-sec-title'; 122 + title.textContent = (heading.textContent || '').trim(); 123 + summary.appendChild(title); 124 + summary.appendChild(chevron()); 125 + details.appendChild(summary); 126 + 127 + // The heading is now the summary, so it must not also appear inside. 128 + panel.removeChild(heading); 129 + 130 + var wrap = document.createElement('div'); 131 + wrap.className = 'alder-sec-body'; 132 + while (panel.firstChild) { wrap.appendChild(panel.firstChild); } 133 + details.appendChild(wrap); 134 + 135 + // Keep the panel as the outer element: it carries XWiki's own 136 + // data-controller, and removing it could break behaviour we did not 137 + // write and cannot see. 138 + panel.appendChild(details); 139 + made.push(details); 140 + }); 141 + } else { 142 + // No Helpjuice panels: build sections from headings. Do NOT assume h2. 143 + // Across the migrated corpus the section level varies -- 304 articles use 144 + // h2, 67 use h3, 13 use h1 -- because Helpjuice authors picked levels by 145 + // appearance rather than structure. Assuming h2 left 99 articles with no 146 + // rows at all. 147 + // 148 + // Rule: use the SHALLOWEST level that appears at least twice among 149 + // headings sharing a parent. That is the level acting as "section" in 150 + // this particular article; anything deeper is a subsection and travels 151 + // inside. 152 + var chosen = null; 153 + for (var level = 1; level <= 4 && !chosen; level++) { 154 + // A heading with no text (Helpjuice left a few) would become a row 155 + // with a blank title, which is worse than no row at all. 156 + var all = content.querySelectorAll('h' + level); 157 + var atLevel = []; 158 + for (var t = 0; t < all.length; t++) { 159 + if ((all[t].textContent || '').trim()) { atLevel.push(all[t]); } 160 + } 161 + if (atLevel.length < MIN_SECTIONS) { continue; } 162 + 163 + var groups = []; 164 + for (var i = 0; i < atLevel.length; i++) { 165 + var parent = atLevel[i].parentNode; 166 + var slot = null; 167 + for (var g = 0; g < groups.length; g++) { 168 + if (groups[g].parent === parent) { slot = groups[g]; break; } 169 + } 170 + if (!slot) { slot = { parent: parent, items: [] }; groups.push(slot); } 171 + slot.items.push(atLevel[i]); 172 + } 173 + groups.sort(function (a, b) { return b.items.length - a.items.length; }); 174 + if (groups[0].items.length >= MIN_SECTIONS) { 175 + chosen = { level: level, container: groups[0].parent, items: groups[0].items }; 176 + } 177 + } 178 + if (!chosen) { return; } 179 + 180 + var container = chosen.container; 181 + chosen.items.forEach(function (heading, index) { 182 + var details = document.createElement('details'); 183 + details.className = 'alder-sec'; 184 + if (index === 0) { details.open = true; } 185 + 186 + var summary = document.createElement('summary'); 187 + var title = document.createElement('span'); 188 + title.className = 'alder-sec-title'; 189 + title.textContent = (heading.textContent || '').trim(); 190 + summary.appendChild(title); 191 + summary.appendChild(chevron()); 192 + details.appendChild(summary); 193 + 194 + var wrap = document.createElement('div'); 195 + wrap.className = 'alder-sec-body'; 196 + details.appendChild(wrap); 197 + 198 + if (heading.id) { details.id = heading.id; heading.removeAttribute('id'); } 199 + container.insertBefore(details, heading); 200 + 201 + // Absorb everything up to the next heading at this level or shallower. 202 + // Stopping only at the same tag would let a shallower heading (a real 203 + // structural break) get swallowed into the section above it. 204 + var node = heading; 205 + while (node) { 206 + var next = node.nextSibling; 207 + wrap.appendChild(node); 208 + node = next; 209 + if (node && node.nodeType === 1 && /^H([1-6])$/.test(node.tagName)) { 210 + if (parseInt(node.tagName.slice(1), 10) <= chosen.level) { break; } 211 + } 212 + } 213 + made.push(details); 214 + }); 215 + } 216 + 217 + if (!made.length) { return; } 218 + 219 + var tools = document.createElement('div'); 220 + tools.className = 'alder-tools'; 221 + tools.appendChild(button('Expand all', true, made)); 222 + tools.appendChild(button('Collapse all', false, made)); 223 + made[0].parentNode.insertBefore(tools, made[0]); 224 + 225 + openFromHash(); 226 + window.addEventListener('hashchange', openFromHash); 227 + } 228 + 229 + function button(label, open, sections) { 230 + var b = document.createElement('button'); 231 + b.type = 'button'; 232 + b.textContent = label; 233 + b.addEventListener('click', function () { 234 + sections.forEach(function (d) { d.open = open; }); 235 + }); 236 + return b; 237 + } 238 + 239 + function openFromHash() { 240 + var hash = window.location.hash; 241 + if (!hash || hash.length < 2) { return; } 242 + var id = hash.slice(1); 243 + var target = null; 244 + try { 245 + target = document.getElementById(id); 246 + } catch (e) { 247 + return; 248 + } 249 + if (!target) { return; } 250 + // Open the target itself if it is a section, plus any collapsed ancestor. 251 + var node = target; 252 + while (node && node !== document.body) { 253 + if (node.tagName === 'DETAILS') { node.open = true; } 254 + node = node.parentNode; 255 + } 256 + if (target.scrollIntoView) { target.scrollIntoView(); } 257 + } 258 + 259 + ready(build); 260 +})(); - Name
-
... ... @@ -1,0 +1,1 @@ 1 +Article accordion - Use this extension
-
... ... @@ -1,0 +1,1 @@ 1 +always