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 7.1
edited by wiki admin
on 2026/08/24 22:31
Change comment: There is no comment for this version

Summary

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -56,116 +56,70 @@
56 56   var content = document.getElementById('xwikicontent');
57 57   if (!content) { return; }
58 58  
59 - // XWiki renders each section as its own <div class="f-accordion-panel">,
60 - // with the section heading as the first child and everything else after it:
61 - //
62 - // <div class="f-accordion-panel" data-controller="accordion">
63 - // <h2>Arm/Disarm</h2> <- section heading
64 - // <h1>Arm</h1><p>...</p> <- content, including Helpjuice's inverted
65 - // </div> h1 "subsections"
66 - //
67 - // So the section container already exists and we only need to make it
68 - // collapsible. Two earlier versions of this script tried to rebuild sections
69 - // by grouping sibling headings -- first from content.children (headings are
70 - // nested, so it found none), then by grouping headings that share a parent
71 - // (each heading has its OWN panel, so every group had one item and the
72 - // script bailed silently). Both looked fine on a hand-written preview page.
73 - var made = [];
74 - var panels = content.querySelectorAll('.f-accordion-panel');
59 + // Find the element the section headings actually live in. XWiki wraps
60 + // rendered content in its own container (.f-accordion-panel on this
61 + // instance), so the headings are NOT direct children of #xwikicontent --
62 + // an earlier version of this script looked only at content.children, found
63 + // nothing on every real article, and silently did nothing. The Preview
64 + // page hid that, because hand-written wiki markup does put headings at the
65 + // top level.
66 + var found = content.querySelectorAll('h2');
67 + if (found.length < MIN_SECTIONS) { return; }
75 75  
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 });
69 + // Group headings that share a parent, using the parent holding the most of
70 + // them. Taking every h2 regardless of depth would tear headings out of
71 + // tables and macro boxes they belong to.
72 + var byParent = [];
73 + for (var f = 0; f < found.length; f++) {
74 + var parent = found[f].parentNode;
75 + var slot = null;
76 + for (var b = 0; b < byParent.length; b++) {
77 + if (byParent[b].parent === parent) { slot = byParent[b]; break; }
81 81   }
79 + if (!slot) { slot = { parent: parent, items: [] }; byParent.push(slot); }
80 + slot.items.push(found[f]);
82 82   }
82 + byParent.sort(function (a, b) { return b.items.length - a.items.length; });
83 83  
84 - if (eligible.length >= MIN_SECTIONS) {
85 - eligible.forEach(function (item, index) {
86 - var panel = item.panel;
87 - var heading = item.heading;
84 + var container = byParent[0].parent;
85 + var headings = byParent[0].items;
86 + if (headings.length < MIN_SECTIONS) { return; }
88 88  
89 - var details = document.createElement('details');
90 - details.className = 'alder-sec';
91 - if (index === 0) { details.open = true; }
92 - if (heading.id) { details.id = heading.id; heading.removeAttribute('id'); }
88 + var made = [];
89 + headings.forEach(function (h2, index) {
90 + var details = document.createElement('details');
91 + details.className = 'alder-sec';
92 + if (index === 0) { details.open = true; }
93 93  
94 - var summary = document.createElement('summary');
95 - var title = document.createElement('span');
96 - title.className = 'alder-sec-title';
97 - title.textContent = (heading.textContent || '').trim();
98 - summary.appendChild(title);
99 - summary.appendChild(chevron());
100 - details.appendChild(summary);
94 + var summary = document.createElement('summary');
95 + var title = document.createElement('span');
96 + title.className = 'alder-sec-title';
97 + title.textContent = (h2.textContent || '').trim();
98 + summary.appendChild(title);
99 + summary.appendChild(chevron());
100 + details.appendChild(summary);
101 101  
102 - // The heading is now the summary, so it must not also appear inside.
103 - panel.removeChild(heading);
102 + var wrap = document.createElement('div');
103 + wrap.className = 'alder-sec-body';
104 + details.appendChild(wrap);
104 104  
105 - var wrap = document.createElement('div');
106 - wrap.className = 'alder-sec-body';
107 - while (panel.firstChild) { wrap.appendChild(panel.firstChild); }
108 - details.appendChild(wrap);
106 + // Keep the heading's id on the details element so existing anchors and
107 + // the table-of-contents macro still resolve to something.
108 + if (h2.id) { details.id = h2.id; h2.removeAttribute('id'); }
109 109  
110 - // Keep the panel as the outer element: it carries XWiki's own
111 - // data-controller, and removing it could break behaviour we did not
112 - // write and cannot see.
113 - panel.appendChild(details);
114 - made.push(details);
115 - });
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; }
110 + container.insertBefore(details, h2);
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; }
128 - }
129 - if (!slot) { slot = { parent: parent, items: [] }; byParent.push(slot); }
130 - slot.items.push(found[f]);
112 + // Move the heading and everything up to the next h2 into the body.
113 + var node = h2;
114 + while (node && !(node !== h2 && node.tagName === 'H2')) {
115 + var next = node.nextSibling;
116 + wrap.appendChild(node);
117 + node = next;
118 + if (node && node.nodeType === 1 && node.tagName === 'H2') { break; }
131 131   }
132 - byParent.sort(function (a, b) { return b.items.length - a.items.length; });
120 + made.push(details);
121 + });
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) {
139 - var details = document.createElement('details');
140 - details.className = 'alder-sec';
141 - if (index === 0) { details.open = true; }
142 -
143 - var summary = document.createElement('summary');
144 - var title = document.createElement('span');
145 - title.className = 'alder-sec-title';
146 - title.textContent = (h2.textContent || '').trim();
147 - summary.appendChild(title);
148 - summary.appendChild(chevron());
149 - details.appendChild(summary);
150 -
151 - var wrap = document.createElement('div');
152 - wrap.className = 'alder-sec-body';
153 - details.appendChild(wrap);
154 -
155 - if (h2.id) { details.id = h2.id; h2.removeAttribute('id'); }
156 - container.insertBefore(details, h2);
157 -
158 - var node = h2;
159 - while (node && !(node !== h2 && node.tagName === 'H2')) {
160 - var next = node.nextSibling;
161 - wrap.appendChild(node);
162 - node = next;
163 - if (node && node.nodeType === 1 && node.tagName === 'H2') { break; }
164 - }
165 - made.push(details);
166 - });
167 - }
168 -
169 169   if (!made.length) { return; }
170 170  
171 171   var tools = document.createElement('div');
... ... @@ -172,7 +172,7 @@
172 172   tools.className = 'alder-tools';
173 173   tools.appendChild(button('Expand all', true, made));
174 174   tools.appendChild(button('Collapse all', false, made));
175 - made[0].parentNode.insertBefore(tools, made[0]);
129 + container.insertBefore(tools, made[0]);
176 176  
177 177   openFromHash();
178 178   window.addEventListener('hashchange', openFromHash);