Subversion Repositories oidplus

Rev

Rev 356 | Go to most recent revision | View as "text/javascript" | Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * OIDplus 2.0
  3.  * Copyright 2019 Daniel Marschall, ViaThinkSoft
  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.  
  18. /*jshint esversion: 6 */
  19.  
  20. // $('#html').jstree();
  21.  
  22. var current_node = "";
  23. var popstate_running = false;
  24. var externalWaiting = 0;
  25. var DEFAULT_LANGUAGE = "enus";
  26. // language_messages will be set by oidplus.min.js.php
  27. // language_tblprefix will be set by oidplus.min.js.php
  28.  
  29. function oidplus_loadScript(src) {
  30.         externalWaiting++;
  31.         var script = document.createElement('script');
  32.         script.onload = function () {
  33.                 externalWaiting--;
  34.         };
  35.         script.src = src;
  36.         script.rel = "preload";
  37.         document.head.appendChild(script);
  38. }
  39.  
  40. function isInternetExplorer() {
  41.         var ua = window.navigator.userAgent;
  42.         return ((ua.indexOf("MSIE ") > 0) || (ua.indexOf("Trident/") > 0));
  43. }
  44.  
  45. function oidplus_external_polyfill() {
  46.         // Disable this code by adding following line to userdata/baseconfig/config.inc.php
  47.         // define('RECAPTCHA_ENABLED', false);
  48.         if (isInternetExplorer()) {
  49.                 // Compatibility with Internet Explorer
  50.                 oidplus_loadScript('https://polyfill.io/v3/polyfill.min.js?features=fetch%2CURL');
  51.         }
  52. }
  53.  
  54. function oidplus_external_recaptcha() {
  55.         // Disable this code by adding following lines to userdata/baseconfig/config.inc.php
  56.         // define('DISABLE_MSIE_COMPAT', true);
  57.         oidplus_loadScript('https://www.google.com/recaptcha/api.js');
  58. }
  59.  
  60. String.prototype.explode = function (separator, limit) {
  61.         // https://stackoverflow.com/questions/4514323/javascript-equivalent-to-php-explode
  62.         const array = this.split(separator);
  63.         if (limit !== undefined && array.length >= limit) {
  64.                 array.push(array.splice(limit - 1).join(separator));
  65.         }
  66.         return array;
  67. };
  68.  
  69. String.prototype.htmlentities = function () {
  70.         return this.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  71. };
  72.  
  73. String.prototype.html_entity_decode = function () {
  74.         return $('<textarea />').html(this).text();
  75. };
  76.  
  77. function getMeta(metaName) {
  78.         const metas = document.getElementsByTagName('meta');
  79.  
  80.         for (let i = 0; i < metas.length; i++) {
  81.                 if (metas[i].getAttribute('name') === metaName) {
  82.                         return metas[i].getAttribute('content');
  83.                 }
  84.         }
  85.  
  86.         return '';
  87. }
  88.  
  89. function getOidPlusSystemTitle() {
  90.         return getMeta('OIDplus-SystemTitle'); // do not translate
  91. }
  92.  
  93. function combine_systemtitle_and_pagetitle(systemtitle, pagetitle) {
  94.         // Please also change the function in index.php
  95.         if (systemtitle == pagetitle) {
  96.                 return systemtitle;
  97.         } else {
  98.                 return pagetitle + ' - ' + systemtitle;
  99.         }
  100. }
  101.  
  102. function getTreeLoadURL() {
  103.         var url = new URL(window.location.href);
  104.         var goto = url.searchParams.get("goto");
  105.         return (goto != null) ? "ajax.php?action=tree_load&goto="+encodeURIComponent(goto)
  106.                               : "ajax.php?action=tree_load";
  107. }
  108.  
  109. function reloadContent() {
  110.         // window.location.href = "?goto="+encodeURIComponent(current_node);
  111.         openOidInPanel(current_node, false);
  112.         $('#oidtree').jstree("refresh");
  113. }
  114.  
  115. function x_rec(x_data, i) {
  116.         $('#oidtree').jstree('open_node', x_data[i], function(e, data) {
  117.                 if (i+1 < x_data.length) {
  118.                         x_rec(x_data, i+1);
  119.                 } else {
  120.                         popstate_running = true; // don't call openOidInPanel again
  121.                         try {
  122.                                 $('#oidtree').jstree('select_node', x_data[i]);
  123.                         } catch (err) {
  124.                                 popstate_running = false;
  125.                         } finally {
  126.                                 popstate_running = false;
  127.                         }
  128.                 }
  129.         });
  130. }
  131.  
  132. function openOidInPanel(id, reselect/*=false*/, anchor/*=''*/) {
  133.         reselect = (typeof reselect === 'undefined') ? false : reselect;
  134.         anchor = (typeof anchor === 'undefined') ? '' : anchor;
  135.  
  136.         if (reselect) {
  137.                 $('#oidtree').jstree('deselect_all');
  138.  
  139.                 popstate_running = true; // don't call openOidInPanel during tree selection
  140.                 try {
  141.                         // If the node is already loaded in the tree, select it
  142.                         if (!$('#oidtree').jstree('select_node', id)) {
  143.                                 // If the node is not loaded, then we try to search it.
  144.                                 // If it can be found, then open all parent nodes and select the node
  145.                                 $.ajax({
  146.                                         url:"ajax.php",
  147.                                         method:"POST",
  148.                                         data:{
  149.                                                 action:"tree_search",
  150.                                                 search:id
  151.                                         },
  152.                                         error:function(jqXHR, textStatus, errorThrown) {
  153.                                                 console.error(_L("Error: %1",errorThrown));
  154.                                         },
  155.                                         success:function(data) {
  156.                                                 if ("error" in data) {
  157.                                                         console.error(data);
  158.                                                 } else if ((data instanceof Array) && (data.length > 0)) {
  159.                                                         x_rec(data, 0);
  160.                                                 } else {
  161.                                                         console.error(data);
  162.                                                 }
  163.                                         }
  164.                                 });
  165.                         }
  166.                 } catch (err) {
  167.                         popstate_running = false;
  168.                 } finally {
  169.                         popstate_running = false;
  170.                 }
  171.         }
  172.  
  173.         // This loads the actual content
  174.  
  175.         document.title = "";
  176.         $('#real_title').html("&nbsp;");
  177.         $('#real_content').html(_L("Loading..."));
  178.         $('#static_link').attr("href", "index.php?goto="+encodeURIComponent(id));
  179.         $("#gotoedit").val(id);
  180.  
  181.         // Normal opening of a description
  182.         fetch('ajax.php?action=get_description&id='+encodeURIComponent(id))
  183.         .then(function(response) {
  184.                 response.json()
  185.                 .then(function(data) {
  186.                         if ("error" in data) {
  187.                                 alert(_L("Failed to load content: %1",data.error));
  188.                                 console.error(data.error);
  189.                                 return;
  190.                         }
  191.  
  192.                         data.id = id;
  193.  
  194.                         document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  195.                         var state = {
  196.                                 "node_id":id,
  197.                                 "titleHTML":(data.icon ? '<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' : '') + data.title.htmlentities(),
  198.                                 "textHTML":data.text,
  199.                                 "staticlinkHREF":"index.php?goto="+encodeURIComponent(id),
  200.                         };
  201.                         if (current_node != id) {
  202.                                 window.history.pushState(state, data.title, "?goto="+encodeURIComponent(id));
  203.                         } else {
  204.                                 window.history.replaceState(state, data.title, "?goto="+encodeURIComponent(id));
  205.                         }
  206.  
  207.                         if (data.icon) {
  208.                                 $('#real_title').html('<img src="'+data.icon+'" width="48" height="48" alt="'+data.title.htmlentities()+'"> ' + data.title.htmlentities());
  209.                         } else {
  210.                                 $('#real_title').html(data.title.htmlentities());
  211.                         }
  212.                         $('#real_content').html(data.text);
  213.                         document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.title);
  214.                         current_node = id;
  215.  
  216.                         if (anchor != '') {
  217.                                 jumpToAnchor(anchor);
  218.                         }
  219.                 })
  220.                 .catch(function(error) {
  221.                         alert(_L("Failed to load content: %1",error));
  222.                         console.error(error);
  223.                 });
  224.         })
  225.         .catch(function(error) {
  226.                 alert(_L("Failed to load content: %1",error));
  227.                 console.error(error);
  228.         });
  229. }
  230.  
  231. // This function opens the "parentID" node, and then selects the "childID" node (which should be beneath the parent node)
  232. function openAndSelectNode(childID, parentID) {
  233.         if ($('#oidtree').jstree(true).get_node(parentID)) {
  234.                 $('#oidtree').jstree('open_node', parentID, function(e, data) { // open parent node
  235.                         if ($('#oidtree').jstree(true).get_node(childID)) { // is the child there?
  236.                                 $('#oidtree').jstree('deselect_all').jstree('select_node', childID); // select it
  237.                         } else {
  238.                                 // This can happen if the content page contains brand new items which are not in the treeview yet
  239.                                 $("#gotoedit").val(childID);
  240.                                 window.location.href = "?goto="+encodeURIComponent(childID);
  241.                         }
  242.                 }, true);
  243.         } else {
  244.                 // This should usually not happen
  245.                 $("#gotoedit").val(childID);
  246.                 window.location.href = "?goto="+encodeURIComponent(childID);
  247.         }
  248. }
  249.  
  250. $(window).on("popstate", function(e) {
  251.         popstate_running = true;
  252.         try {
  253.                 var data = e.originalEvent.state;
  254.  
  255.                 current_node = data.node_id;
  256.                 $("#gotoedit").val(current_node);
  257.                 $('#oidtree').jstree('deselect_all').jstree('select_node', data.node_id);
  258.                 $('#real_title').html(data.titleHTML);
  259.                 $('#real_content').html(data.textHTML);
  260.                 $('#static_link').attr("href", data.staticlinkHREF);
  261.                 document.title = combine_systemtitle_and_pagetitle(getOidPlusSystemTitle(), data.titleHTML.html_entity_decode());
  262.         } catch (err) {
  263.                 popstate_running = false;
  264.         } finally {
  265.                 popstate_running = false;
  266.         }
  267. });
  268.  
  269. $(document).ready(function () {
  270.         initBeforeExternals();
  271. });
  272.  
  273. function initBeforeExternals() {
  274.         if (externalWaiting > 0) {
  275.                 setTimeout(initBeforeExternals, 100);
  276.         } else {
  277.                 initAfterExternals();
  278.         }
  279. }
  280.  
  281. function initAfterExternals() {
  282.  
  283.         // --- JsTree
  284.  
  285.         $('#oidtree')
  286.         .jstree({
  287.                 plugins: ['massload','search','conditionalselect'],
  288.                 'core' : {
  289.                         'data' : {
  290.                                 "url" : getTreeLoadURL(),
  291.                                 "data" : function (node) {
  292.                                         return { "id" : node.id };
  293.                                 }
  294.                         },
  295.                         "multiple": false
  296.                 },
  297.                 'conditionalselect' : function (node) {
  298.                         if (node.original.conditionalselect !== undefined) {
  299.                                 return eval(node.original.conditionalselect);
  300.                         } else {
  301.                                 return true; // allow select
  302.                         }
  303.                 },
  304.         })
  305.         .on('ready.jstree', function (e, data) {
  306.                 var url = new URL(window.location.href);
  307.                 var goto = url.searchParams.get("goto");
  308.                 if (goto == null) goto = "oidplus:system"; // the page was not called with ?goto=...
  309.                 $("#gotoedit").val(goto);
  310.  
  311.                 // 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)
  312.                 // But then we need to set the history state manually
  313.                 current_node = goto;
  314.                 window.history.replaceState({
  315.                         "node_id":goto,
  316.                         "titleHTML":$('#real_title').html(),
  317.                         "textHTML":$('#real_content').html(),
  318.                         "staticlinkHREF":"index.php?goto="+encodeURIComponent(goto),
  319.                 }, $('#real_title').html(), "?goto="+encodeURIComponent(goto));
  320.  
  321.                 if (goto != null) data.instance.select_node([goto]);
  322.  
  323.                 setTimeout(glayoutWorkaroundA, 100);
  324.                 setTimeout(glayoutWorkaroundB, 100);
  325.         })
  326.         .on('select_node.jstree', function (node, selected, event) {
  327.                 mobileNavClose();
  328.  
  329.                 var id = selected.node.id;
  330.                 if ((!popstate_running) && (current_node != id)) {
  331.                         openOidInPanel(id, false);
  332.                 }
  333.         });
  334.  
  335.         // --- Layout
  336.  
  337.         document.getElementById('system_title_menu').style.display = "block";
  338.  
  339.         var tmpObjectTree = _L("OBJECT TREE").replace(/(.{1})/g,"$1<br>");
  340.         tmpObjectTree = tmpObjectTree.substring(0, tmpObjectTree.length-"<br>".length);
  341.  
  342.         $('#oidtree').addClass('ui-layout-west');
  343.         $('#content_window').addClass('ui-layout-center');
  344.         $('#system_title_bar').addClass('ui-layout-north');
  345.         glayout = $('#frames').layout({
  346.                 north__size:                  40,
  347.                 north__slidable:              false,
  348.                 north__closable:              false,
  349.                 north__resizable:             false,
  350.                 west__size:                   450,
  351.                 west__spacing_closed:         20,
  352.                 west__togglerLength_closed:   230,
  353.                 west__togglerAlign_closed:    "top",
  354.                 west__togglerContent_closed:  tmpObjectTree,
  355.                 west__togglerTip_closed:      _L("Open & Pin Menu"),
  356.                 west__sliderTip:              _L("Slide Open Menu"),
  357.                 west__slideTrigger_open:      "mouseover",
  358.                 center__maskContents:         true // IMPORTANT - enable iframe masking
  359.         });
  360.  
  361.         $("#gotobox").addClass("mobilehidden");
  362.         $("#languageBox").addClass("mobilehidden");
  363.         document.getElementById('gotobox').style.display = "block";
  364.         $('#gotoedit').keypress(function(event) {
  365.                 var keycode = (event.keyCode ? event.keyCode : event.which);
  366.                 if (keycode == '13') {
  367.                         gotoButtonClicked();
  368.                 }
  369.         });
  370. }
  371.  
  372. function glayoutWorkaroundA() {
  373.         // "Bug A": Sometimes, the design is completely destroyed after reloading the page. It does not help when glayout.resizeAll()
  374.         //          is called at the beginning (e.g. during the ready function), and it does not help if we wait 500ms.
  375.         //          So we do it all the time. It has probably something to do with slow loading times, since the error
  376.         //          does only appear when the page is "blank" for a short while while it is loading.
  377.         glayout.resizeAll();
  378.         setTimeout(glayoutWorkaroundA, 100);
  379.  
  380.         // "Bug C": With Firefox (And sometimes with Chrome), there is a gap between the content-window (including scroll bars)
  381.         //          and the right corner of the screen. Removing the explicit width solves this problem.
  382.         document.getElementById("content_window").style.removeProperty("width");
  383. }
  384.  
  385. function glayoutWorkaroundB() {
  386.         // "Bug B": Sometimes, after reload, weird space between oidtree and content window, because oidtree has size of 438px
  387.         document.getElementById("oidtree").style.width = "450px";
  388. }
  389.  
  390. function mobileNavClose() {
  391.         if ($("#system_title_menu").is(":hidden")) {
  392.                 return;
  393.         }
  394.  
  395.         $("#oidtree").slideUp("medium").promise().done(function() {
  396.                 $("#oidtree").addClass("ui-layout-west");
  397.                 $("#oidtree").show();
  398. //              $("#gotobox").hide();
  399. //              $("#languageBox").hide();
  400.                 $("#gotobox").addClass("mobilehidden");
  401.                 $("#languageBox").addClass("mobilehidden");
  402.         });
  403.         $("#system_title_menu").removeClass("active");
  404. }
  405.  
  406. function mobileNavOpen() {
  407.         $("#oidtree").hide();
  408.         $("#oidtree").removeClass("ui-layout-west");
  409.         $("#oidtree").slideDown("medium");
  410. //      $("#gotobox").show();
  411. //      $("#languageBox").show();
  412.         $("#gotobox").removeClass("mobilehidden");
  413.         $("#languageBox").removeClass("mobilehidden");
  414.         $("#system_title_menu").addClass("active");
  415. }
  416.  
  417. function mobileNavButtonClick(sender) {
  418.         if ($("#oidtree").hasClass("ui-layout-west")) {
  419.                 mobileNavOpen();
  420.         } else {
  421.                 mobileNavClose();
  422.         }
  423. }
  424.  
  425. function mobileNavButtonHover(sender) {
  426.         sender.classList.toggle("hover");
  427. }
  428.  
  429. function gotoButtonClicked() {
  430.         openOidInPanel($("#gotoedit").val(), 1);
  431. }
  432.  
  433. function jumpToAnchor(anchor) {
  434.         window.location.href = "#" + anchor;
  435. }
  436.  
  437. function getCookie(cname) {
  438.         // Source: https://www.w3schools.com/js/js_cookies.asp
  439.         var name = cname + "=";
  440.         var decodedCookie = decodeURIComponent(document.cookie);
  441.         var ca = decodedCookie.split(';');
  442.         for(var i = 0; i <ca.length; i++) {
  443.                 var c = ca[i];
  444.                 while (c.charAt(0) == ' ') {
  445.                         c = c.substring(1);
  446.                 }
  447.                 if (c.indexOf(name) == 0) {
  448.                         return c.substring(name.length, c.length);
  449.                 }
  450.         }
  451.         return undefined;
  452. }
  453.  
  454. function setCookie(cname, cvalue, exdays, path) {
  455.         var d = new Date();
  456.         d.setTime(d.getTime() + (exdays*24*60*60*1000));
  457.         var expires = exdays == 0 ? "" : "; expires="+d.toUTCString();
  458.         document.cookie = cname + "=" + cvalue + expires + ";path=" + path;
  459. }
  460.  
  461. function setLanguage(lngid) {
  462.         setCookie('LANGUAGE', lngid, 0/*Until browser closes*/, location.pathname);
  463.  
  464.         $(".lng_flag").each(function(){
  465.                 $(this).addClass("picture_ghost");
  466.         });
  467.         $("#lng_flag_"+lngid).removeClass("picture_ghost");
  468.  
  469.         if (isInternetExplorer()) {
  470.                 // Internet Explorer has problems with sending new cookies to new AJAX requests, so we reload the page completely
  471.                 window.location.reload();
  472.         } else {
  473.                 reloadContent();
  474.                 mobileNavClose();
  475.         }
  476. }
  477.  
  478. function getCurrentLang() {
  479.         // Note: If the argument "?lang=" is used, PHP will automatically set a Cookie, so it is OK when we only check for the cookie
  480.         var lang = getCookie('LANGUAGE');
  481.         return (typeof lang != "undefined") ? lang : DEFAULT_LANGUAGE;
  482. }
  483.  
  484. function _L() {
  485.         var args = Array.prototype.slice.call(arguments);
  486.         var str = args.shift();
  487.  
  488.         var tmp = "";
  489.         if (typeof language_messages[getCurrentLang()] == "undefined") {
  490.                 tmp = str;
  491.         } else {
  492.                 var msg = language_messages[getCurrentLang()][str];
  493.                 if (typeof msg != "undefined") {
  494.                         tmp = msg;
  495.                 } else {
  496.                         tmp = str;
  497.                 }
  498.         }
  499.  
  500.         tmp = tmp.replace('###', language_tblprefix);
  501.  
  502.         var n = 1;
  503.         while (args.length > 0) {
  504.                 var val = args.shift();
  505.                 tmp = tmp.replace("%"+n, val);
  506.                 n++;
  507.         }
  508.  
  509.         return tmp;
  510. }
  511.