xref: /template/readthedokus/js/readthedokus.js (revision b63c3580f6426be7f2fddd8d7c83f9561c637654)
1function ReadtheDokus()
2{
3
4	this._currentPage;
5	this._currentPageIndex;
6	this._pages;
7	this._toc = document.getElementById("dw__toc");
8	this._header = document.querySelector("header");
9	this._sidebar = document.querySelector("#dokuwiki__aside");
10	this._delimiter = ( window.location.search.indexOf(":") > -1 ? ":" : "/");
11	this._id = ( this._delimiter == ":" ? JSINFO["id"] : JSINFO["id"].split(":").join("/") );
12	this._startPage = "";
13
14}
15
16ReadtheDokus.prototype.run = function()
17{
18
19	// Enum sidebar items to
20	//   - embed toc in the corresponding sidebar item
21	//   - collect all page links
22	var isFound = false;
23	this._pages = [];
24	if (JSINFO["ACT"] == "show")
25	{
26		this._enumSidebarLinks(function(elem) {
27			// Embed toc
28			if (elem.href.indexOf(this._id) > -1)
29			{
30				this._embedToc(elem, this._toc);
31				isFound = true;
32			}
33
34			// Collect page links
35			this._pages.push(elem.href);
36		}.bind(this));
37	}
38
39	// Start page
40	if (this._pages.length > 0)
41	{
42		this._startPage = this._getStartPage(this._pages[0], this._delimiter);
43		this._pages.unshift(this._startPage);
44		var list = document.querySelectorAll("#sidebar-header > div.home > a, #page-header .breadcrumbs > .home > a");
45		var nodes = Array.prototype.slice.call(list, 0);
46		nodes.forEach(function(elem) {
47			elem.href = this._startPage;
48		}.bind(this));
49	}
50
51	// Show toc on top of sidebar if item was not found in sidebar
52	if (!isFound && this._toc)
53	{
54		this._showToc(this._toc);
55		this.showSidebar();
56	}
57
58	this._initToc(this._toc);
59	this._initMobileHeader();
60	this._initPageButtons();
61	this._sidebar.querySelector("#sidebar-header #qsearch__in").setAttribute("placeholder", "Search docs");
62
63	if (this._toc)
64	{
65		this._toc.scrollIntoView(true);
66	}
67
68};
69
70ReadtheDokus.prototype.getMediaQuery = function(elem)
71{
72
73	return getComputedStyle(document.querySelector("#__media_query")).getPropertyValue("--media-query").trim();
74
75};
76
77ReadtheDokus.prototype.toggleTocMenu = function(elem)
78{
79
80	var invisible = elem.parentNode.querySelector(".toc").classList.contains("invisible");
81	if (invisible)
82	{
83		this.expandTocMenu(elem);
84	}
85	else
86	{
87		this.collapseTocMenu(elem);
88	}
89
90};
91
92ReadtheDokus.prototype.expandTocMenu = function(elem, allChildren)
93{
94
95	if (elem && elem.classList.contains("expandable"))
96	{
97		elem.parentNode.querySelector(".toc").classList.remove("invisible");
98
99		var i = elem.children[0].children[0].children[0];
100		i.classList.remove("fa-plus-square");
101		i.classList.add("fa-minus-square");
102
103		var img = elem.children[0].children[0].children[1];
104		img.classList.remove("plus");
105		img.classList.add("minus");
106		img.src= DOKU_BASE + "lib/images/minus.gif";
107	}
108
109};
110
111ReadtheDokus.prototype.collapseTocMenu = function(elem, allChildren)
112{
113
114	if (elem && elem.classList.contains("expandable"))
115	{
116		elem.parentNode.querySelector(".toc").classList.add("invisible");
117
118		var i = elem.children[0].children[0].children[0];
119		i.classList.remove("fa-minus-square");
120		i.classList.add("fa-plus-square");
121
122		var img = elem.children[0].children[0].children[1];
123		img.classList.remove("minus");
124		img.classList.add("plus");
125		img.src=DOKU_BASE + "lib/images/plus.gif";
126	}
127
128};
129
130ReadtheDokus.prototype.toggleSidebar = function(elem)
131{
132
133	if (dokus.getMediaQuery() == "pc" || dokus.getMediaQuery() == "tb")
134	{
135		document.querySelector("#dokuwiki__site").classList.toggle("showSidebar");
136	}
137	else
138	{
139		document.querySelector("#dokuwiki__site").classList.toggle("showSidebarSP");
140	}
141
142}
143
144ReadtheDokus.prototype.showSidebar = function(elem)
145{
146
147	if (dokus.getMediaQuery() == "pc" || dokus.getMediaQuery() == "tb")
148	{
149		document.querySelector("#dokuwiki__site").classList.add("showSidebar");
150	}
151	else
152	{
153		document.querySelector("#dokuwiki__site").classList.add("showSidebarSP");
154	}
155
156}
157
158ReadtheDokus.prototype.hideSidebar = function(elem)
159{
160
161	if (dokus.getMediaQuery() == "pc" || dokus.getMediaQuery() == "tb")
162	{
163		document.querySelector("#dokuwiki__site").classList.remove("showSidebar");
164	}
165	else
166	{
167		document.querySelector("#dokuwiki__site").classList.remove("showSidebarSP");
168	}
169
170}
171
172
173ReadtheDokus.prototype._enumSidebarLinks = function(callback)
174{
175
176	callback = ( typeof callback === "function" ? callback : function(){} );
177	var links = this._sidebar.querySelectorAll(".aside > ul .level1 a");
178	var nodes = Array.prototype.slice.call(links, 0);
179	nodes.forEach(function(elem) {
180		callback(elem);
181	});
182
183};
184
185ReadtheDokus.prototype._getStartPage = function(basePage, delimiter)
186{
187
188	var result = "";
189
190	if (basePage && delimiter)
191	{
192		var re = new RegExp("\\" + delimiter + "[^\\" + delimiter + "]*[^\\" + delimiter + "]*$");
193		result = basePage.replace(re, "").replace(re, "") + delimiter + "start";
194	}
195
196	return result;
197
198};
199
200ReadtheDokus.prototype._embedToc = function(target, toc)
201{
202
203	if (target && toc)
204	{
205		target.parentNode.parentNode.appendChild(toc);
206		target.parentNode.style.display = "none";
207	}
208
209};
210
211ReadtheDokus.prototype._showToc = function(toc)
212{
213
214	if (toc)
215	{
216		this._toc.parentNode.style.display = "block";
217	}
218
219};
220
221ReadtheDokus.prototype._initToc = function(toc)
222{
223
224	if (toc)
225	{
226		this._installTocSelectHandler();
227		this._installTocMenuHandler();
228		this._installTocJumpHandler();
229	}
230
231};
232
233// Install click handler to highlight and expand toc menu
234ReadtheDokus.prototype._installTocSelectHandler = function()
235{
236
237	var list = this._toc.querySelectorAll(".level1 div.li");
238	var nodes = Array.prototype.slice.call(list, 0);
239	nodes.forEach(function(elem) {
240		elem.addEventListener("click", function() {
241			// Get level2 parent
242			let p = this._getParent(elem, "level2");
243
244			// Remove all current
245			var list2 = this._toc.querySelectorAll(".current");
246			var nodes2 = Array.prototype.slice.call(list2, 0);
247			nodes2.forEach(function(elem) {
248				elem.classList.remove("current");
249			});
250
251			// Set current to this and level2 parent
252			if (p)
253			{
254				p.parentNode.classList.add("current");
255				p.classList.add("current");
256				elem.classList.add("current");
257				elem.scrollIntoView(true);
258			}
259
260			// Expand
261			this.expandTocMenu(elem);
262
263			// Fold the other level2 items
264			var list3 = this._toc.querySelectorAll(".level2 > div.li.expandable");
265			var nodes3 = Array.prototype.slice.call(list3, 0);
266			nodes3.forEach(function(item) {
267				if (item != p)
268				{
269					this.collapseTocMenu(item);
270				}
271			}.bind(this));
272		}.bind(this));
273	}.bind(this));
274
275};
276
277// Install click handler to expand/collapse toc menu
278ReadtheDokus.prototype._installTocMenuHandler = function()
279{
280
281	// Search for toc menu items which have children
282	var list = this._toc.querySelectorAll("div.li");
283	var nodes = Array.prototype.slice.call(list, 0);
284	nodes.forEach(function(elem) {
285		if (elem.parentNode.querySelector(".toc"))
286		{
287			elem.classList.add("expandable");
288
289			// Insert +/- fontawesome icon and image
290			elem.children[0].insertAdjacentHTML("afterbegin", '<div class="btn-expand"><i class="far fa-minus-square"></i><img class="minus" src="' + DOKU_BASE + 'lib/images/minus.gif" alt="−"></div>');
291
292			// Install click handler
293			elem.children[0].children[0].addEventListener("click", function(e) {
294				this.toggleTocMenu(elem);
295
296				e.stopPropagation();
297				e.preventDefault();
298			}.bind(this));
299
300			// Only level1 menu items are open at start
301			if (!elem.parentNode.classList.contains("level1"))
302			{
303				this.collapseTocMenu(elem);
304			}
305		}
306
307		// Install click handler to move an clicked item to top
308		elem.addEventListener("click", function() {
309			elem.scrollIntoView(true);
310		});
311	}.bind(this));
312
313};
314
315// Install click handler to jump to anchor taking fixed header into account
316ReadtheDokus.prototype._installTocJumpHandler = function()
317{
318
319	var headerHight = this._header.height;
320	var list = this._toc.querySelectorAll('a[href*="#"]');
321	var nodes = Array.prototype.slice.call(list, 0);
322	nodes.forEach(function(elem){
323		elem.addEventListener("click", function(e) {
324			var hash = elem.getAttribute("href");
325			var target = document.querySelector(hash);
326			if (target)
327			{
328				if (dokus.getMediaQuery() == "sp")
329				{
330					this.hideSidebar();
331				}
332
333				var top = target.getBoundingClientRect().top;
334				window.scrollTo({top:window.pageYOffset + top - 50});
335			}
336
337			e.preventDefault();
338			return false;
339		}.bind(this));
340	}.bind(this));
341
342};
343ReadtheDokus.prototype._getParent = function(elem, level)
344{
345
346	let current = elem.parentNode;
347
348	while (current && !current.classList.contains("level1"))
349	{
350		if (current.classList.contains(level))
351		{
352			return current.children[0];
353		}
354
355		current = current.parentNode.parentNode;
356	}
357
358	return null;
359
360};
361
362ReadtheDokus.prototype._initMobileHeader = function()
363{
364
365	// Add click event handler for mobile menu
366	document.getElementById("btn-mobilemenu").addEventListener("click", function(){
367		this.toggleSidebar();
368	}.bind(this));
369
370};
371
372ReadtheDokus.prototype._initPageButtons = function()
373{
374
375	// Get current page (remove hash)
376	this._currentPage = window.location.href.replace(/#.*$/, "");
377
378	// Get current page index
379	this._currentPageIndex = this._pages.indexOf(this._currentPage);
380
381	// Show prev button
382	if (this._currentPageIndex > 0)
383	{
384		document.getElementById("btn-prevpage").classList.remove("invisible");
385		document.getElementById("btn-prevpage").href = this._pages[this._currentPageIndex - 1];
386	}
387
388	// Show next button
389	if (this._currentPageIndex > -1 && this._currentPageIndex < this._pages.length - 1)
390	{
391		document.getElementById("btn-nextpage").classList.remove("invisible");
392		document.getElementById("btn-nextpage").href = this._pages[this._currentPageIndex + 1];
393	}
394
395};
396