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