Skip to content
This repository was archived by the owner on Aug 3, 2024. It is now read-only.

Commit e41c1cb

Browse files
timjbalexbiehl
authored andcommitted
Use <details> element for collapsibles (#690)
* Remove unnecessary call to 'collapseSection' The call is unnecessary since there is no corresponding toggle for hiding the section of orphan instances. * Use <details> for collapsibles This makes them work even when JS is disabled. Closes #560.
1 parent 406030f commit e41c1cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2933
-2537
lines changed

haddock-api/resources/html/Ocean.std-theme/ocean.css

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ a[href].def:hover { color: rgb(78, 98, 114); }
4646

4747
/* @end */
4848

49+
/* @group Show and hide with JS */
50+
51+
body.js-enabled .hide-when-js-enabled {
52+
display: none;
53+
}
54+
55+
/* @end */
56+
4957
/* @group Fonts & Sizes */
5058

5159
/* Basic technique & IE workarounds from YUI 3
@@ -106,7 +114,7 @@ pre, code, kbd, samp, tt, .src {
106114

107115
/* @group Common */
108116

109-
.caption, h1, h2, h3, h4, h5, h6 {
117+
.caption, h1, h2, h3, h4, h5, h6, summary {
110118
font-weight: bold;
111119
color: rgb(78,98,114);
112120
margin: 0.8em 0 0.4em;
@@ -168,6 +176,16 @@ p.caption.expander {
168176
min-height: 9px;
169177
}
170178

179+
summary {
180+
cursor: pointer;
181+
outline: none;
182+
list-style-image: url(plus.gif);
183+
list-style-position: outside;
184+
}
185+
186+
details[open] > summary {
187+
list-style-image: url(minus.gif);
188+
}
171189

172190
pre {
173191
padding: 0.25em;
@@ -338,24 +356,22 @@ div#style-menu-holder {
338356
z-index: 1;
339357
}
340358

341-
#synopsis .caption {
359+
#synopsis summary {
360+
display: block;
342361
float: left;
343362
width: 29px;
344363
color: rgba(255,255,255,0);
345364
height: 110px;
346365
margin: 0;
347366
font-size: 1px;
348367
padding: 0;
368+
background: url(synopsis.png) no-repeat 0px -8px;
349369
}
350370

351-
#synopsis p.caption.collapser {
371+
#synopsis details[open] > summary {
352372
background: url(synopsis.png) no-repeat -64px -8px;
353373
}
354374

355-
#synopsis p.caption.expander {
356-
background: url(synopsis.png) no-repeat 0px -8px;
357-
}
358-
359375
#synopsis ul {
360376
height: 100%;
361377
overflow: auto;

haddock-api/resources/html/haddock-bundle.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export function setCookie(name: string, value: string) {
2+
document.cookie = name + "=" + encodeURIComponent(value) + ";path=/;";
3+
}
4+
5+
export function clearCookie(name: string) {
6+
document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT;";
7+
}
8+
9+
export function getCookie(name: string) {
10+
const nameEQ = name + "=";
11+
const ca = document.cookie.split(';');
12+
for (let i = 0; i < ca.length; i++) {
13+
let c = ca[i];
14+
while (c.charAt(0)==' ') c = c.substring(1,c.length);
15+
if (c.indexOf(nameEQ) == 0) {
16+
return decodeURIComponent(c.substring(nameEQ.length,c.length));
17+
}
18+
}
19+
return null;
20+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import {getCookie} from "./cookies";
2+
3+
interface HTMLDetailsElement extends HTMLElement {
4+
open: boolean
5+
}
6+
7+
interface DetailsInfo {
8+
element: HTMLDetailsElement
9+
openByDefault: boolean
10+
toggles: HTMLElement[]
11+
}
12+
13+
// Global state
14+
const detailsRegistry: { [id: string]: DetailsInfo } = {};
15+
const toggled: { [id: string]: true } = {}; /* stores which <details> are not in their default state */
16+
17+
function lookupDetailsRegistry(id: string): DetailsInfo {
18+
const info = detailsRegistry[id];
19+
if (info == undefined) { throw new Error(`could not find <details> element with id '${id}'`); }
20+
return info;
21+
}
22+
23+
function onDetailsToggle(ev: Event) {
24+
const element = ev.target as HTMLDetailsElement;
25+
const id = element.id;
26+
const info = lookupDetailsRegistry(id);
27+
const isOpen = info.element.open;
28+
for (const toggle of info.toggles) {
29+
if (toggle.classList.contains('details-toggle-control')) {
30+
toggle.classList.add(isOpen ? 'collapser' : 'expander');
31+
toggle.classList.remove(isOpen ? 'expander' : 'collapser');
32+
}
33+
}
34+
if (element.open == info.openByDefault) {
35+
delete toggled[id];
36+
} else {
37+
toggled[id] = true;
38+
}
39+
rememberToggled();
40+
}
41+
42+
function gatherDetailsElements() {
43+
const els: HTMLDetailsElement[] = Array.prototype.slice.call(document.getElementsByTagName('details'));
44+
for (const el of els) {
45+
if (typeof el.id == "string" && el.id.length > 0) {
46+
detailsRegistry[el.id] = {
47+
element: el,
48+
openByDefault: !!el.open,
49+
toggles: [] // added later
50+
};
51+
el.addEventListener('toggle', onDetailsToggle);
52+
}
53+
}
54+
}
55+
56+
function toggleDetails(id: string) {
57+
const {element} = lookupDetailsRegistry(id);
58+
element.open = !element.open;
59+
}
60+
61+
function rememberToggled() {
62+
const sections: string[] = Object.keys(toggled);
63+
// cookie specific to this page; don't use setCookie which sets path=/
64+
document.cookie = "toggled=" + encodeURIComponent(sections.join('+'));
65+
}
66+
67+
function restoreToggled() {
68+
const cookie = getCookie("toggled");
69+
if (!cookie) { return; }
70+
const ids = cookie.split('+');
71+
for (const id of ids) {
72+
const info = detailsRegistry[id];
73+
toggled[id] = true;
74+
if (info) {
75+
info.element.open = !info.element.open;
76+
}
77+
}
78+
}
79+
80+
function onToggleClick(ev: MouseEvent) {
81+
ev.preventDefault();
82+
const toggle = ev.currentTarget as HTMLElement;
83+
const id = toggle.getAttribute('data-details-id');
84+
if (!id) { throw new Error("element with class 'details-toggle' has no 'data-details-id' attribute!"); }
85+
toggleDetails(id);
86+
}
87+
88+
function initCollapseToggles() {
89+
const toggles: HTMLElement[] = Array.prototype.slice.call(document.getElementsByClassName('details-toggle'));
90+
toggles.forEach(toggle => {
91+
const id = toggle.getAttribute('data-details-id');
92+
if (!id) { throw new Error("element with class 'details-toggle' has no 'data-details-id' attribute!"); }
93+
const info = lookupDetailsRegistry(id);
94+
info.toggles.push(toggle);
95+
toggle.addEventListener('click', onToggleClick);
96+
if (toggle.classList.contains('details-toggle-control')) {
97+
toggle.classList.add(info.element.open ? 'collapser' : 'expander');
98+
}
99+
});
100+
}
101+
102+
export function init() {
103+
gatherDetailsElements();
104+
restoreToggled();
105+
initCollapseToggles();
106+
}

haddock-api/resources/html/js-src/init.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import * as util from "./haddock-util";
1+
import * as styleMenu from "./style-menu";
2+
import * as detailsHelper from "./details-helper";
23
import * as quickJump from "./quick-jump";
34

45
function onDomReady(callback: () => void) {
@@ -14,8 +15,8 @@ function onDomReady(callback: () => void) {
1415
}
1516

1617
onDomReady(() => {
17-
util.addStyleMenu();
18-
util.resetStyle();
19-
util.restoreCollapsed();
18+
document.body.classList.add('js-enabled');
19+
styleMenu.init();
20+
detailsHelper.init();
2021
quickJump.init();
2122
});

haddock-api/resources/html/js-src/haddock-util.ts renamed to haddock-api/resources/html/js-src/style-menu.ts

Lines changed: 9 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Haddock JavaScript utilities
22

3+
import {getCookie, setCookie, clearCookie} from "./cookies";
4+
35
const rspace = /\s\s+/g,
46
rtrim = /^\s+|\s+$/g;
57

@@ -37,77 +39,13 @@ function toggleClass(elem: Element, valueOn: string, valueOff: string, bool?: bo
3739
return bool;
3840
}
3941

40-
4142
function makeClassToggle(valueOn: string, valueOff: string): (elem: Element, bool?: boolean) => boolean {
4243
return function(elem, bool) {
4344
return toggleClass(elem, valueOn, valueOff, bool);
4445
}
4546
}
4647

4748
const toggleShow = makeClassToggle("show", "hide");
48-
const toggleCollapser = makeClassToggle("collapser", "expander");
49-
50-
function toggleSection(id: string): boolean {
51-
const b = toggleShow(document.getElementById("section." + id) as Element);
52-
toggleCollapser(document.getElementById("control." + id) as Element, b);
53-
rememberCollapsed(id);
54-
return b;
55-
}
56-
57-
// TODO: get rid of global variables
58-
if (typeof window !== 'undefined') {
59-
(window as any).toggleSection = toggleSection;
60-
}
61-
62-
const collapsed: { [id: string]: boolean } = {};
63-
function rememberCollapsed(id: string) {
64-
if(collapsed[id])
65-
delete collapsed[id]
66-
else
67-
collapsed[id] = true;
68-
69-
const sections: string[] = [];
70-
for(let i in collapsed) {
71-
if(collapsed.hasOwnProperty(i))
72-
sections.push(i);
73-
}
74-
// cookie specific to this page; don't use setCookie which sets path=/
75-
document.cookie = "collapsed=" + encodeURIComponent(sections.join('+'));
76-
}
77-
78-
export function restoreCollapsed() {
79-
const cookie = getCookie("collapsed");
80-
if(!cookie)
81-
return;
82-
83-
const ids = cookie.split('+');
84-
for(const i in ids)
85-
{
86-
if(document.getElementById("section." + ids[i]))
87-
toggleSection(ids[i]);
88-
}
89-
}
90-
91-
function setCookie(name: string, value: string) {
92-
document.cookie = name + "=" + encodeURIComponent(value) + ";path=/;";
93-
}
94-
95-
function clearCookie(name: string) {
96-
document.cookie = name + "=;path=/;expires=Thu, 01-Jan-1970 00:00:01 GMT;";
97-
}
98-
99-
function getCookie(name: string) {
100-
const nameEQ = name + "=";
101-
const ca = document.cookie.split(';');
102-
for (let i = 0; i < ca.length; i++) {
103-
let c = ca[i];
104-
while (c.charAt(0)==' ') c = c.substring(1,c.length);
105-
if (c.indexOf(nameEQ) == 0) {
106-
return decodeURIComponent(c.substring(nameEQ.length,c.length));
107-
}
108-
}
109-
return null;
110-
}
11149

11250
function addMenuItem(html: string) {
11351
const menu = document.getElementById("page-menu");
@@ -123,7 +61,7 @@ function styles(): HTMLLinkElement[] {
12361
return es.filter((a: HTMLLinkElement) => a.rel.indexOf("style") != -1 && a.title);
12462
}
12563

126-
export function addStyleMenu() {
64+
function addStyleMenu() {
12765
const as = styles();
12866
let btns = "";
12967
as.forEach((a) => {
@@ -162,12 +100,17 @@ function setActiveStyleSheet(title: string) {
162100
styleMenu(false);
163101
}
164102

165-
export function resetStyle() {
103+
function resetStyle() {
166104
const s = getCookie("haddock-style");
167105
if (s) setActiveStyleSheet(s);
168106
}
169107

170108
function styleMenu(show?: boolean) {
171109
const m = document.getElementById('style-menu');
172110
if (m) toggleShow(m, show);
111+
}
112+
113+
export function init() {
114+
addStyleMenu();
115+
resetStyle();
173116
}

haddock-api/src/Haddock/Backends/Xhtml.hs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,11 @@ mkNode qual ss p (Node s leaf pkg srcPkg short ts) =
310310
htmlModule <+> shortDescr +++ htmlPkg +++ subtree
311311
where
312312
modAttrs = case (ts, leaf) of
313-
(_:_, False) -> collapseControl p True "module"
313+
(_:_, False) -> collapseControl p "module"
314314
(_, _ ) -> [theclass "module"]
315315

316316
cBtn = case (ts, leaf) of
317-
(_:_, True) -> thespan ! collapseControl p True "" << spaceHtml
317+
(_:_, True) -> thespan ! collapseControl p "" << spaceHtml
318318
(_, _ ) -> noHtml
319319
-- We only need an explicit collapser button when the module name
320320
-- is also a leaf, and so is a link to a module page. Indeed, the
@@ -332,7 +332,12 @@ mkNode qual ss p (Node s leaf pkg srcPkg short ts) =
332332
shortDescr = maybe noHtml (origDocToHtml qual) short
333333
htmlPkg = maybe noHtml (thespan ! [theclass "package"] <<) srcPkg
334334

335-
subtree = mkNodeList qual (s:ss) p ts ! collapseSection p True ""
335+
subtree =
336+
if null ts then noHtml else
337+
collapseDetails p DetailsOpen (
338+
thesummary ! [ theclass "hide-when-js-enabled" ] << "Submodules" +++
339+
mkNodeList qual (s:ss) p ts
340+
)
336341

337342

338343

@@ -586,10 +591,12 @@ ifaceToHtml maybe_source_url maybe_wiki_url iface unicode qual
586591
| no_doc_at_all = noHtml
587592
| otherwise
588593
= divSynopsis $
589-
paragraph ! collapseControl "syn" False "caption" << "Synopsis" +++
590-
shortDeclList (
591-
mapMaybe (processExport True linksInfo unicode qual) exports
592-
) ! (collapseSection "syn" False "" ++ collapseToggle "syn")
594+
collapseDetails "syn" DetailsClosed (
595+
thesummary << "Synopsis" +++
596+
shortDeclList (
597+
mapMaybe (processExport True linksInfo unicode qual) exports
598+
) ! collapseToggle "syn" ""
599+
)
593600

594601
-- if the documentation doesn't begin with a section header, then
595602
-- add one ("Documentation").

haddock-api/src/Haddock/Backends/Xhtml/DocMarkup.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ hackMarkup fmt' h' =
165165
UntouchedDoc d -> (markup fmt $ _doc d, [_meta d])
166166
CollapsingHeader (Header lvl titl) par n nm ->
167167
let id_ = makeAnchorId $ "ch:" ++ fromMaybe "noid:" nm ++ show n
168-
expanded = False
169-
col' = collapseControl id_ expanded "caption"
170-
instTable = (thediv ! collapseSection id_ expanded [] <<)
168+
col' = collapseControl id_ "caption"
169+
summary = thesummary ! [ theclass "hide-when-js-enabled" ] << "Expand"
170+
instTable contents = collapseDetails id_ DetailsClosed (summary +++ contents)
171171
lvs = zip [1 .. ] [h1, h2, h3, h4, h5, h6]
172172
getHeader = fromMaybe caption (lookup lvl lvs)
173173
subCaption = getHeader ! col' << markup fmt titl

0 commit comments

Comments
 (0)