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