Subversion Repositories oidplus

Rev

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');//"
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("&nbsp;");
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
}