Changes for page Alder Support Center scripts
Last modified by wiki admin on 2026/08/26 01:04
From version 9.1
edited by wiki admin
on 2026/08/24 23:09
on 2026/08/24 23:09
Change comment:
There is no comment for this version
To version 2.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
Summary
-
Page properties (1 modified, 0 added, 0 removed)
-
Objects (1 modified, 0 added, 0 removed)
Details
- Page properties
-
- Content
-
... ... @@ -1,1 +1,1 @@ 1 -Article accordion. Edit the object, not this text. 1 +Article accordion for the customer-facing design. Edit the object, not this text.
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -38,182 +38,57 @@ 38 38 return svg; 39 39 } 40 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 67 function build() { 68 68 var body = document.body; 69 69 if (!body || body.className.indexOf('viewbody') === -1) { 70 70 return; // edit, preview, or anything that is not a rendered page 71 71 } 72 - if (isWikiHome()) { return; } 73 73 var content = document.getElementById('xwikicontent'); 74 74 if (!content) { return; } 75 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 - } 49 + // Only top-level headings. An h2 nested inside a table or a macro box is 50 + // part of that structure and must not be torn out of it. 51 + var headings = []; 52 + for (var i = 0; i < content.children.length; i++) { 53 + if (content.children[i].tagName === 'H2') { headings.push(content.children[i]); } 107 107 } 55 + if (headings.length < MIN_SECTIONS) { return; } 108 108 109 - if (eligible.length >= MIN_SECTIONS) { 110 - eligible.forEach(function (item, index) { 111 - var panel = item.panel; 112 - var heading = item.heading; 57 + var made = []; 58 + headings.forEach(function (h2, index) { 59 + var details = document.createElement('details'); 60 + details.className = 'alder-sec'; 61 + if (index === 0) { details.open = true; } 113 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'); } 63 + var summary = document.createElement('summary'); 64 + var title = document.createElement('span'); 65 + title.className = 'alder-sec-title'; 66 + title.textContent = (h2.textContent || '').trim(); 67 + summary.appendChild(title); 68 + summary.appendChild(chevron()); 69 + details.appendChild(summary); 118 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); 71 + var wrap = document.createElement('div'); 72 + wrap.className = 'alder-sec-body'; 73 + details.appendChild(wrap); 126 126 127 - // The heading is now the summary, so it must not also appear inside. 128 - panel.removeChild(heading); 75 + // Keep the heading's id on the details element so existing anchors and 76 + // the table-of-contents macro still resolve to something. 77 + if (h2.id) { details.id = h2.id; h2.removeAttribute('id'); } 129 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); 79 + content.insertBefore(details, h2); 134 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 - } 81 + // Move the heading and everything up to the next h2 into the body. 82 + var node = h2; 83 + while (node && !(node !== h2 && node.tagName === 'H2')) { 84 + var next = node.nextSibling; 85 + wrap.appendChild(node); 86 + node = next; 87 + if (node && node.nodeType === 1 && node.tagName === 'H2') { break; } 177 177 } 178 - if (!chosen) { return; } 89 + made.push(details); 90 + }); 179 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 217 if (!made.length) { return; } 218 218 219 219 var tools = document.createElement('div'); ... ... @@ -220,7 +220,7 @@ 220 220 tools.className = 'alder-tools'; 221 221 tools.appendChild(button('Expand all', true, made)); 222 222 tools.appendChild(button('Collapse all', false, made)); 223 - made[0].parentNode.insertBefore(tools, made[0]);98 + content.insertBefore(tools, made[0]); 224 224 225 225 openFromHash(); 226 226 window.addEventListener('hashchange', openFromHash); - Name
-
... ... @@ -1,1 +1,1 @@ 1 -Article accordion 1 +Alder article accordion - Use this extension
-
... ... @@ -1,1 +1,1 @@ 1 -a lways1 +onDemand