xref: /template/readthedokus/js/readthedokus.js (revision 8c5da031f9ca838b351713a6f78cb26a5fd5cec4)
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() || getComputedStyle(document.querySelector("#__media_query"))["-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	var mq = this.getMediaQuery();
137	if (mq == "pc" || mq == "tb")
138	{
139		document.querySelector("#dokuwiki__site").classList.toggle("showSidebar");
140	}
141	else
142	{
143		document.querySelector("#dokuwiki__site").classList.toggle("showSidebarSP");
144	}
145
146}
147
148ReadtheDokus.prototype.showSidebar = function(elem)
149{
150
151	var mq = this.getMediaQuery();
152	if (mq == "pc" || mq == "tb")
153	{
154		document.querySelector("#dokuwiki__site").classList.add("showSidebar");
155	}
156	else
157	{
158		document.querySelector("#dokuwiki__site").classList.add("showSidebarSP");
159	}
160
161}
162
163ReadtheDokus.prototype.hideSidebar = function(elem)
164{
165
166	if (dokus.getMediaQuery() == "pc" || dokus.getMediaQuery() == "tb")
167	{
168		document.querySelector("#dokuwiki__site").classList.remove("showSidebar");
169	}
170	else
171	{
172		document.querySelector("#dokuwiki__site").classList.remove("showSidebarSP");
173	}
174
175}
176
177
178ReadtheDokus.prototype._enumSidebarLinks = function(callback)
179{
180
181	callback = ( typeof callback === "function" ? callback : function(){} );
182	var links = this._sidebar.querySelectorAll(".aside > #sidebar > ul .level1 a");
183	var nodes = Array.prototype.slice.call(links, 0);
184	nodes.forEach(function(elem) {
185		callback(elem);
186	});
187
188};
189
190ReadtheDokus.prototype._getStartPage = function(basePage, delimiter)
191{
192
193	var result = "";
194
195	if (basePage && delimiter)
196	{
197		var re = new RegExp("\\" + delimiter + "[^\\" + delimiter + "]*[^\\" + delimiter + "]*$");
198		result = basePage.replace(re, "").replace(re, "") + delimiter + "start";
199
200	}
201
202	return result;
203
204};
205
206ReadtheDokus.prototype._embedToc = function(target, toc)
207{
208
209	if (target && toc)
210	{
211		target.parentNode.parentNode.appendChild(toc);
212		target.parentNode.style.display = "none";
213	}
214
215};
216
217ReadtheDokus.prototype._showToc = function(toc)
218{
219
220	if (toc)
221	{
222		this._toc.parentNode.style.display = "block";
223	}
224
225};
226
227ReadtheDokus.prototype._initToc = function(toc)
228{
229
230	if (toc)
231	{
232		this._installTocSelectHandler();
233		this._installTocMenuHandler();
234		this._installTocJumpHandler();
235	}
236
237};
238
239// Install click handler to highlight and expand toc menu
240ReadtheDokus.prototype._installTocSelectHandler = function()
241{
242
243	var list = this._toc.querySelectorAll(".level1 div.li");
244	var nodes = Array.prototype.slice.call(list, 0);
245	nodes.forEach(function(elem) {
246		elem.addEventListener("click", function() {
247			// Get level2 parent
248			let p = this._getParent(elem, "level2");
249
250			// Remove all current
251			var list2 = this._toc.querySelectorAll(".current");
252			var nodes2 = Array.prototype.slice.call(list2, 0);
253			nodes2.forEach(function(elem) {
254				elem.classList.remove("current");
255			});
256
257			// Set current to this and level2 parent
258			if (p)
259			{
260				p.parentNode.classList.add("current");
261				p.classList.add("current");
262				elem.classList.add("current");
263				elem.scrollIntoView(true);
264			}
265
266			// Expand
267			this.expandTocMenu(elem);
268
269			// Fold the other level2 items
270			var list3 = this._toc.querySelectorAll(".level2 > div.li.expandable");
271			var nodes3 = Array.prototype.slice.call(list3, 0);
272			nodes3.forEach(function(item) {
273				if (item != p)
274				{
275					this.collapseTocMenu(item);
276				}
277			}.bind(this));
278		}.bind(this));
279	}.bind(this));
280
281};
282
283// Install click handler to expand/collapse toc menu
284ReadtheDokus.prototype._installTocMenuHandler = function()
285{
286
287	// Search for toc menu items which have children
288	var list = this._toc.querySelectorAll("div.li");
289	var nodes = Array.prototype.slice.call(list, 0);
290	nodes.forEach(function(elem) {
291		if (elem.parentNode.querySelector(".toc"))
292		{
293			elem.classList.add("expandable");
294
295			// Insert +/- fontawesome icon and image
296			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>');
297
298			// Install click handler
299			elem.children[0].children[0].addEventListener("click", function(e) {
300				this.toggleTocMenu(elem);
301
302				e.stopPropagation();
303				e.preventDefault();
304			}.bind(this));
305
306			// Only level1 menu items are open at start
307			if (!elem.parentNode.classList.contains("level1"))
308			{
309				this.collapseTocMenu(elem);
310			}
311		}
312
313		// Install click handler to move an clicked item to top
314		elem.addEventListener("click", function() {
315			elem.scrollIntoView(true);
316		});
317	}.bind(this));
318
319};
320
321// Install click handler to jump to anchor taking fixed header into account
322ReadtheDokus.prototype._installTocJumpHandler = function()
323{
324
325	var headerHeight = this._header.offsetHeight;
326	var list = this._toc.querySelectorAll('a[href*="#"]');
327	var nodes = Array.prototype.slice.call(list, 0);
328	nodes.forEach(function(elem){
329		elem.addEventListener("click", function(e) {
330			var hash = elem.getAttribute("href");
331			var target = document.querySelector(hash);
332			if (target)
333			{
334				if (dokus.getMediaQuery() == "sp")
335				{
336					this.hideSidebar();
337				}
338
339				var top = target.getBoundingClientRect().top;
340				window.scrollTo(0, window.pageYOffset + top - headerHeight);
341			}
342
343			e.preventDefault();
344			return false;
345		}.bind(this));
346	}.bind(this));
347
348};
349ReadtheDokus.prototype._getParent = function(elem, level)
350{
351
352	let current = elem.parentNode;
353
354	while (current && !current.classList.contains("level1"))
355	{
356		if (current.classList.contains(level))
357		{
358			return current.children[0];
359		}
360
361		current = current.parentNode.parentNode;
362	}
363
364	return null;
365
366};
367
368ReadtheDokus.prototype._initMobileHeader = function()
369{
370
371	// Add click event handler for mobile menu
372	document.getElementById("btn-mobilemenu").addEventListener("click", function(){
373		this.toggleSidebar();
374	}.bind(this));
375
376};
377
378ReadtheDokus.prototype._initPageButtons = function()
379{
380
381	// Get current page (remove hash)
382	this._currentPage = window.location.href.replace(/#.*$/, "");
383
384	// Get current page index
385	this._currentPageIndex = this._pages.indexOf(this._currentPage);
386
387	// Show prev button
388	if (this._currentPageIndex > 0)
389	{
390		document.getElementById("btn-prevpage").classList.remove("invisible");
391		document.getElementById("btn-prevpage").href = this._pages[this._currentPageIndex - 1];
392	}
393
394	// Show next button
395	if (this._currentPageIndex > -1 && this._currentPageIndex < this._pages.length - 1)
396	{
397		document.getElementById("btn-nextpage").classList.remove("invisible");
398		document.getElementById("btn-nextpage").href = this._pages[this._currentPageIndex + 1];
399	}
400
401};
402