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

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

Summary

Details

XWiki.JavaScriptExtension[0]
Code
... ... @@ -38,57 +38,134 @@
38 38   return svg;
39 39   }
40 40  
41 + function isWikiHome() {
42 + // The employees home page has several H2s (departments, article status,
43 + // publishing workflow). Collapsing a landing page into rows hides the
44 + // thing people came for, so skip it. Matches both URL schemes this
45 + // deployment serves: /bin/view/Main/ and /wiki/<name>/view/Main/.
46 + var path = (window.location.pathname || '').replace(/\/+$/, '');
47 + return /\/view\/Main(\/WebHome)?$/.test(path);
48 + }
49 +
41 41   function build() {
42 42   var body = document.body;
43 43   if (!body || body.className.indexOf('viewbody') === -1) {
44 44   return; // edit, preview, or anything that is not a rendered page
45 45   }
55 + if (isWikiHome()) { return; }
46 46   var content = document.getElementById('xwikicontent');
47 47   if (!content) { return; }
48 48  
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]); }
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');
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 });
81 + }
54 54   }
55 - if (headings.length < MIN_SECTIONS) { return; }
56 56  
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; }
84 + if (eligible.length >= MIN_SECTIONS) {
85 + eligible.forEach(function (item, index) {
86 + var panel = item.panel;
87 + var heading = item.heading;
62 62  
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);
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'); }
70 70  
71 - var wrap = document.createElement('div');
72 - wrap.className = 'alder-sec-body';
73 - details.appendChild(wrap);
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);
74 74  
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'); }
102 + // The heading is now the summary, so it must not also appear inside.
103 + panel.removeChild(heading);
78 78  
79 - content.insertBefore(details, h2);
105 + var wrap = document.createElement('div');
106 + wrap.className = 'alder-sec-body';
107 + while (panel.firstChild) { wrap.appendChild(panel.firstChild); }
108 + details.appendChild(wrap);
80 80  
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; }
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; }
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]);
88 88   }
89 - made.push(details);
90 - });
132 + byParent.sort(function (a, b) { return b.items.length - a.items.length; });
91 91  
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 +
92 92   if (!made.length) { return; }
93 93  
94 94   var tools = document.createElement('div');
... ... @@ -95,7 +95,7 @@
95 95   tools.className = 'alder-tools';
96 96   tools.appendChild(button('Expand all', true, made));
97 97   tools.appendChild(button('Collapse all', false, made));
98 - content.insertBefore(tools, made[0]);
175 + made[0].parentNode.insertBefore(tools, made[0]);
99 99  
100 100   openFromHash();
101 101   window.addEventListener('hashchange', openFromHash);