Subversion Repositories oidplus

Rev

Rev 797 | Rev 819 | 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
 
22
var current_node = "";
23
var popstate_running = false;
362 daniel-mar 24
// DEFAULT_LANGUAGE will be set by oidplus.min.js.php
360 daniel-mar 25
// language_messages will be set by oidplus.min.js.php
26
// language_tblprefix will be set by oidplus.min.js.php
424 daniel-mar 27
// csrf_token will be set by oidplus.min.js.php
561 daniel-mar 28
// samesite_policy will bet set by oidplus.min.js.php
219 daniel-mar 29
 
399 daniel-mar 30
var pageChangeCallbacks = [];
31
var pageChangeRequestCallbacks = [];
32
 
818 daniel-mar 33
var oidplus_menu_width = 450; // In pixels. TODO: Make configurable
34
 
356 daniel-mar 35
function isInternetExplorer() {
641 daniel-mar 36
        // see also includes/functions.inc.php
356 daniel-mar 37
        var ua = window.navigator.userAgent;
38
        return ((ua.indexOf("MSIE ") > 0) || (ua.indexOf("Trident/") > 0));
39
}
40
 
2 daniel-mar 41
String.prototype.explode = function (separator, limit) {
42
        // https://stackoverflow.com/questions/4514323/javascript-equivalent-to-php-explode
43
        const array = this.split(separator);
44
        if (limit !== undefined && array.length >= limit) {
45
                array.push(array.splice(limit - 1).join(separator));
46
        }
47
        return array;
48
};
49
 
50
String.prototype.htmlentities = function () {
399 daniel-mar 51
        return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');//"
9 daniel-mar 52
};
2 daniel-mar 53
 
54
String.prototype.html_entity_decode = function () {
55
        return $('<textarea />').html(this).text();
9 daniel-mar 56
};
2 daniel-mar 57
 
781 daniel-mar 58
if (!String.prototype.replaceAll) {
59
        /**
60
         * String.prototype.replaceAll() polyfill
61
         * https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
62
         * @author Chris Ferdinandi
63
         * @license MIT
64
         */
65
        String.prototype.replaceAll = function(str, newStr){
66
                // If a regex pattern
67
                if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
68
                        return this.replace(str, newStr);
69
                }
70
                // If a string
71
                return this.replace(new RegExp(str, 'g'), newStr);
72
        };
73
}
74
 
75
if (!String.prototype.startsWith) {
76
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith#polyfill
77
        Object.defineProperty(String.prototype, 'startsWith', {
78
                value: function(search, rawPos) {
79
                        var pos = rawPos > 0 ? rawPos|0 : 0;
80
                        return this.substring(pos, pos + search.length) === search;
81
                }
82
        });
83
}
84
 
112 daniel-mar 85
function getMeta(metaName) {
561 daniel-mar 86
        const metas = $('meta[name='+metaName+']');
87
        return (metas.length == 0) ? '' : metas[0].content;
112 daniel-mar 88
}
89
 
90
function getOidPlusSystemTitle() {
360 daniel-mar 91
        return getMeta('OIDplus-SystemTitle'); // do not translate
112 daniel-mar 92
}
93
 
5 daniel-mar 94
function combine_systemtitle_and_pagetitle(systemtitle, pagetitle) {
309 daniel-mar 95
        // Please also change the function in index.php
5 daniel-mar 96
        if (systemtitle == pagetitle) {
97
                return systemtitle;
98
        } else {
309 daniel-mar 99
                return pagetitle + ' - ' + systemtitle;
5 daniel-mar 100
        }
101
}
102
 
385 daniel-mar 103
function getSystemUrl(relative) {
104
        var url = new URL(window.location.href);
105
        if (relative) {
106
                return url.pathname;
107
        } else {
108
                return url.href.substr(0, url.href.length-url.search.length);
109
        }
110
}
111
 
2 daniel-mar 112
function getTreeLoadURL() {
113
        var url = new URL(window.location.href);
114
        var goto = url.searchParams.get("goto");
641 daniel-mar 115
        return (goto != null) ? "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now()+"&goto="+encodeURIComponent(goto)
116
                              : "ajax.php?csrf_token="+encodeURIComponent(csrf_token)+"&action=tree_load&anticache="+Date.now();
2 daniel-mar 117
}
118
 
119
function reloadContent() {
154 daniel-mar 120
        // window.location.href = "?goto="+encodeURIComponent(current_node);
399 daniel-mar 121
        if (openOidInPanel(current_node, false)) {
450 daniel-mar 122
                if(!$('#oidtree').jstree(true).get_node(current_node)) {
123
                        // Avoid that a language change at "oidplus:srvreg_status" won't redirect the user to "oidplus:srv_registration" because of the reselection during refresh
124
                        $('#oidtree').jstree("deselect_all");
125
                }
126
 
399 daniel-mar 127
                $('#oidtree').jstree("refresh");
128
        }
2 daniel-mar 129
}
130
 
107 daniel-mar 131
function x_rec(x_data, i) {
132
        $('#oidtree').jstree('open_node', x_data[i], function(e, data) {
133
                if (i+1 < x_data.length) {
134
                        x_rec(x_data, i+1);
135
                } else {
114 daniel-mar 136
                        popstate_running = true; // don't call openOidInPanel again
137
                        try {
138
                                $('#oidtree').jstree('select_node', x_data[i]);
139
                        } catch (err) {
140
                                popstate_running = false;
141
                        } finally {
142
                                popstate_running = false;
143
                        }
107 daniel-mar 144
                }
145
        });
146
}
2 daniel-mar 147
 
399 daniel-mar 148
function performCloseQueryCB() {
149
        for (var i=0; i<pageChangeRequestCallbacks.length; i++) {
150
                if (!pageChangeRequestCallbacks[i][0](pageChangeRequestCallbacks[i][1])) return false;
151
        }
152
        pageChangeRequestCallbacks = [];
153
        return true; // may close
154
}
155
 
156
function performCloseCB() {
157
        for (var i=0; i<pageChangeCallbacks.length; i++) {
158
                pageChangeCallbacks[i][0](pageChangeCallbacks[i][1]);
159
        }
160
        pageChangeCallbacks = [];
161
}
162
 
163
function openOidInPanel(id, reselect/*=false*/, anchor/*=''*/, force/*=false*/) {
699 daniel-mar 164
        reselect = (typeof reselect === 'undefined') ? false : reselect; // do not translate
165
        anchor = (typeof anchor === 'undefined') ? '' : anchor; // do not translate
166
        force = (typeof force === 'undefined') ? false : force; // do not translate
150 daniel-mar 167
 
399 daniel-mar 168
        var mayClose = performCloseQueryCB();
169
        if (!force && !mayClose) return false;
170
 
171
        performCloseCB();
405 daniel-mar 172
 
704 daniel-mar 173
        $.xhrPool.abortAll();
174
 
107 daniel-mar 175
        if (reselect) {
2 daniel-mar 176
                $('#oidtree').jstree('deselect_all');
107 daniel-mar 177
 
114 daniel-mar 178
                popstate_running = true; // don't call openOidInPanel during tree selection
179
                try {
180
                        // If the node is already loaded in the tree, select it
181
                        if (!$('#oidtree').jstree('select_node', id)) {
182
                                // If the node is not loaded, then we try to search it.
183
                                // If it can be found, then open all parent nodes and select the node
184
                                $.ajax({
185
                                        url:"ajax.php",
186
                                        method:"POST",
544 daniel-mar 187
                                        beforeSend: function(jqXHR, settings) {
704 daniel-mar 188
                                                //$.xhrPool.abortAll();
544 daniel-mar 189
                                                $.xhrPool.add(jqXHR);
190
                                        },
191
                                        complete: function(jqXHR, text) {
192
                                                $.xhrPool.remove(jqXHR);
193
                                        },
114 daniel-mar 194
                                        data:{
424 daniel-mar 195
                                                csrf_token:csrf_token,
114 daniel-mar 196
                                                action:"tree_search",
641 daniel-mar 197
                                                search:id,
198
                                                anticache:Date.now()
114 daniel-mar 199
                                        },
200
                                        error:function(jqXHR, textStatus, errorThrown) {
544 daniel-mar 201
                                                if (errorThrown == "abort") return;
797 daniel-mar 202
                                                console.error("Tree search failed");
360 daniel-mar 203
                                                console.error(_L("Error: %1",errorThrown));
114 daniel-mar 204
                                        },
205
                                        success:function(data) {
206
                                                if ("error" in data) {
797 daniel-mar 207
                                                        console.error("Tree search failed");
114 daniel-mar 208
                                                        console.error(data);
209
                                                } else if ((data instanceof Array) && (data.length > 0)) {
210
                                                        x_rec(data, 0);
211
                                                } else {
797 daniel-mar 212
                                                        console.error("Tree search failed");
114 daniel-mar 213
                                                        console.error(data);
214
                                                }
215
                                        }
216
                                });
107 daniel-mar 217
                        }
114 daniel-mar 218
                } catch (err) {
219
                        popstate_running = false;
220
                } finally {
221
                        popstate_running = false;
222
                }
2 daniel-mar 223
        }
224
 
114 daniel-mar 225
        // This loads the actual content
226
 
405 daniel-mar 227
        // document.title = ""; // <-- we may not do this, otherwise Firefox won't
228
        //                            show titles in the browser history (right-click
229
        //                            on back-button), although document.title() is
499 daniel-mar 230
        //                            set inside the AJAX-callback [Firefox bug?!]
405 daniel-mar 231
 
2 daniel-mar 232
        $('#real_title').html("&nbsp;");
355 daniel-mar 233
        $('#real_content').html(_L("Loading..."));
108 daniel-mar 234
        $('#static_link').attr("href", "index.php?goto="+encodeURIComponent(id));
183 daniel-mar 235
        $("#gotoedit").val(id);
2 daniel-mar 236
 
237
        // Normal opening of a description
560 daniel-mar 238
        $.ajax({
239
                url:"ajax.php",
240
                method:"GET",
241
                beforeSend: function(jqXHR, settings) {
704 daniel-mar 242
                        //$.xhrPool.abortAll();
560 daniel-mar 243
                        $.xhrPool.add(jqXHR);
244
                },
245
                complete: function(jqXHR, text) {
246
                        $.xhrPool.remove(jqXHR);
247
                },
248
                data:{
249
                        csrf_token:csrf_token,
250
                        action:"get_description",
641 daniel-mar 251
                        id:id,
252
                        anticache:Date.now()
560 daniel-mar 253
                },
254
                error:function(jqXHR, textStatus, errorThrown) {
255
                        if (errorThrown == "abort") return;
256
                        alert(_L("Failed to load content: %1",errorThrown));
257
                        console.error(_L("Error: %1",errorThrown));
258
                },
259
                success:function(data) {
107 daniel-mar 260
                        if ("error" in data) {
360 daniel-mar 261
                                alert(_L("Failed to load content: %1",data.error));
107 daniel-mar 262
                                console.error(data.error);
560 daniel-mar 263
                        } else if (data.status >= 0) {
264
                                data.id = id;
107 daniel-mar 265
 
560 daniel-mar 266
                                var state = {
267
                                        "node_id":id,
268
                                        "titleHTML":(data.icon ? '<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' : '') + data.title.htmlentities(),
269
                                        "textHTML":data.text,
270
                                        "staticlinkHREF":"index.php?goto="+encodeURIComponent(id),
271
                                };
272
                                if (current_node != id) {
273
                                        window.history.pushState(state, data.title, "?goto="+encodeURIComponent(id));
274
                                } else {
275
                                        window.history.replaceState(state, data.title, "?goto="+encodeURIComponent(id));
276
                                }
2 daniel-mar 277
 
560 daniel-mar 278
                                document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
2 daniel-mar 279
 
560 daniel-mar 280
                                if (data.icon) {
281
                                        $('#real_title').html('<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' + data.title.htmlentities());
282
                                } else {
283
                                        $('#real_title').html(data.title.htmlentities());
284
                                }
285
                                $('#real_content').html(data.text);
286
                                document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
287
                                current_node = id;
405 daniel-mar 288
 
560 daniel-mar 289
                                if (anchor != '') {
290
                                        jumpToAnchor(anchor);
291
                                }
32 daniel-mar 292
                        } else {
560 daniel-mar 293
                                alert(_L("Failed to load content: %1",data.status));
294
                                console.error(data);
32 daniel-mar 295
                        }
560 daniel-mar 296
                }
2 daniel-mar 297
        });
399 daniel-mar 298
 
299
        return true;
2 daniel-mar 300
}
301
 
107 daniel-mar 302
// This function opens the "parentID" node, and then selects the "childID" node (which should be beneath the parent node)
2 daniel-mar 303
function openAndSelectNode(childID, parentID) {
304
        if ($('#oidtree').jstree(true).get_node(parentID)) {
107 daniel-mar 305
                $('#oidtree').jstree('open_node', parentID, function(e, data) { // open parent node
2 daniel-mar 306
                        if ($('#oidtree').jstree(true).get_node(childID)) { // is the child there?
107 daniel-mar 307
                                $('#oidtree').jstree('deselect_all').jstree('select_node', childID); // select it
2 daniel-mar 308
                        } else {
309
                                // This can happen if the content page contains brand new items which are not in the treeview yet
183 daniel-mar 310
                                $("#gotoedit").val(childID);
154 daniel-mar 311
                                window.location.href = "?goto="+encodeURIComponent(childID);
2 daniel-mar 312
                        }
313
                }, true);
314
        } else {
315
                // This should usually not happen
183 daniel-mar 316
                $("#gotoedit").val(childID);
154 daniel-mar 317
                window.location.href = "?goto="+encodeURIComponent(childID);
2 daniel-mar 318
        }
319
}
320
 
321
$(window).on("popstate", function(e) {
399 daniel-mar 322
        if (!performCloseQueryCB()) {
323
                // TODO: does not work!!! The "back/forward" action will be cancelled, but the browser still thinks it was successful,
324
                // so if you do it again, you will then jump 2 pages back, etc!
325
                // This does also not help:
326
                //window.history.pushState(e.originalEvent.state, e.originalEvent.title, e.originalEvent.url);
327
                //window.history.forward();
328
                return;
329
        }
330
 
2 daniel-mar 331
        popstate_running = true;
332
        try {
333
                var data = e.originalEvent.state;
334
 
335
                current_node = data.node_id;
327 daniel-mar 336
                $("#gotoedit").val(current_node);
107 daniel-mar 337
                $('#oidtree').jstree('deselect_all').jstree('select_node', data.node_id);
2 daniel-mar 338
                $('#real_title').html(data.titleHTML);
339
                $('#real_content').html(data.textHTML);
340
                $('#static_link').attr("href", data.staticlinkHREF);
112 daniel-mar 341
                document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.titleHTML.html_entity_decode());
2 daniel-mar 342
        } catch (err) {
343
                popstate_running = false;
344
        } finally {
345
                popstate_running = false;
346
        }
347
});
348
 
349
$(document).ready(function () {
399 daniel-mar 350
        /*
351
        window.onbeforeunload = function(e) {
352
                // TODO: This won't be called because TinyMCE overrides it??
353
                // TODO: when the user accepted the query in performCloseQueryCB(), then the message will be shown again by the browser!
354
                if (!performCloseQueryCB()) {
355
                        // Cancel the event
356
                        e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
357
                        // Chrome requires returnValue to be set
358
                        e.returnValue = '';
359
                } else {
360
                        // the absence of a returnValue property on the event will guarantee the browser unload happens
361
                        delete e['returnValue'];
362
                }
363
        };
364
        */
214 daniel-mar 365
 
2 daniel-mar 366
        // --- JsTree
367
 
368
        $('#oidtree')
369
        .jstree({
370
                plugins: ['massload','search','conditionalselect'],
371
                'core' : {
372
                        'data' : {
373
                                "url" : getTreeLoadURL(),
374
                                "data" : function (node) {
375
                                        return { "id" : node.id };
376
                                }
377
                        },
378
                        "multiple": false
379
                },
380
                'conditionalselect' : function (node) {
68 daniel-mar 381
                        if (node.original.conditionalselect !== undefined) {
382
                                return eval(node.original.conditionalselect);
2 daniel-mar 383
                        } else {
399 daniel-mar 384
                                return performCloseQueryCB();
2 daniel-mar 385
                        }
386
                },
387
        })
388
        .on('ready.jstree', function (e, data) {
389
                var url = new URL(window.location.href);
390
                var goto = url.searchParams.get("goto");
391
                if (goto == null) goto = "oidplus:system"; // the page was not called with ?goto=...
183 daniel-mar 392
                $("#gotoedit").val(goto);
2 daniel-mar 393
 
107 daniel-mar 394
                // 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 395
                // But then we need to set the history state manually
396
                current_node = goto;
100 daniel-mar 397
                window.history.replaceState({
107 daniel-mar 398
                        "node_id":goto,
100 daniel-mar 399
                        "titleHTML":$('#real_title').html(),
400
                        "textHTML":$('#real_content').html(),
108 daniel-mar 401
                        "staticlinkHREF":"index.php?goto="+encodeURIComponent(goto),
113 daniel-mar 402
                }, $('#real_title').html(), "?goto="+encodeURIComponent(goto));
2 daniel-mar 403
 
404
                if (goto != null) data.instance.select_node([goto]);
128 daniel-mar 405
 
643 daniel-mar 406
                setTimeout(glayoutWorkaroundAC, 100);
137 daniel-mar 407
                setTimeout(glayoutWorkaroundB, 100);
2 daniel-mar 408
        })
409
        .on('select_node.jstree', function (node, selected, event) {
120 daniel-mar 410
                mobileNavClose();
95 daniel-mar 411
 
9 daniel-mar 412
                var id = selected.node.id;
114 daniel-mar 413
                if ((!popstate_running) && (current_node != id)) {
399 daniel-mar 414
                        // 4th argument: we force the reload (because in the
415
                        // conditional select above, we already asked if
416
                        // tinyMCE needs to be saved)
417
                        openOidInPanel(id, false, '', true);
2 daniel-mar 418
                }
419
        });
420
 
421
        // --- Layout
422
 
561 daniel-mar 423
        $("#system_title_menu")[0].style.display = "block";
120 daniel-mar 424
 
355 daniel-mar 425
        var tmpObjectTree = _L("OBJECT TREE").replace(/(.{1})/g,"$1<br>");
426
        tmpObjectTree = tmpObjectTree.substring(0, tmpObjectTree.length-"<br>".length);
427
 
120 daniel-mar 428
        $('#oidtree').addClass('ui-layout-west');
429
        $('#content_window').addClass('ui-layout-center');
430
        $('#system_title_bar').addClass('ui-layout-north');
128 daniel-mar 431
        glayout = $('#frames').layout({
120 daniel-mar 432
                north__size:                  40,
433
                north__slidable:              false,
434
                north__closable:              false,
435
                north__resizable:             false,
818 daniel-mar 436
                west__size:                   oidplus_menu_width,
120 daniel-mar 437
                west__spacing_closed:         20,
438
                west__togglerLength_closed:   230,
695 daniel-mar 439
                west__togglerAlign_closed:    "center", // TODO: does not work! The text "OBJECT TREE" is still at the top and nearly cut off by the title bar!!!
355 daniel-mar 440
                west__togglerContent_closed:  tmpObjectTree,
441
                west__togglerTip_closed:      _L("Open & Pin Menu"),
442
                west__sliderTip:              _L("Slide Open Menu"),
120 daniel-mar 443
                west__slideTrigger_open:      "mouseover",
444
                center__maskContents:         true // IMPORTANT - enable iframe masking
445
        });
183 daniel-mar 446
 
185 daniel-mar 447
        $("#gotobox").addClass("mobilehidden");
355 daniel-mar 448
        $("#languageBox").addClass("mobilehidden");
561 daniel-mar 449
        $("#gotobox")[0].style.display = "block";
183 daniel-mar 450
        $('#gotoedit').keypress(function(event) {
451
                var keycode = (event.keyCode ? event.keyCode : event.which);
452
                if (keycode == '13') {
453
                        gotoButtonClicked();
454
                }
455
        });
376 daniel-mar 456
});
2 daniel-mar 457
 
643 daniel-mar 458
function glayoutWorkaroundAC() {
211 daniel-mar 459
        // "Bug A": Sometimes, the design is completely destroyed after reloading the page. It does not help when glayout.resizeAll()
137 daniel-mar 460
        //          is called at the beginning (e.g. during the ready function), and it does not help if we wait 500ms.
461
        //          So we do it all the time. It has probably something to do with slow loading times, since the error
462
        //          does only appear when the page is "blank" for a short while while it is loading.
463
        glayout.resizeAll();
287 daniel-mar 464
 
465
        // "Bug C": With Firefox (And sometimes with Chrome), there is a gap between the content-window (including scroll bars)
466
        //          and the right corner of the screen. Removing the explicit width solves this problem.
561 daniel-mar 467
        $("#content_window")[0].style.removeProperty("width");
643 daniel-mar 468
 
469
        setTimeout(glayoutWorkaroundAC, 100);
137 daniel-mar 470
}
471
 
472
function glayoutWorkaroundB() {
473
        // "Bug B": Sometimes, after reload, weird space between oidtree and content window, because oidtree has size of 438px
818 daniel-mar 474
        $("#oidtree")[0].style.width = oidplus_menu_width + "px";
137 daniel-mar 475
}
476
 
120 daniel-mar 477
function mobileNavClose() {
478
        if ($("#system_title_menu").is(":hidden")) {
479
                return;
480
        }
481
 
482
        $("#oidtree").slideUp("medium").promise().done(function() {
483
                $("#oidtree").addClass("ui-layout-west");
484
                $("#oidtree").show();
185 daniel-mar 485
//              $("#gotobox").hide();
355 daniel-mar 486
//              $("#languageBox").hide();
185 daniel-mar 487
                $("#gotobox").addClass("mobilehidden");
355 daniel-mar 488
                $("#languageBox").addClass("mobilehidden");
120 daniel-mar 489
        });
490
        $("#system_title_menu").removeClass("active");
95 daniel-mar 491
}
492
 
120 daniel-mar 493
function mobileNavOpen() {
494
        $("#oidtree").hide();
495
        $("#oidtree").removeClass("ui-layout-west");
496
        $("#oidtree").slideDown("medium");
185 daniel-mar 497
//      $("#gotobox").show();
355 daniel-mar 498
//      $("#languageBox").show();
185 daniel-mar 499
        $("#gotobox").removeClass("mobilehidden");
355 daniel-mar 500
        $("#languageBox").removeClass("mobilehidden");
120 daniel-mar 501
        $("#system_title_menu").addClass("active");
502
}
503
 
105 daniel-mar 504
function mobileNavButtonClick(sender) {
120 daniel-mar 505
        if ($("#oidtree").hasClass("ui-layout-west")) {
506
                mobileNavOpen();
103 daniel-mar 507
        } else {
120 daniel-mar 508
                mobileNavClose();
103 daniel-mar 509
        }
510
}
105 daniel-mar 511
 
512
function mobileNavButtonHover(sender) {
513
        sender.classList.toggle("hover");
514
}
183 daniel-mar 515
 
516
function gotoButtonClicked() {
704 daniel-mar 517
        openOidInPanel($("#gotoedit").val(), true);
183 daniel-mar 518
}
199 daniel-mar 519
 
219 daniel-mar 520
function jumpToAnchor(anchor) {
521
        window.location.href = "#" + anchor;
522
}
523
 
355 daniel-mar 524
function getCookie(cname) {
525
        // Source: https://www.w3schools.com/js/js_cookies.asp
526
        var name = cname + "=";
527
        var decodedCookie = decodeURIComponent(document.cookie);
528
        var ca = decodedCookie.split(';');
529
        for(var i = 0; i <ca.length; i++) {
530
                var c = ca[i];
531
                while (c.charAt(0) == ' ') {
532
                        c = c.substring(1);
533
                }
534
                if (c.indexOf(name) == 0) {
535
                        return c.substring(name.length, c.length);
536
                }
537
        }
538
        return undefined;
539
}
540
 
356 daniel-mar 541
function setCookie(cname, cvalue, exdays, path) {
542
        var d = new Date();
543
        d.setTime(d.getTime() + (exdays*24*60*60*1000));
544
        var expires = exdays == 0 ? "" : "; expires="+d.toUTCString();
561 daniel-mar 545
        document.cookie = cname + "=" + cvalue + expires + ";path=" + path + ";SameSite=" + samesite_policy;
356 daniel-mar 546
}
547
 
355 daniel-mar 548
function setLanguage(lngid) {
356 daniel-mar 549
        setCookie('LANGUAGE', lngid, 0/*Until browser closes*/, location.pathname);
550
 
551
        $(".lng_flag").each(function(){
552
                $(this).addClass("picture_ghost");
355 daniel-mar 553
        });
675 daniel-mar 554
        $("#lng_flag_"+$.escapeSelector(lngid)).removeClass("picture_ghost");
356 daniel-mar 555
 
556
        if (isInternetExplorer()) {
557
                // Internet Explorer has problems with sending new cookies to new AJAX requests, so we reload the page completely
558
                window.location.reload();
559
        } else {
561 daniel-mar 560
                // TODO: Small detail: The "Go" button also needs to be re-translated
360 daniel-mar 561
                reloadContent();
356 daniel-mar 562
                mobileNavClose();
563
        }
355 daniel-mar 564
}
565
 
566
function getCurrentLang() {
360 daniel-mar 567
        // 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 568
        var lang = getCookie('LANGUAGE'); // do not translate
569
        return (typeof lang != 'undefined') ? lang : DEFAULT_LANGUAGE; // do not translate
355 daniel-mar 570
}
571
 
360 daniel-mar 572
function _L() {
573
        var args = Array.prototype.slice.call(arguments);
506 daniel-mar 574
        var str = args.shift().trim();
360 daniel-mar 575
 
576
        var tmp = "";
699 daniel-mar 577
        if (typeof language_messages[getCurrentLang()] == 'undefined') { // do not translate
360 daniel-mar 578
                tmp = str;
579
        } else {
580
                var msg = language_messages[getCurrentLang()][str];
699 daniel-mar 581
                if (typeof msg != 'undefined') { // do not translate
360 daniel-mar 582
                        tmp = msg;
583
                } else {
584
                        tmp = str;
585
                }
586
        }
587
 
588
        tmp = tmp.replace('###', language_tblprefix);
589
 
590
        var n = 1;
591
        while (args.length > 0) {
592
                var val = args.shift();
593
                tmp = tmp.replace("%"+n, val);
594
                n++;
595
        }
596
 
370 daniel-mar 597
        tmp = tmp.replace("%%", "%");
598
 
360 daniel-mar 599
        return tmp;
355 daniel-mar 600
}
532 daniel-mar 601
 
602
function show_waiting_anim() {
603
        $("#loading").show();
604
}
605
 
606
function hide_waiting_anim() {
607
        $("#loading").hide();
608
}
544 daniel-mar 609
 
610
/* Mini-framework to abort all AJAX requests if a new request is made */
611
 
612
$.xhrPool = [];
613
$.xhrPool.add = function(jqXHR) {
614
        $.xhrPool.push(jqXHR);
615
}
616
$.xhrPool.remove = function(jqXHR) {
617
        var index = $.xhrPool.indexOf(jqXHR);
618
        if (index > -1) {
619
                $.xhrPool.splice(index, 1);
620
        }
621
};
622
$.xhrPool.abortAll = function() {
623
        var calls = Array.from($.xhrPool);
624
        $.each(calls, function(key, value) {
625
                value.abort();
626
        });
627
}