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
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

Page properties
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]
Code
... ... @@ -1,0 +1,166 @@
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 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 +
50 + function build() {
51 + var body = document.body;
52 + if (!body || body.className.indexOf('viewbody') === -1) {
53 + return; // edit, preview, or anything that is not a rendered page
54 + }
55 + if (isWikiHome()) { return; }
56 + var content = document.getElementById('xwikicontent');
57 + if (!content) { return; }
58 +
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; }
68 +
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; }
78 + }
79 + if (!slot) { slot = { parent: parent, items: [] }; byParent.push(slot); }
80 + slot.items.push(found[f]);
81 + }
82 + byParent.sort(function (a, b) { return b.items.length - a.items.length; });
83 +
84 + var container = byParent[0].parent;
85 + var headings = byParent[0].items;
86 + if (headings.length < MIN_SECTIONS) { return; }
87 +
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 +
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 +
102 + var wrap = document.createElement('div');
103 + wrap.className = 'alder-sec-body';
104 + details.appendChild(wrap);
105 +
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 +
110 + container.insertBefore(details, h2);
111 +
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; }
119 + }
120 + made.push(details);
121 + });
122 +
123 + if (!made.length) { return; }
124 +
125 + var tools = document.createElement('div');
126 + tools.className = 'alder-tools';
127 + tools.appendChild(button('Expand all', true, made));
128 + tools.appendChild(button('Collapse all', false, made));
129 + container.insertBefore(tools, made[0]);
130 +
131 + openFromHash();
132 + window.addEventListener('hashchange', openFromHash);
133 + }
134 +
135 + function button(label, open, sections) {
136 + var b = document.createElement('button');
137 + b.type = 'button';
138 + b.textContent = label;
139 + b.addEventListener('click', function () {
140 + sections.forEach(function (d) { d.open = open; });
141 + });
142 + return b;
143 + }
144 +
145 + function openFromHash() {
146 + var hash = window.location.hash;
147 + if (!hash || hash.length < 2) { return; }
148 + var id = hash.slice(1);
149 + var target = null;
150 + try {
151 + target = document.getElementById(id);
152 + } catch (e) {
153 + return;
154 + }
155 + if (!target) { return; }
156 + // Open the target itself if it is a section, plus any collapsed ancestor.
157 + var node = target;
158 + while (node && node !== document.body) {
159 + if (node.tagName === 'DETAILS') { node.open = true; }
160 + node = node.parentNode;
161 + }
162 + if (target.scrollIntoView) { target.scrollIntoView(); }
163 + }
164 +
165 + ready(build);
166 +})();
Name
... ... @@ -1,0 +1,1 @@
1 +Article accordion
Parse content
... ... @@ -1,0 +1,1 @@
1 +No
Use this extension
... ... @@ -1,0 +1,1 @@
1 +always