Rev 839 | Rev 982 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
4 | daniel-mar | 1 | /* |
2 | * OIDplus 2.0 |
||
781 | daniel-mar | 3 | * Copyright 2019 - 2022 Daniel Marschall, ViaThinkSoft |
4 | daniel-mar | 4 | * |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); |
||
6 | * you may not use this file except in compliance with the License. |
||
7 | * You may obtain a copy of the License at |
||
8 | * |
||
9 | * http://www.apache.org/licenses/LICENSE-2.0 |
||
10 | * |
||
11 | * Unless required by applicable law or agreed to in writing, software |
||
12 | * distributed under the License is distributed on an "AS IS" BASIS, |
||
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||
14 | * See the License for the specific language governing permissions and |
||
15 | * limitations under the License. |
||
16 | */ |
||
17 | |||
9 | daniel-mar | 18 | /*jshint esversion: 6 */ |
19 | |||
219 | daniel-mar | 20 | // $('#html').jstree(); |
21 | |||
833 | daniel-mar | 22 | var bs5Utils = undefined; |
23 | |||
219 | daniel-mar | 24 | var current_node = ""; |
25 | var popstate_running = false; |
||
362 | daniel-mar | 26 | // DEFAULT_LANGUAGE will be set by oidplus.min.js.php |
360 | daniel-mar | 27 | // language_messages will be set by oidplus.min.js.php |
28 | // language_tblprefix will be set by oidplus.min.js.php |
||
424 | daniel-mar | 29 | // csrf_token will be set by oidplus.min.js.php |
561 | daniel-mar | 30 | // samesite_policy will bet set by oidplus.min.js.php |
219 | daniel-mar | 31 | |
399 | daniel-mar | 32 | var pageChangeCallbacks = []; |
33 | var pageChangeRequestCallbacks = []; |
||
34 | |||
839 | daniel-mar | 35 | var pageLoadedCallbacks= { |
876 | daniel-mar | 36 | "anyPageLoad": [], // this is processed inside both AJAX successful reload and document.ready (at the very end) |
839 | daniel-mar | 37 | "ajaxPageLoad": [], // inside AJAX successful reload only |
38 | "documentReadyBefore": [], // inside document.ready, in the very beginning of the function |
||
39 | "documentReadyAfter": [] // inside document.ready, in the very end of the function |
||
40 | }; |
||
833 | daniel-mar | 41 | |
819 | daniel-mar | 42 | var oidplus_menu_width = 450; // In pixels. You can change this at runtime because of the glayoutWorkaroundB() workaround |
818 | daniel-mar | 43 | |
839 | daniel-mar | 44 | function executeAllCallbacks(functionsArray) { |
45 | functionsArray.forEach( |
||
46 | function(fel) { |
||
47 | if (typeof fel == 'function') fel(); |
||
48 | } |
||
49 | ); |
||
50 | } |
||
51 | |||
356 | daniel-mar | 52 | function isInternetExplorer() { |
641 | daniel-mar | 53 | // see also includes/functions.inc.php |
356 | daniel-mar | 54 | var ua = window.navigator.userAgent; |
55 | return ((ua.indexOf("MSIE ") > 0) || (ua.indexOf("Trident/") > 0)); |
||
56 | } |
||
57 | |||
2 | daniel-mar | 58 | String.prototype.explode = function (separator, limit) { |
59 | // https://stackoverflow.com/questions/4514323/javascript-equivalent-to-php-explode |
||
60 | const array = this.split(separator); |
||
61 | if (limit !== undefined && array.length >= limit) { |
||
62 | array.push(array.splice(limit - 1).join(separator)); |
||
63 | } |
||
64 | return array; |
||
65 | }; |
||
66 | |||
67 | String.prototype.htmlentities = function () { |
||
399 | daniel-mar | 68 | return this.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');//" |
9 | daniel-mar | 69 | }; |
2 | daniel-mar | 70 | |
71 | String.prototype.html_entity_decode = function () { |
||
72 | return $('<textarea />').html(this).text(); |
||
9 | daniel-mar | 73 | }; |
2 | daniel-mar | 74 | |
781 | daniel-mar | 75 | if (!String.prototype.replaceAll) { |
76 | /** |
||
77 | * String.prototype.replaceAll() polyfill |
||
78 | * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/ |
||
79 | * @author Chris Ferdinandi |
||
80 | * @license MIT |
||
81 | */ |
||
82 | String.prototype.replaceAll = function(str, newStr){ |
||
83 | // If a regex pattern |
||
84 | if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') { |
||
85 | return this.replace(str, newStr); |
||
86 | } |
||
87 | // If a string |
||
88 | return this.replace(new RegExp(str, 'g'), newStr); |
||
89 | }; |
||
90 | } |
||
91 | |||
92 | if (!String.prototype.startsWith) { |
||
93 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith#polyfill |
||
94 | Object.defineProperty(String.prototype, 'startsWith', { |
||
95 | value: function(search, rawPos) { |
||
96 | var pos = rawPos > 0 ? rawPos|0 : 0; |
||
97 | return this.substring(pos, pos + search.length) === search; |
||
98 | } |
||
99 | }); |
||
100 | } |
||
101 | |||
112 | daniel-mar | 102 | function getMeta(metaName) { |
561 | daniel-mar | 103 | const metas = $('meta[name='+metaName+']'); |
104 | return (metas.length == 0) ? '' : metas[0].content; |
||
112 | daniel-mar | 105 | } |
106 | |||
107 | function getOidPlusSystemTitle() { |
||
360 | daniel-mar | 108 | return getMeta('OIDplus-SystemTitle'); // do not translate |
112 | daniel-mar | 109 | } |
110 | |||
5 | daniel-mar | 111 | function combine_systemtitle_and_pagetitle(systemtitle, pagetitle) { |
309 | daniel-mar | 112 | // Please also change the function in index.php |
5 | daniel-mar | 113 | if (systemtitle == pagetitle) { |
114 | return systemtitle; |
||
115 | } else { |
||
309 | daniel-mar | 116 | return pagetitle + ' - ' + systemtitle; |
5 | daniel-mar | 117 | } |
118 | } |
||
119 | |||
385 | daniel-mar | 120 | function getSystemUrl(relative) { |
121 | var url = new URL(window.location.href); |
||
122 | if (relative) { |
||
123 | return url.pathname; |
||
124 | } else { |
||
125 | return url.href.substr(0, url.href.length-url.search.length); |
||
126 | } |
||
127 | } |
||
128 | |||
2 | daniel-mar | 129 | function getTreeLoadURL() { |
130 | var url = new URL(window.location.href); |
||
131 | var goto = url.searchParams.get("goto"); |
||
641 | daniel-mar | 132 | return (goto != null) ? "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now()+"&goto="+encodeURIComponent(goto) |
133 | : "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now(); |
||
2 | daniel-mar | 134 | } |
135 | |||
136 | function reloadContent() { |
||
154 | daniel-mar | 137 | // window.location.href = "?goto="+encodeURIComponent(current_node); |
399 | daniel-mar | 138 | if (openOidInPanel(current_node, false)) { |
450 | daniel-mar | 139 | if(!$('#oidtree').jstree(true).get_node(current_node)) { |
140 | // Avoid that a language change at "oidplus:srvreg_status" won't redirect the user to "oidplus:srv_registration" because of the reselection during refresh |
||
141 | $('#oidtree').jstree("deselect_all"); |
||
142 | } |
||
143 | |||
399 | daniel-mar | 144 | $('#oidtree').jstree("refresh"); |
145 | } |
||
2 | daniel-mar | 146 | } |
147 | |||
107 | daniel-mar | 148 | function x_rec(x_data, i) { |
149 | $('#oidtree').jstree('open_node', x_data[i], function(e, data) { |
||
150 | if (i+1 < x_data.length) { |
||
151 | x_rec(x_data, i+1); |
||
152 | } else { |
||
114 | daniel-mar | 153 | popstate_running = true; // don't call openOidInPanel again |
154 | try { |
||
155 | $('#oidtree').jstree('select_node', x_data[i]); |
||
156 | } catch (err) { |
||
157 | popstate_running = false; |
||
158 | } finally { |
||
159 | popstate_running = false; |
||
160 | } |
||
107 | daniel-mar | 161 | } |
162 | }); |
||
163 | } |
||
2 | daniel-mar | 164 | |
399 | daniel-mar | 165 | function performCloseQueryCB() { |
166 | for (var i=0; i<pageChangeRequestCallbacks.length; i++) { |
||
167 | if (!pageChangeRequestCallbacks[i][0](pageChangeRequestCallbacks[i][1])) return false; |
||
168 | } |
||
169 | pageChangeRequestCallbacks = []; |
||
170 | return true; // may close |
||
171 | } |
||
172 | |||
173 | function performCloseCB() { |
||
174 | for (var i=0; i<pageChangeCallbacks.length; i++) { |
||
175 | pageChangeCallbacks[i][0](pageChangeCallbacks[i][1]); |
||
176 | } |
||
177 | pageChangeCallbacks = []; |
||
178 | } |
||
179 | |||
180 | function openOidInPanel(id, reselect/*=false*/, anchor/*=''*/, force/*=false*/) { |
||
699 | daniel-mar | 181 | reselect = (typeof reselect === 'undefined') ? false : reselect; // do not translate |
182 | anchor = (typeof anchor === 'undefined') ? '' : anchor; // do not translate |
||
183 | force = (typeof force === 'undefined') ? false : force; // do not translate |
||
150 | daniel-mar | 184 | |
399 | daniel-mar | 185 | var mayClose = performCloseQueryCB(); |
186 | if (!force && !mayClose) return false; |
||
187 | |||
188 | performCloseCB(); |
||
405 | daniel-mar | 189 | |
704 | daniel-mar | 190 | $.xhrPool.abortAll(); |
191 | |||
107 | daniel-mar | 192 | if (reselect) { |
2 | daniel-mar | 193 | $('#oidtree').jstree('deselect_all'); |
107 | daniel-mar | 194 | |
114 | daniel-mar | 195 | popstate_running = true; // don't call openOidInPanel during tree selection |
196 | try { |
||
197 | // If the node is already loaded in the tree, select it |
||
198 | if (!$('#oidtree').jstree('select_node', id)) { |
||
199 | // If the node is not loaded, then we try to search it. |
||
200 | // If it can be found, then open all parent nodes and select the node |
||
201 | $.ajax({ |
||
202 | url:"ajax.php", |
||
203 | method:"POST", |
||
544 | daniel-mar | 204 | beforeSend: function(jqXHR, settings) { |
704 | daniel-mar | 205 | //$.xhrPool.abortAll(); |
544 | daniel-mar | 206 | $.xhrPool.add(jqXHR); |
207 | }, |
||
208 | complete: function(jqXHR, text) { |
||
209 | $.xhrPool.remove(jqXHR); |
||
210 | }, |
||
114 | daniel-mar | 211 | data:{ |
424 | daniel-mar | 212 | csrf_token:csrf_token, |
114 | daniel-mar | 213 | action:"tree_search", |
641 | daniel-mar | 214 | search:id, |
215 | anticache:Date.now() |
||
114 | daniel-mar | 216 | }, |
217 | error:function(jqXHR, textStatus, errorThrown) { |
||
544 | daniel-mar | 218 | if (errorThrown == "abort") return; |
797 | daniel-mar | 219 | console.error("Tree search failed"); |
360 | daniel-mar | 220 | console.error(_L("Error: %1",errorThrown)); |
114 | daniel-mar | 221 | }, |
222 | success:function(data) { |
||
223 | if ("error" in data) { |
||
797 | daniel-mar | 224 | console.error("Tree search failed"); |
114 | daniel-mar | 225 | console.error(data); |
226 | } else if ((data instanceof Array) && (data.length > 0)) { |
||
227 | x_rec(data, 0); |
||
228 | } else { |
||
797 | daniel-mar | 229 | console.error("Tree search failed"); |
114 | daniel-mar | 230 | console.error(data); |
231 | } |
||
232 | } |
||
233 | }); |
||
107 | daniel-mar | 234 | } |
114 | daniel-mar | 235 | } catch (err) { |
236 | popstate_running = false; |
||
237 | } finally { |
||
238 | popstate_running = false; |
||
239 | } |
||
2 | daniel-mar | 240 | } |
241 | |||
114 | daniel-mar | 242 | // This loads the actual content |
243 | |||
405 | daniel-mar | 244 | // document.title = ""; // <-- we may not do this, otherwise Firefox won't |
245 | // show titles in the browser history (right-click |
||
246 | // on back-button), although document.title() is |
||
499 | daniel-mar | 247 | // set inside the AJAX-callback [Firefox bug?!] |
405 | daniel-mar | 248 | |
2 | daniel-mar | 249 | $('#real_title').html(" "); |
355 | daniel-mar | 250 | $('#real_content').html(_L("Loading...")); |
108 | daniel-mar | 251 | $('#static_link').attr("href", "index.php?goto="+encodeURIComponent(id)); |
183 | daniel-mar | 252 | $("#gotoedit").val(id); |
2 | daniel-mar | 253 | |
254 | // Normal opening of a description |
||
560 | daniel-mar | 255 | $.ajax({ |
256 | url:"ajax.php", |
||
257 | method:"GET", |
||
258 | beforeSend: function(jqXHR, settings) { |
||
704 | daniel-mar | 259 | //$.xhrPool.abortAll(); |
560 | daniel-mar | 260 | $.xhrPool.add(jqXHR); |
261 | }, |
||
262 | complete: function(jqXHR, text) { |
||
263 | $.xhrPool.remove(jqXHR); |
||
264 | }, |
||
265 | data:{ |
||
266 | csrf_token:csrf_token, |
||
267 | action:"get_description", |
||
641 | daniel-mar | 268 | id:id, |
269 | anticache:Date.now() |
||
560 | daniel-mar | 270 | }, |
271 | error:function(jqXHR, textStatus, errorThrown) { |
||
272 | if (errorThrown == "abort") return; |
||
833 | daniel-mar | 273 | alertError(_L("Failed to load content: %1",errorThrown)); |
560 | daniel-mar | 274 | console.error(_L("Error: %1",errorThrown)); |
275 | }, |
||
276 | success:function(data) { |
||
107 | daniel-mar | 277 | if ("error" in data) { |
833 | daniel-mar | 278 | alertError(_L("Failed to load content: %1",data.error)); |
107 | daniel-mar | 279 | console.error(data.error); |
560 | daniel-mar | 280 | } else if (data.status >= 0) { |
281 | data.id = id; |
||
107 | daniel-mar | 282 | |
560 | daniel-mar | 283 | var state = { |
284 | "node_id":id, |
||
285 | "titleHTML":(data.icon ? '<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' : '') + data.title.htmlentities(), |
||
286 | "textHTML":data.text, |
||
287 | "staticlinkHREF":"index.php?goto="+encodeURIComponent(id), |
||
288 | }; |
||
289 | if (current_node != id) { |
||
290 | window.history.pushState(state, data.title, "?goto="+encodeURIComponent(id)); |
||
291 | } else { |
||
292 | window.history.replaceState(state, data.title, "?goto="+encodeURIComponent(id)); |
||
293 | } |
||
2 | daniel-mar | 294 | |
560 | daniel-mar | 295 | document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title); |
2 | daniel-mar | 296 | |
560 | daniel-mar | 297 | if (data.icon) { |
298 | $('#real_title').html('<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' + data.title.htmlentities()); |
||
299 | } else { |
||
300 | $('#real_title').html(data.title.htmlentities()); |
||
301 | } |
||
302 | $('#real_content').html(data.text); |
||
303 | document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title); |
||
304 | current_node = id; |
||
405 | daniel-mar | 305 | |
839 | daniel-mar | 306 | executeAllCallbacks(pageLoadedCallbacks.anyPageLoad); |
307 | executeAllCallbacks(pageLoadedCallbacks.ajaxPageLoad); |
||
833 | daniel-mar | 308 | |
560 | daniel-mar | 309 | if (anchor != '') { |
310 | jumpToAnchor(anchor); |
||
311 | } |
||
32 | daniel-mar | 312 | } else { |
833 | daniel-mar | 313 | alertError(_L("Failed to load content: %1",data.status)); |
560 | daniel-mar | 314 | console.error(data); |
32 | daniel-mar | 315 | } |
560 | daniel-mar | 316 | } |
2 | daniel-mar | 317 | }); |
399 | daniel-mar | 318 | |
319 | return true; |
||
2 | daniel-mar | 320 | } |
321 | |||
107 | daniel-mar | 322 | // This function opens the "parentID" node, and then selects the "childID" node (which should be beneath the parent node) |
2 | daniel-mar | 323 | function openAndSelectNode(childID, parentID) { |
324 | if ($('#oidtree').jstree(true).get_node(parentID)) { |
||
107 | daniel-mar | 325 | $('#oidtree').jstree('open_node', parentID, function(e, data) { // open parent node |
2 | daniel-mar | 326 | if ($('#oidtree').jstree(true).get_node(childID)) { // is the child there? |
107 | daniel-mar | 327 | $('#oidtree').jstree('deselect_all').jstree('select_node', childID); // select it |
2 | daniel-mar | 328 | } else { |
329 | // This can happen if the content page contains brand new items which are not in the treeview yet |
||
183 | daniel-mar | 330 | $("#gotoedit").val(childID); |
154 | daniel-mar | 331 | window.location.href = "?goto="+encodeURIComponent(childID); |
2 | daniel-mar | 332 | } |
333 | }, true); |
||
334 | } else { |
||
335 | // This should usually not happen |
||
183 | daniel-mar | 336 | $("#gotoedit").val(childID); |
154 | daniel-mar | 337 | window.location.href = "?goto="+encodeURIComponent(childID); |
2 | daniel-mar | 338 | } |
339 | } |
||
340 | |||
341 | $(window).on("popstate", function(e) { |
||
399 | daniel-mar | 342 | if (!performCloseQueryCB()) { |
343 | // TODO: does not work!!! The "back/forward" action will be cancelled, but the browser still thinks it was successful, |
||
344 | // so if you do it again, you will then jump 2 pages back, etc! |
||
345 | // This does also not help: |
||
346 | //window.history.pushState(e.originalEvent.state, e.originalEvent.title, e.originalEvent.url); |
||
347 | //window.history.forward(); |
||
348 | return; |
||
349 | } |
||
350 | |||
2 | daniel-mar | 351 | popstate_running = true; |
352 | try { |
||
353 | var data = e.originalEvent.state; |
||
354 | |||
355 | current_node = data.node_id; |
||
327 | daniel-mar | 356 | $("#gotoedit").val(current_node); |
107 | daniel-mar | 357 | $('#oidtree').jstree('deselect_all').jstree('select_node', data.node_id); |
2 | daniel-mar | 358 | $('#real_title').html(data.titleHTML); |
359 | $('#real_content').html(data.textHTML); |
||
360 | $('#static_link').attr("href", data.staticlinkHREF); |
||
112 | daniel-mar | 361 | document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.titleHTML.html_entity_decode()); |
2 | daniel-mar | 362 | } catch (err) { |
363 | popstate_running = false; |
||
364 | } finally { |
||
365 | popstate_running = false; |
||
366 | } |
||
367 | }); |
||
368 | |||
369 | $(document).ready(function () { |
||
876 | daniel-mar | 370 | |
839 | daniel-mar | 371 | executeAllCallbacks(pageLoadedCallbacks.documentReadyBefore); |
876 | daniel-mar | 372 | |
399 | daniel-mar | 373 | /* |
374 | window.onbeforeunload = function(e) { |
||
375 | // TODO: This won't be called because TinyMCE overrides it?? |
||
376 | // TODO: when the user accepted the query in performCloseQueryCB(), then the message will be shown again by the browser! |
||
377 | if (!performCloseQueryCB()) { |
||
378 | // Cancel the event |
||
379 | e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown |
||
380 | // Chrome requires returnValue to be set |
||
381 | e.returnValue = ''; |
||
382 | } else { |
||
383 | // the absence of a returnValue property on the event will guarantee the browser unload happens |
||
384 | delete e['returnValue']; |
||
385 | } |
||
386 | }; |
||
387 | */ |
||
214 | daniel-mar | 388 | |
829 | daniel-mar | 389 | if (typeof oidplus_menu_width_uservalue !== 'undefined') { |
390 | oidplus_menu_width = oidplus_menu_width_uservalue; |
||
391 | } |
||
392 | |||
2 | daniel-mar | 393 | // --- JsTree |
394 | |||
395 | $('#oidtree') |
||
396 | .jstree({ |
||
397 | plugins: ['massload','search','conditionalselect'], |
||
398 | 'core' : { |
||
399 | 'data' : { |
||
400 | "url" : getTreeLoadURL(), |
||
401 | "data" : function (node) { |
||
402 | return { "id" : node.id }; |
||
403 | } |
||
404 | }, |
||
405 | "multiple": false |
||
406 | }, |
||
407 | 'conditionalselect' : function (node) { |
||
68 | daniel-mar | 408 | if (node.original.conditionalselect !== undefined) { |
409 | return eval(node.original.conditionalselect); |
||
2 | daniel-mar | 410 | } else { |
399 | daniel-mar | 411 | return performCloseQueryCB(); |
2 | daniel-mar | 412 | } |
413 | }, |
||
414 | }) |
||
415 | .on('ready.jstree', function (e, data) { |
||
416 | var url = new URL(window.location.href); |
||
417 | var goto = url.searchParams.get("goto"); |
||
418 | if (goto == null) goto = "oidplus:system"; // the page was not called with ?goto=... |
||
183 | daniel-mar | 419 | $("#gotoedit").val(goto); |
2 | daniel-mar | 420 | |
107 | daniel-mar | 421 | // By setting current_node, select_node() will not cause ajax.php?action=get_description to load (since we already loaded the first static content via PHP, for search engines mainly) |
2 | daniel-mar | 422 | // But then we need to set the history state manually |
423 | current_node = goto; |
||
100 | daniel-mar | 424 | window.history.replaceState({ |
107 | daniel-mar | 425 | "node_id":goto, |
100 | daniel-mar | 426 | "titleHTML":$('#real_title').html(), |
427 | "textHTML":$('#real_content').html(), |
||
108 | daniel-mar | 428 | "staticlinkHREF":"index.php?goto="+encodeURIComponent(goto), |
113 | daniel-mar | 429 | }, $('#real_title').html(), "?goto="+encodeURIComponent(goto)); |
2 | daniel-mar | 430 | |
431 | if (goto != null) data.instance.select_node([goto]); |
||
128 | daniel-mar | 432 | |
643 | daniel-mar | 433 | setTimeout(glayoutWorkaroundAC, 100); |
137 | daniel-mar | 434 | setTimeout(glayoutWorkaroundB, 100); |
2 | daniel-mar | 435 | }) |
436 | .on('select_node.jstree', function (node, selected, event) { |
||
120 | daniel-mar | 437 | mobileNavClose(); |
95 | daniel-mar | 438 | |
9 | daniel-mar | 439 | var id = selected.node.id; |
114 | daniel-mar | 440 | if ((!popstate_running) && (current_node != id)) { |
399 | daniel-mar | 441 | // 4th argument: we force the reload (because in the |
442 | // conditional select above, we already asked if |
||
443 | // tinyMCE needs to be saved) |
||
444 | openOidInPanel(id, false, '', true); |
||
2 | daniel-mar | 445 | } |
446 | }); |
||
447 | |||
448 | // --- Layout |
||
449 | |||
561 | daniel-mar | 450 | $("#system_title_menu")[0].style.display = "block"; |
120 | daniel-mar | 451 | |
355 | daniel-mar | 452 | var tmpObjectTree = _L("OBJECT TREE").replace(/(.{1})/g,"$1<br>"); |
453 | tmpObjectTree = tmpObjectTree.substring(0, tmpObjectTree.length-"<br>".length); |
||
454 | |||
120 | daniel-mar | 455 | $('#oidtree').addClass('ui-layout-west'); |
456 | $('#content_window').addClass('ui-layout-center'); |
||
457 | $('#system_title_bar').addClass('ui-layout-north'); |
||
128 | daniel-mar | 458 | glayout = $('#frames').layout({ |
120 | daniel-mar | 459 | north__size: 40, |
460 | north__slidable: false, |
||
461 | north__closable: false, |
||
462 | north__resizable: false, |
||
818 | daniel-mar | 463 | west__size: oidplus_menu_width, |
120 | daniel-mar | 464 | west__spacing_closed: 20, |
465 | west__togglerLength_closed: 230, |
||
829 | daniel-mar | 466 | west__togglerAlign_closed: "center", |
355 | daniel-mar | 467 | west__togglerContent_closed: tmpObjectTree, |
468 | west__togglerTip_closed: _L("Open & Pin Menu"), |
||
469 | west__sliderTip: _L("Slide Open Menu"), |
||
120 | daniel-mar | 470 | west__slideTrigger_open: "mouseover", |
829 | daniel-mar | 471 | center__maskContents: true, // IMPORTANT - enable iframe masking |
472 | onresize_start: function() { if (typeof handle_glayout_onresize_start == 'function') handle_glayout_onresize_start(); } |
||
120 | daniel-mar | 473 | }); |
183 | daniel-mar | 474 | |
185 | daniel-mar | 475 | $("#gotobox").addClass("mobilehidden"); |
355 | daniel-mar | 476 | $("#languageBox").addClass("mobilehidden"); |
561 | daniel-mar | 477 | $("#gotobox")[0].style.display = "block"; |
183 | daniel-mar | 478 | $('#gotoedit').keypress(function(event) { |
479 | var keycode = (event.keyCode ? event.keyCode : event.which); |
||
480 | if (keycode == '13') { |
||
481 | gotoButtonClicked(); |
||
482 | } |
||
483 | }); |
||
833 | daniel-mar | 484 | |
485 | if (typeof Bs5Utils !== "undefined") { |
||
486 | Bs5Utils.defaults.toasts.position = 'top-center'; |
||
487 | Bs5Utils.defaults.toasts.stacking = true; |
||
488 | bs5Utils = new Bs5Utils(); |
||
489 | } |
||
876 | daniel-mar | 490 | |
839 | daniel-mar | 491 | executeAllCallbacks(pageLoadedCallbacks.anyPageLoad); |
492 | executeAllCallbacks(pageLoadedCallbacks.documentReadyAfter); |
||
376 | daniel-mar | 493 | }); |
2 | daniel-mar | 494 | |
829 | daniel-mar | 495 | // can be overridden if necessary |
496 | var handle_glayout_onresize_start = undefined; |
||
497 | |||
643 | daniel-mar | 498 | function glayoutWorkaroundAC() { |
211 | daniel-mar | 499 | // "Bug A": Sometimes, the design is completely destroyed after reloading the page. It does not help when glayout.resizeAll() |
137 | daniel-mar | 500 | // is called at the beginning (e.g. during the ready function), and it does not help if we wait 500ms. |
501 | // So we do it all the time. It has probably something to do with slow loading times, since the error |
||
502 | // does only appear when the page is "blank" for a short while while it is loading. |
||
503 | glayout.resizeAll(); |
||
287 | daniel-mar | 504 | |
505 | // "Bug C": With Firefox (And sometimes with Chrome), there is a gap between the content-window (including scroll bars) |
||
506 | // and the right corner of the screen. Removing the explicit width solves this problem. |
||
561 | daniel-mar | 507 | $("#content_window")[0].style.removeProperty("width"); |
643 | daniel-mar | 508 | |
509 | setTimeout(glayoutWorkaroundAC, 100); |
||
137 | daniel-mar | 510 | } |
511 | |||
512 | function glayoutWorkaroundB() { |
||
513 | // "Bug B": Sometimes, after reload, weird space between oidtree and content window, because oidtree has size of 438px |
||
818 | daniel-mar | 514 | $("#oidtree")[0].style.width = oidplus_menu_width + "px"; |
137 | daniel-mar | 515 | } |
516 | |||
120 | daniel-mar | 517 | function mobileNavClose() { |
518 | if ($("#system_title_menu").is(":hidden")) { |
||
519 | return; |
||
520 | } |
||
521 | |||
522 | $("#oidtree").slideUp("medium").promise().done(function() { |
||
523 | $("#oidtree").addClass("ui-layout-west"); |
||
524 | $("#oidtree").show(); |
||
185 | daniel-mar | 525 | // $("#gotobox").hide(); |
355 | daniel-mar | 526 | // $("#languageBox").hide(); |
185 | daniel-mar | 527 | $("#gotobox").addClass("mobilehidden"); |
355 | daniel-mar | 528 | $("#languageBox").addClass("mobilehidden"); |
120 | daniel-mar | 529 | }); |
530 | $("#system_title_menu").removeClass("active"); |
||
95 | daniel-mar | 531 | } |
532 | |||
120 | daniel-mar | 533 | function mobileNavOpen() { |
534 | $("#oidtree").hide(); |
||
535 | $("#oidtree").removeClass("ui-layout-west"); |
||
536 | $("#oidtree").slideDown("medium"); |
||
185 | daniel-mar | 537 | // $("#gotobox").show(); |
355 | daniel-mar | 538 | // $("#languageBox").show(); |
185 | daniel-mar | 539 | $("#gotobox").removeClass("mobilehidden"); |
355 | daniel-mar | 540 | $("#languageBox").removeClass("mobilehidden"); |
120 | daniel-mar | 541 | $("#system_title_menu").addClass("active"); |
542 | } |
||
543 | |||
105 | daniel-mar | 544 | function mobileNavButtonClick(sender) { |
120 | daniel-mar | 545 | if ($("#oidtree").hasClass("ui-layout-west")) { |
546 | mobileNavOpen(); |
||
103 | daniel-mar | 547 | } else { |
120 | daniel-mar | 548 | mobileNavClose(); |
103 | daniel-mar | 549 | } |
550 | } |
||
105 | daniel-mar | 551 | |
552 | function mobileNavButtonHover(sender) { |
||
553 | sender.classList.toggle("hover"); |
||
554 | } |
||
183 | daniel-mar | 555 | |
556 | function gotoButtonClicked() { |
||
704 | daniel-mar | 557 | openOidInPanel($("#gotoedit").val(), true); |
183 | daniel-mar | 558 | } |
199 | daniel-mar | 559 | |
219 | daniel-mar | 560 | function jumpToAnchor(anchor) { |
561 | window.location.href = "#" + anchor; |
||
562 | } |
||
563 | |||
355 | daniel-mar | 564 | function getCookie(cname) { |
565 | // Source: https://www.w3schools.com/js/js_cookies.asp |
||
566 | var name = cname + "="; |
||
567 | var decodedCookie = decodeURIComponent(document.cookie); |
||
568 | var ca = decodedCookie.split(';'); |
||
569 | for(var i = 0; i <ca.length; i++) { |
||
570 | var c = ca[i]; |
||
571 | while (c.charAt(0) == ' ') { |
||
572 | c = c.substring(1); |
||
573 | } |
||
574 | if (c.indexOf(name) == 0) { |
||
575 | return c.substring(name.length, c.length); |
||
576 | } |
||
577 | } |
||
578 | return undefined; |
||
579 | } |
||
580 | |||
356 | daniel-mar | 581 | function setCookie(cname, cvalue, exdays, path) { |
582 | var d = new Date(); |
||
583 | d.setTime(d.getTime() + (exdays*24*60*60*1000)); |
||
584 | var expires = exdays == 0 ? "" : "; expires="+d.toUTCString(); |
||
561 | daniel-mar | 585 | document.cookie = cname + "=" + cvalue + expires + ";path=" + path + ";SameSite=" + samesite_policy; |
356 | daniel-mar | 586 | } |
587 | |||
355 | daniel-mar | 588 | function setLanguage(lngid) { |
356 | daniel-mar | 589 | setCookie('LANGUAGE', lngid, 0/*Until browser closes*/, location.pathname); |
590 | |||
591 | $(".lng_flag").each(function(){ |
||
592 | $(this).addClass("picture_ghost"); |
||
355 | daniel-mar | 593 | }); |
675 | daniel-mar | 594 | $("#lng_flag_"+$.escapeSelector(lngid)).removeClass("picture_ghost"); |
356 | daniel-mar | 595 | |
596 | if (isInternetExplorer()) { |
||
597 | // Internet Explorer has problems with sending new cookies to new AJAX requests, so we reload the page completely |
||
598 | window.location.reload(); |
||
599 | } else { |
||
561 | daniel-mar | 600 | // TODO: Small detail: The "Go" button also needs to be re-translated |
360 | daniel-mar | 601 | reloadContent(); |
356 | daniel-mar | 602 | mobileNavClose(); |
603 | } |
||
355 | daniel-mar | 604 | } |
605 | |||
606 | function getCurrentLang() { |
||
360 | daniel-mar | 607 | // Note: If the argument "?lang=" is used, PHP will automatically set a Cookie, so it is OK when we only check for the cookie |
699 | daniel-mar | 608 | var lang = getCookie('LANGUAGE'); // do not translate |
609 | return (typeof lang != 'undefined') ? lang : DEFAULT_LANGUAGE; // do not translate |
||
355 | daniel-mar | 610 | } |
611 | |||
360 | daniel-mar | 612 | function _L() { |
613 | var args = Array.prototype.slice.call(arguments); |
||
506 | daniel-mar | 614 | var str = args.shift().trim(); |
360 | daniel-mar | 615 | |
616 | var tmp = ""; |
||
699 | daniel-mar | 617 | if (typeof language_messages[getCurrentLang()] == 'undefined') { // do not translate |
360 | daniel-mar | 618 | tmp = str; |
619 | } else { |
||
620 | var msg = language_messages[getCurrentLang()][str]; |
||
699 | daniel-mar | 621 | if (typeof msg != 'undefined') { // do not translate |
360 | daniel-mar | 622 | tmp = msg; |
623 | } else { |
||
624 | tmp = str; |
||
625 | } |
||
626 | } |
||
627 | |||
628 | tmp = tmp.replace('###', language_tblprefix); |
||
629 | |||
630 | var n = 1; |
||
631 | while (args.length > 0) { |
||
632 | var val = args.shift(); |
||
633 | tmp = tmp.replace("%"+n, val); |
||
634 | n++; |
||
635 | } |
||
636 | |||
370 | daniel-mar | 637 | tmp = tmp.replace("%%", "%"); |
638 | |||
360 | daniel-mar | 639 | return tmp; |
355 | daniel-mar | 640 | } |
532 | daniel-mar | 641 | |
642 | function show_waiting_anim() { |
||
643 | $("#loading").show(); |
||
644 | } |
||
645 | |||
646 | function hide_waiting_anim() { |
||
647 | $("#loading").hide(); |
||
648 | } |
||
544 | daniel-mar | 649 | |
650 | /* Mini-framework to abort all AJAX requests if a new request is made */ |
||
651 | |||
652 | $.xhrPool = []; |
||
653 | $.xhrPool.add = function(jqXHR) { |
||
654 | $.xhrPool.push(jqXHR); |
||
655 | } |
||
656 | $.xhrPool.remove = function(jqXHR) { |
||
657 | var index = $.xhrPool.indexOf(jqXHR); |
||
658 | if (index > -1) { |
||
659 | $.xhrPool.splice(index, 1); |
||
660 | } |
||
661 | }; |
||
662 | $.xhrPool.abortAll = function() { |
||
663 | var calls = Array.from($.xhrPool); |
||
664 | $.each(calls, function(key, value) { |
||
665 | value.abort(); |
||
666 | }); |
||
667 | } |
||
829 | daniel-mar | 668 | |
669 | /* Misc functions */ |
||
670 | |||
671 | function isNull(val, def) { |
||
672 | // For compatibility with Internet Explorer, use isNull(a,b) instead of a??b |
||
673 | if (val == null) { |
||
674 | // since null==undefined, this also works with undefined |
||
675 | return def; |
||
676 | } else { |
||
677 | return val; |
||
678 | } |
||
679 | } |
||
833 | daniel-mar | 680 | |
681 | /* Individual alert types */ |
||
682 | /* TODO: alert() blocks program flow, but alertSuccess() not! Will the mass change have negative effects (e.g. a redirect without the user seeing the toast)? */ |
||
683 | |||
684 | function alertSuccess(txt) { |
||
685 | if (typeof bs5Utils !== "undefined") { |
||
686 | bs5Utils.Snack.show('success', _L(txt), delay = 5000, dismissible = true); |
||
687 | } else { |
||
688 | alert(txt); |
||
689 | } |
||
690 | } |
||
691 | |||
692 | function alertWarning(txt) { |
||
693 | // TODO: as toast? |
||
694 | alert(txt); |
||
695 | } |
||
696 | |||
697 | function alertError(txt) { |
||
698 | // TODO: as toast? |
||
699 | alert(txt); |
||
700 | } |
||
876 | daniel-mar | 701 | |
702 | /* Misc functions */ |
||
703 | |||
704 | function copyToClipboard(elem) { |
||
705 | // Source: https://stackoverflow.com/questions/22581345/click-button-copy-to-clipboard-using-jquery |
||
706 | |||
707 | // create hidden text element, if it doesn't already exist |
||
708 | var targetId = "_hiddenCopyText_"; |
||
709 | var isInput = elem.tagName === "INPUT" || elem.tagName === "TEXTAREA"; |
||
710 | var origSelectionStart, origSelectionEnd; |
||
711 | if (isInput) { |
||
712 | // can just use the original source element for the selection and copy |
||
713 | target = elem; |
||
714 | origSelectionStart = elem.selectionStart; |
||
715 | origSelectionEnd = elem.selectionEnd; |
||
716 | } else { |
||
717 | // must use a temporary form element for the selection and copy |
||
718 | target = document.getElementById(targetId); |
||
719 | if (!target) { |
||
720 | var target = document.createElement("textarea"); |
||
721 | target.style.position = "absolute"; |
||
722 | target.style.left = "-9999px"; |
||
723 | target.style.top = "0"; |
||
724 | target.id = targetId; |
||
725 | document.body.appendChild(target); |
||
726 | } |
||
727 | target.textContent = elem.textContent; |
||
728 | } |
||
729 | // select the content |
||
730 | var currentFocus = document.activeElement; |
||
731 | target.focus(); |
||
732 | target.setSelectionRange(0, target.value.length); |
||
733 | |||
734 | // copy the selection |
||
735 | var succeed; |
||
736 | try { |
||
737 | succeed = document.execCommand("copy"); |
||
738 | } catch(e) { |
||
739 | succeed = false; |
||
740 | } |
||
741 | // restore original focus |
||
742 | if (currentFocus && typeof currentFocus.focus === "function") { |
||
743 | currentFocus.focus(); |
||
744 | } |
||
745 | |||
746 | if (isInput) { |
||
747 | // restore prior selection |
||
748 | elem.setSelectionRange(origSelectionStart, origSelectionEnd); |
||
749 | } else { |
||
750 | // clear temporary content |
||
751 | target.textContent = ""; |
||
752 | } |
||
753 | return succeed; |
||
754 | } |