Last modified by wiki admin on 2026/08/26 01:04

From version 8.1
edited by wiki admin
on 2026/08/24 22:49
Change comment: There is no comment for this version
To version 9.1
edited by wiki admin
on 2026/08/24 23:09
Change comment: There is no comment for this version

Summary

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -38,6 +38,23 @@
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 +
41 41   function isWikiHome() {
42 42   // The employees home page has several H2s (departments, article status,
43 43   // publishing workflow). Collapsing a landing page into rows hides the
... ... @@ -71,13 +71,21 @@
71 71   // (each heading has its OWN panel, so every group had one item and the
72 72   // script bailed silently). Both looked fine on a hand-written preview page.
73 73   var made = [];
74 - var panels = content.querySelectorAll('.f-accordion-panel');
75 75  
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);
76 76   var eligible = [];
77 - for (var p = 0; p < panels.length; p++) {
78 - var first = panels[p].firstElementChild;
79 - if (first && /^H[1-4]$/.test(first.tagName)) {
80 - eligible.push({ panel: panels[p], heading: first });
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 });
81 81   }
82 82   }
83 83  
... ... @@ -114,28 +114,46 @@
114 114   made.push(details);
115 115   });
116 116   } else {
117 - // Fallback for content without those panels -- hand-written wiki markup,
118 - // and any XWiki version that renders sections differently.
119 - var found = content.querySelectorAll('h2');
120 - if (found.length < MIN_SECTIONS) { return; }
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; }
121 121  
122 - var byParent = [];
123 - for (var f = 0; f < found.length; f++) {
124 - var parent = found[f].parentNode;
125 - var slot = null;
126 - for (var b = 0; b < byParent.length; b++) {
127 - if (byParent[b].parent === parent) { slot = byParent[b]; break; }
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]);
128 128   }
129 - if (!slot) { slot = { parent: parent, items: [] }; byParent.push(slot); }
130 - slot.items.push(found[f]);
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 + }
131 131   }
132 - byParent.sort(function (a, b) { return b.items.length - a.items.length; });
178 + if (!chosen) { return; }
133 133  
134 - var container = byParent[0].parent;
135 - var headings = byParent[0].items;
136 - if (headings.length < MIN_SECTIONS) { return; }
137 -
138 - headings.forEach(function (h2, index) {
180 + var container = chosen.container;
181 + chosen.items.forEach(function (heading, index) {
139 139   var details = document.createElement('details');
140 140   details.className = 'alder-sec';
141 141   if (index === 0) { details.open = true; }
... ... @@ -143,7 +143,7 @@
143 143   var summary = document.createElement('summary');
144 144   var title = document.createElement('span');
145 145   title.className = 'alder-sec-title';
146 - title.textContent = (h2.textContent || '').trim();
189 + title.textContent = (heading.textContent || '').trim();
147 147   summary.appendChild(title);
148 148   summary.appendChild(chevron());
149 149   details.appendChild(summary);
... ... @@ -152,15 +152,20 @@
152 152   wrap.className = 'alder-sec-body';
153 153   details.appendChild(wrap);
154 154  
155 - if (h2.id) { details.id = h2.id; h2.removeAttribute('id'); }
156 - container.insertBefore(details, h2);
198 + if (heading.id) { details.id = heading.id; heading.removeAttribute('id'); }
199 + container.insertBefore(details, heading);
157 157  
158 - var node = h2;
159 - while (node && !(node !== h2 && node.tagName === 'H2')) {
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) {
160 160   var next = node.nextSibling;
161 161   wrap.appendChild(node);
162 162   node = next;
163 - if (node && node.nodeType === 1 && node.tagName === 'H2') { break; }
209 + if (node && node.nodeType === 1 && /^H([1-6])$/.test(node.tagName)) {
210 + if (parseInt(node.tagName.slice(1), 10) <= chosen.level) { break; }
211 + }
164 164   }
165 165   made.push(details);
166 166   });