From 16868f259d2b461dff816a86b0fddac3737c9cde Mon Sep 17 00:00:00 2001 From: Luuk Verhoeven Date: Thu, 10 Oct 2024 07:33:30 +0200 Subject: [PATCH] Improve javascript --- amd/build/commander.min.js | 12 +- amd/build/commander.min.js.map | 2 +- amd/build/settings.min.js | 18 +- amd/build/settings.min.js.map | 2 +- amd/src/commander.js | 866 ++++++++++++++++----------------- amd/src/settings.js | 91 ++-- 6 files changed, 473 insertions(+), 518 deletions(-) diff --git a/amd/build/commander.min.js b/amd/build/commander.min.js index 8c053d1..2a9f4fe 100644 --- a/amd/build/commander.min.js +++ b/amd/build/commander.min.js @@ -1,13 +1,3 @@ -/** - * JS to the popup and interact with it. - * - * - * Tested in Moodle 3.8 - * - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright 2018 MFreak.nl - * @author Luuk Verhoeven - **/ -define("local_commander/commander",["jquery","core/notification","core/log"],(function($,notification,Log){$.fn.scrollTo=function(elem,speed){return $(this).stop().animate({scrollTop:$(this).scrollTop()-$(this).offset().top+$(elem).offset().top-10},null==speed?1e3:speed),this};var commanderAppOptions={courseid:"",keys:[]};const commanderApp={$mainModal:!1,$mainModalBackLayer:!1,$mainModalCommand:!1,$liSet:!1,isShow:!1,json:"",render:function(){var timer=0;Log.debug("render UI"),$("body").append('

'+M.util.get_string("js:header","local_commander")+'

'),commanderApp.$mainModal=$("#local_commander_modal"),commanderApp.$mainModalBackLayer=$("#local_commander_back_layer"),commanderApp.$mainModalCommand=$("#local_commander_command"),commanderApp.setHeight(),commanderApp.$mainModalBackLayer.on("click",(function(){commanderApp.hide()})),commanderApp.$mainModalCommand.on("keydown",(function(e){var keyboardCode=e.keyCode||e.which;switch(Log.debug("Code pressed:"+keyboardCode),keyboardCode){case 27:case 13:case 38:case 40:return}Log.debug("Searching"),clearTimeout(timer),timer=setTimeout((function(){commanderApp.search(commanderApp.$mainModalCommand.val())}),100)})),""===commanderApp.json&&commanderApp.loadMenu()},start:function(){commanderApp.$mainModal=$("#local_commander_modal"),$(window).on("keydown",(function(e){var keyboardCode=e.keyCode||e.which;if(Log.debug("Code pressed:",keyboardCode),Log.debug("Trigger keys:",commanderAppOptions.keys),Log.debug("Commander is visible:",commanderApp.isShow),commanderApp.isShow)switch(keyboardCode){case 27:commanderApp.hide();break;case 13:e.preventDefault(),commanderApp.goToCommand();break;case 38:e.preventDefault(),commanderApp.arrowUp();break;case 40:e.preventDefault(),commanderApp.arrowDown()}else if(-1!==commanderAppOptions.keys.indexOf(keyboardCode.toString())){if(Log.debug("Commander keyboard key triggered"),"INPUT"==e.target.tagName||"SELECT"==e.target.tagName||"TEXTAREA"==e.target.tagName||e.target.isContentEditable)return void Log.debug("Hide when we are in an editable element");e.preventDefault(),0==commanderApp.$mainModal.length&&commanderApp.render(),Log.debug("Open commander."),commanderApp.isShow?commanderApp.hide():commanderApp.show()}}))},highlightWord:function(node,word){if(3==node.nodeType){var pos=node.data.toUpperCase().indexOf(word);if(pos>=0){var spannode=document.createElement("span");spannode.className="highlight",spannode.style.backgroundColor="#f4bd21";var middlebit=node.splitText(pos),middleclone=middlebit.cloneNode(!0);spannode.appendChild(middleclone),middlebit.parentNode.replaceChild(spannode,middlebit)}}else if(1==node.nodeType&&node.childNodes)for(var i=0;i0&&(Log.debug("Has course param."),html+=commanderApp.renderMenuItems(commanderApp.json.courseadmin,1)),html+=commanderApp.renderMenuItems(commanderApp.json.admin,1),html+="",commanderApp.$mainModal.find(".local_commander-body").append(html),commanderApp.$liSet=$(".local_commander-body ul")},renderMenuItems:function(child,depth,parentName){var html="";return child.name?(parentName?parentName+=" → ":parentName="",html+="
  • ",child.name&&(html+=''+parentName+child.name+""),child.haschildren&&$.each(child.children,(function(i,el){html+=commanderApp.renderMenuItems(el,depth+1,parentName+child.name)})),html+="
  • "):html},show:function(){commanderApp.$mainModal.show(),commanderApp.$mainModalBackLayer.show(),commanderApp.isShow=!0,commanderApp.$mainModalCommand.focus()},hide:function(){commanderApp.$mainModal.hide(),commanderApp.$mainModalBackLayer.hide(),commanderApp.isShow=!1},setHeight:function(){var height=Math.round($(window).height()/2);commanderApp.$mainModal.height(height),$(".local_commander-body div").height(height-100)},removeHighlight:function(node){$(node).html($(node).text())}};return{init:function(params){!function(options){var key,vartype;for(key in commanderAppOptions)commanderAppOptions.hasOwnProperty(key)&&options.hasOwnProperty(key)&&(vartype=typeof commanderAppOptions[key],commanderAppOptions[key]="boolean"===vartype?Boolean(options[key]):"number"===vartype?Number(options[key]):"string"===vartype?String(options[key]):options[key])}(params),$(document).ready((function(){Log.debug("ready() - local commander v4.4"),Log.debug(commanderAppOptions),commanderApp.start()}))}}})); +define("local_commander/commander",["exports","core/notification","core/log"],(function(_exports,_notification,_log){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_log=_interopRequireDefault(_log);const ESCAPE=27,ENTER=13,ARROWUP=38,ARROWDOWN=40,commanderAppOptions={courseid:"",keys:[]};const commanderApp={mainModal:null,mainModalBackLayer:null,mainModalCommand:null,liSet:null,isShow:!1,json:"",render(){let timer=0;_log.default.debug("Rendering UI");const modalHtml='\n
    \n
    \n

    '.concat(M.util.get_string("js:header","local_commander"),'

    \n
    \n
    \n
      \n
      \n \n
      \n
      \n ');document.body.insertAdjacentHTML("beforeend",modalHtml),this.mainModal=document.getElementById("local_commander_modal"),this.mainModalBackLayer=document.getElementById("local_commander_back_layer"),this.mainModalCommand=document.getElementById("local_commander_command"),this.setHeight(),this.mainModalBackLayer.addEventListener("click",(()=>{this.hide()})),this.mainModalCommand.addEventListener("keydown",(e=>{const keyboardCode=e.keyCode||e.which;_log.default.debug("Code pressed: ".concat(keyboardCode)),[ESCAPE,ENTER,ARROWUP,ARROWDOWN].includes(keyboardCode)||(_log.default.debug("Searching"),clearTimeout(timer),timer=setTimeout((()=>{this.search(this.mainModalCommand.value)}),100))})),""===this.json&&this.loadMenu()},start(){window.addEventListener("keydown",(e=>{const keyboardCode=e.keyCode||e.which;if(_log.default.debug("Code pressed: ".concat(keyboardCode)),_log.default.debug("Trigger keys: ".concat(commanderAppOptions.keys)),_log.default.debug("Commander is visible: ".concat(this.isShow)),this.isShow)switch(keyboardCode){case ESCAPE:this.hide();break;case ENTER:e.preventDefault(),this.goToCommand();break;case ARROWUP:e.preventDefault(),this.arrowUp();break;case ARROWDOWN:e.preventDefault(),this.arrowDown()}else if(commanderAppOptions.keys.includes(keyboardCode.toString())){_log.default.debug("Commander keyboard key triggered");const target=e.target,tagName=target.tagName.toUpperCase();if(["INPUT","SELECT","TEXTAREA"].includes(tagName)||target.isContentEditable)return void _log.default.debug("Ignoring keypress in editable element");e.preventDefault(),this.mainModal||this.render(),_log.default.debug("Opening commander"),this.isShow?this.hide():this.show()}}))},highlightWord(node,word){if(node.nodeType===Node.TEXT_NODE){const pos=node.data.toUpperCase().indexOf(word);if(pos>=0){const spannode=document.createElement("span");spannode.className="highlight";const middlebit=node.splitText(pos);middlebit.splitText(word.length);const middleclone=middlebit.cloneNode(!0);spannode.appendChild(middleclone),middlebit.parentNode.replaceChild(spannode,middlebit)}}else node.nodeType===Node.ELEMENT_NODE&&node.childNodes&&!["SCRIPT","STYLE"].includes(node.tagName)&&node.childNodes.forEach((child=>{this.highlightWord(child,word)}))},arrowUp(){_log.default.debug("Navigating up");const activeItem=this.mainModal.querySelector("ul li.active");let prevItem=null;if(activeItem)for(activeItem.classList.remove("active"),prevItem=activeItem.previousElementSibling;prevItem&&"none"===prevItem.style.display;)prevItem=prevItem.previousElementSibling;prevItem?prevItem.classList.add("active"):activeItem&&activeItem.classList.add("active"),this.scrollToActiveItem()},arrowDown(){_log.default.debug("Navigating down");const activeItem=this.mainModal.querySelector("ul li.active");let nextItem=null;if(activeItem)for(activeItem.classList.remove("active"),nextItem=activeItem.nextElementSibling;nextItem&&"none"===nextItem.style.display;)nextItem=nextItem.nextElementSibling;if(nextItem)nextItem.classList.add("active");else{const lastVisibleItem=Array.from(this.mainModal.querySelectorAll("ul li")).reverse().find((item=>"none"!==item.style.display));lastVisibleItem&&lastVisibleItem.classList.add("active")}this.scrollToActiveItem()},scrollToActiveItem(){const container=this.mainModal.querySelector(".local_commander-body div"),activeItem=this.mainModal.querySelector("li.active");activeItem&&container&&(container.scrollTop=activeItem.offsetTop-container.offsetTop-10)},goToCommand(){_log.default.debug("Executing command");const activeLink=this.mainModal.querySelector("ul li.active a");if(activeLink){const link=activeLink.getAttribute("href");"#"!==link&&(window.location.href=link)}},loadMenu(){fetch("".concat(M.cfg.wwwroot,"/local/commander/ajax.php?courseid=").concat(commanderAppOptions.courseid),{method:"GET",credentials:"same-origin"}).then((response=>response.json())).then((data=>{_log.default.debug(data),this.json=data,this.setMenu(),this.setHeight()})).catch((()=>{_notification.default.alert("js:error_parsing","local_commander")}))},search(word){const listItems=this.mainModal.querySelectorAll(".local_commander-body ul li");listItems.forEach((li=>{li.style.display="",li.classList.remove("active")}));if(this.mainModal.querySelectorAll(".highlight").forEach((span=>{this.removeHighlight(span.parentNode)})),""!==word){const wordUpper=word.toUpperCase();let firstHighlighted=null;this.liSet.forEach((li=>{this.highlightWord(li,wordUpper),!firstHighlighted&&li.querySelector(".highlight")&&(firstHighlighted=li)})),firstHighlighted&&firstHighlighted.classList.add("active"),listItems.forEach((li=>{li.querySelector(".highlight")||(li.style.display="none")}))}},setMenu(){_log.default.debug("Setting up menu");let html="";commanderAppOptions.courseid>0&&(_log.default.debug("Including course administration menu"),html+=this.renderMenuItems(this.json.courseadmin,"")),html+=this.renderMenuItems(this.json.admin,"");this.mainModal.querySelector(".local_commander-body ul").innerHTML=html,this.liSet=this.mainModal.querySelectorAll(".local_commander-body ul li")},renderMenuItems(item,parentName){if(!item.name)return"";let html="";const fullName=parentName?"".concat(parentName," → ").concat(item.name):item.name;return html+='
    • ').concat(fullName,"
    • "),item.haschildren&&item.children.forEach((child=>{html+=this.renderMenuItems(child,fullName)})),html},show(){this.mainModal.style.display="block",this.mainModalBackLayer.style.display="block",this.isShow=!0,this.mainModalCommand.focus()},hide(){this.mainModal.style.display="none",this.mainModalBackLayer.style.display="none",this.isShow=!1},setHeight(){const height=Math.round(window.innerHeight/2);this.mainModal.style.height="".concat(height,"px");const bodyDiv=this.mainModal.querySelector(".local_commander-body div");bodyDiv&&(bodyDiv.style.height="".concat(height-100,"px"))},removeHighlight(node){node.innerHTML=node.textContent}};var _default={init:function(params){var options;options=params,Object.keys(commanderAppOptions).forEach((key=>{if(options.hasOwnProperty(key)){const vartype=typeof commanderAppOptions[key];commanderAppOptions[key]="boolean"===vartype?Boolean(options[key]):"number"===vartype?Number(options[key]):"string"===vartype?String(options[key]):options[key]}})),document.addEventListener("DOMContentLoaded",(()=>{_log.default.debug("DOM fully loaded - initializing commanderApp"),_log.default.debug(commanderAppOptions),commanderApp.start()}))}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=commander.min.js.map \ No newline at end of file diff --git a/amd/build/commander.min.js.map b/amd/build/commander.min.js.map index 122631a..5956168 100644 --- a/amd/build/commander.min.js.map +++ b/amd/build/commander.min.js.map @@ -1 +1 @@ -{"version":3,"file":"commander.min.js","sources":["../src/commander.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JS to the popup and interact with it.\n *\n *\n * Tested in Moodle 3.8\n *\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @copyright 2018 MFreak.nl\n * @author Luuk Verhoeven\n **/\n/* eslint-disable no-invalid-this */\ndefine(['jquery', 'core/notification', 'core/log'], function($, notification, Log) {\n 'use strict';\n\n // Keyboard codes.\n var ESCAPE = 27,\n ENTER = 13,\n ARROWUP = 38,\n ARROWDOWN = 40;\n\n /**\n * scroll to element.\n *\n * @param {string} elem\n * @param {int} speed\n * @returns {$}\n */\n $.fn.scrollTo = function(elem, speed) {\n $(this).stop().animate({\n scrollTop: $(this).scrollTop() - $(this).offset().top + $(elem).offset().top - 10\n }, speed == undefined ? 1000 : speed);\n return this;\n };\n\n /**\n * Options we can set from amd.\n * @type {{selector: string, blockid: number}}\n */\n var commanderAppOptions = {\n courseid: '',\n keys: [],\n };\n\n /**\n * Set options base on listed options\n * @param {object} options\n */\n var setOptions = function(options) {\n \"use strict\";\n var key, vartype;\n for (key in commanderAppOptions) {\n if (commanderAppOptions.hasOwnProperty(key) && options.hasOwnProperty(key)) {\n\n // Casting to prevent errors.\n vartype = typeof commanderAppOptions[key];\n if (vartype === \"boolean\") {\n commanderAppOptions[key] = Boolean(options[key]);\n } else if (vartype === 'number') {\n commanderAppOptions[key] = Number(options[key]);\n } else if (vartype === 'string') {\n commanderAppOptions[key] = String(options[key]);\n } else {\n commanderAppOptions[key] = options[key];\n }\n }\n }\n };\n\n /**\n * Commander plugin.\n * @type {{}}\n */\n const commanderApp = {\n\n /**\n * Modal jQuery element instance.\n */\n $mainModal: false,\n\n /**\n * Modal BG jQuery element instance.\n */\n $mainModalBackLayer: false,\n\n /**\n * Input field\n */\n $mainModalCommand: false,\n\n /**\n * Stores all li elements\n */\n $liSet: false,\n\n /**\n * Flag to check if modal is open.\n */\n isShow: false,\n\n /**\n * Save response\n */\n json: '',\n\n /**\n * Render UI.\n */\n render: function() {\n \"use strict\";\n var timer = 0;\n Log.debug('render UI');\n\n // @TODO we should use mustache.\n $('body').append('
      ' +\n '

      ' + M.util.get_string('js:header', 'local_commander') + '

      ' +\n '
      ' +\n '
      ' +\n '' +\n '
      ');\n\n // Set references.\n commanderApp.$mainModal = $('#local_commander_modal');\n commanderApp.$mainModalBackLayer = $('#local_commander_back_layer');\n commanderApp.$mainModalCommand = $('#local_commander_command');\n\n commanderApp.setHeight();\n\n commanderApp.$mainModalBackLayer.on('click', function() {\n commanderApp.hide();\n });\n\n // Search set some timeout optimize speed.\n commanderApp.$mainModalCommand.on('keydown', function(e) {\n var keyboardCode = e.keyCode || e.which;\n Log.debug('Code pressed:' + keyboardCode);\n\n switch (keyboardCode) {\n case ESCAPE:\n case ENTER:\n case ARROWUP:\n case ARROWDOWN:\n return;\n }\n\n Log.debug('Searching');\n\n clearTimeout(timer);\n timer = setTimeout(function() {\n commanderApp.search(commanderApp.$mainModalCommand.val());\n }, 100);\n });\n\n // Loading the menu content once.\n if (commanderApp.json === '') {\n commanderApp.loadMenu();\n }\n },\n\n /**\n * Start the commander.\n */\n start: function() {\n // Set holders.\n commanderApp.$mainModal = $('#local_commander_modal');\n\n $(window).on('keydown', function(e) {\n\n var keyboardCode = e.keyCode || e.which;\n Log.debug('Code pressed:', keyboardCode);\n Log.debug('Trigger keys:', commanderAppOptions.keys);\n Log.debug('Commander is visible:', commanderApp.isShow);\n\n // Check for arrow keys.\n if (commanderApp.isShow) {\n switch (keyboardCode) {\n case ESCAPE:\n commanderApp.hide();\n break;\n\n case ENTER:\n e.preventDefault();\n commanderApp.goToCommand();\n break;\n\n case ARROWUP:\n e.preventDefault();\n commanderApp.arrowUp();\n break;\n\n case ARROWDOWN:\n e.preventDefault();\n commanderApp.arrowDown();\n break;\n }\n return;\n }\n\n if (commanderAppOptions.keys.indexOf(keyboardCode.toString()) !== -1) {\n\n Log.debug('Commander keyboard key triggered');\n\n // Validate we not triggered in an editable area.\n if (e.target.tagName == 'INPUT' || e.target.tagName == 'SELECT'\n || e.target.tagName == 'TEXTAREA' || e.target.isContentEditable) {\n Log.debug('Hide when we are in an editable element');\n return;\n }\n\n e.preventDefault();\n\n // Only render if needed.\n if (commanderApp.$mainModal.length == 0) {\n commanderApp.render();\n }\n\n Log.debug('Open commander.');\n\n if (commanderApp.isShow) {\n commanderApp.hide();\n } else {\n commanderApp.show();\n }\n }\n });\n },\n\n /**\n * Highlight words\n *\n * @param {object} node\n * @param {string} word\n */\n highlightWord: function(node, word) {\n if (node.nodeType == 3) {\n var pos = node.data.toUpperCase().indexOf(word);\n if (pos >= 0) {\n var spannode = document.createElement('span');\n spannode.className = 'highlight';\n spannode.style.backgroundColor = '#f4bd21';\n var middlebit = node.splitText(pos);\n var middleclone = middlebit.cloneNode(true);\n spannode.appendChild(middleclone);\n middlebit.parentNode.replaceChild(spannode, middlebit);\n }\n\n } else if (node.nodeType == 1 && node.childNodes) {\n for (var i = 0; i < node.childNodes.length; ++i) {\n i += commanderApp.highlightWord(node.childNodes[i], word);\n }\n }\n },\n\n /**\n * Action on keyboard arrow key UP.\n */\n arrowUp: function() {\n Log.debug('arrowUp');\n var $el = $('#local_commander_modal ul li.active'),\n $prev = $el.closest(\"li\").prevAll(\"li:visible\").eq(0);\n\n if ($el.length) {\n $el.removeClass('active');\n }\n\n if ($prev.length) {\n $prev.addClass('active');\n } else {\n $el.addClass('active');\n }\n\n //\n commanderApp.scrollTo();\n },\n\n /**\n * Action on keyboard arrow key DOWN.\n */\n arrowDown: function() {\n Log.debug('arrowDown');\n var $el = $('#local_commander_modal ul li.active'),\n $next = $el.closest(\"li\").nextAll(\"li:visible\").eq(0);\n\n if ($el.length) {\n $el.removeClass('active');\n }\n if ($next.length) {\n $next.addClass('active');\n } else {\n $('#local_commander_modal ul li:visible').last().addClass('active');\n }\n //\n commanderApp.scrollTo();\n },\n\n /**\n * Scroll to active item.\n */\n scrollTo: function() {\n $('#local_commander_modal .local_commander-body div').scrollTo('#local_commander_modal li.active', 200);\n },\n\n /**\n * The command that we need to execute.\n */\n goToCommand: function() {\n Log.debug('goToCommand');\n // Check if there is a element selected.\n // Check if the element has link.\n // TODO maybe add way to execute other type of commands.\n var $el = $('#local_commander_modal ul li.active a');\n if ($el) {\n var link = $el.attr('href');\n if (link != '#') {\n window.location = link;\n }\n }\n },\n\n /**\n * Load menu\n */\n loadMenu: function() {\n \"use strict\";\n\n // TODO use the default webservice from Moodle instead.\n $.ajax({\n url: M.cfg.wwwroot + '/local/commander/ajax.php',\n method: \"GET\",\n data: {\n 'courseid': commanderAppOptions.courseid\n },\n dataType: \"json\",\n }).done(function(response) {\n Log.debug(response);\n commanderApp.json = response;\n\n commanderApp.setMenu();\n commanderApp.setHeight();\n }).fail(function() {\n notification.alert('js:error_parsing', 'local_commander');\n });\n },\n\n /**\n * Search in the commands.\n * @param {string} word\n */\n search: function(word) {\n \"use strict\";\n\n // Remove active.\n $('.local_commander-body ul li').show();\n commanderApp.$liSet.find('li.active').removeClass('active');\n\n // Remove highlights.\n commanderApp.$liSet.find(\"span.highlight\").each(function() {\n commanderApp.removeHighlight(this.parentNode);\n });\n\n if (word !== '') {\n\n commanderApp.$liSet.children().each(function() {\n commanderApp.highlightWord(this, word.toUpperCase());\n });\n\n // Set active li item.\n $('.local_commander-body span.highlight').first().parent().parent().addClass('active');\n\n // Hide others.\n $('.local_commander-body ul li:not(:has(span))').hide();\n }\n },\n\n /**\n * Build the ul command list.\n */\n setMenu: function() {\n \"use strict\";\n Log.debug('setMenu() ');\n\n var html = '
        ';\n\n // Only do things when needed.\n if (commanderAppOptions.courseid > 0) {\n Log.debug('Has course param.');\n html += commanderApp.renderMenuItems(commanderApp.json.courseadmin, 1);\n }\n\n // Always try adding admin menu.\n html += commanderApp.renderMenuItems(commanderApp.json.admin, 1);\n\n html += '
      ';\n commanderApp.$mainModal.find('.local_commander-body').append(html);\n\n commanderApp.$liSet = $('.local_commander-body ul');\n },\n\n /**\n * Render items and add the correct attr.\n *\n * @param {object} child\n * @param {int} depth\n * @param {string} parentName\n *\n * @returns {string}\n */\n renderMenuItems: function(child, depth, parentName) {\n \"use strict\";\n var html = '';\n\n // Check child.\n if (!child.name) {\n return html;\n }\n\n // Set parentName.\n if (!parentName) {\n parentName = '';\n } else {\n parentName += ' → ';\n }\n\n html += '
    • ';\n\n if (child.name) {\n // Add the same to buffer.\n //\n html += '' + parentName + child.name + '';\n }\n\n if (child.haschildren) {\n $.each(child.children, function(i, el) {\n html += commanderApp.renderMenuItems(el, depth + 1, parentName + child.name);\n });\n }\n\n html += '
    • ';\n return html;\n },\n\n /**\n * Show the modal\n */\n show: function() {\n \"use strict\";\n commanderApp.$mainModal.show();\n commanderApp.$mainModalBackLayer.show();\n\n commanderApp.isShow = true;\n\n // Focus on search field.\n commanderApp.$mainModalCommand.focus();\n },\n\n /**\n * Hide the modal\n */\n hide: function() {\n commanderApp.$mainModal.hide();\n commanderApp.$mainModalBackLayer.hide();\n\n commanderApp.isShow = false;\n },\n\n /**\n * Set 50% of viewport height\n */\n setHeight: function() {\n var height = Math.round($(window).height() / 2);\n commanderApp.$mainModal.height(height);\n $('.local_commander-body div').height(height - 100);\n },\n\n /**\n * Remove highlight\n * @param {object} node\n */\n removeHighlight: function(node) {\n $(node).html($(node).text());\n }\n };\n\n return {\n\n /**\n * Called from Moodle.\n * @param {array} params\n */\n init: function(params) {\n\n /**\n * Set the options.\n */\n setOptions(params);\n\n /**\n * Wait for jQuery\n */\n $(document).ready(function() {\n Log.debug('ready() - local commander v4.4');\n Log.debug(commanderAppOptions);\n commanderApp.start();\n });\n }\n };\n});\n"],"names":["define","$","notification","Log","fn","scrollTo","elem","speed","this","stop","animate","scrollTop","offset","top","undefined","commanderAppOptions","courseid","keys","commanderApp","$mainModal","$mainModalBackLayer","$mainModalCommand","$liSet","isShow","json","render","timer","debug","append","M","util","get_string","setHeight","on","hide","e","keyboardCode","keyCode","which","clearTimeout","setTimeout","search","val","loadMenu","start","window","preventDefault","goToCommand","arrowUp","arrowDown","indexOf","toString","target","tagName","isContentEditable","length","show","highlightWord","node","word","nodeType","pos","data","toUpperCase","spannode","document","createElement","className","style","backgroundColor","middlebit","splitText","middleclone","cloneNode","appendChild","parentNode","replaceChild","childNodes","i","$el","$prev","closest","prevAll","eq","removeClass","addClass","$next","nextAll","last","link","attr","location","ajax","url","cfg","wwwroot","method","dataType","done","response","setMenu","fail","alert","find","each","removeHighlight","children","first","parent","html","renderMenuItems","courseadmin","admin","child","depth","parentName","name","haschildren","el","focus","height","Math","round","text","init","params","options","key","vartype","hasOwnProperty","Boolean","Number","String","setOptions","ready"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"commander.min.js","sources":["../src/commander.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n// Initialize the module with imports.\nimport notification from 'core/notification';\nimport Log from 'core/log';\n\n/**\n * Keyboard key codes.\n */\nconst ESCAPE = 27;\nconst ENTER = 13;\nconst ARROWUP = 38;\nconst ARROWDOWN = 40;\n\n/**\n * Options we can set from AMD.\n */\nconst commanderAppOptions = {\n courseid: '',\n keys: [],\n};\n\n/**\n * Set options based on provided parameters.\n * @param {object} options\n */\nfunction setOptions(options) {\n Object.keys(commanderAppOptions).forEach((key) => {\n if (options.hasOwnProperty(key)) {\n const vartype = typeof commanderAppOptions[key];\n if (vartype === 'boolean') {\n commanderAppOptions[key] = Boolean(options[key]);\n } else if (vartype === 'number') {\n commanderAppOptions[key] = Number(options[key]);\n } else if (vartype === 'string') {\n commanderAppOptions[key] = String(options[key]);\n } else {\n commanderAppOptions[key] = options[key];\n }\n }\n });\n}\n\n/**\n * The main commander application.\n */\nconst commanderApp = {\n /**\n * Modal DOM element instance.\n */\n mainModal: null,\n\n /**\n * Modal background layer DOM element.\n */\n mainModalBackLayer: null,\n\n /**\n * Input field element.\n */\n mainModalCommand: null,\n\n /**\n * Stores all list item elements.\n */\n liSet: null,\n\n /**\n * Flag to check if the modal is open.\n */\n isShow: false,\n\n /**\n * Stores the response JSON.\n */\n json: '',\n\n /**\n * Render the UI.\n */\n render() {\n let timer = 0;\n Log.debug('Rendering UI');\n\n // Create the modal HTML.\n const modalHtml = `\n
      \n
      \n

      ${M.util.get_string('js:header', 'local_commander')}

      \n
      \n
      \n
        \n
        \n \n
        \n
        \n `;\n\n // Append the modal to the body.\n document.body.insertAdjacentHTML('beforeend', modalHtml);\n\n // Set references to the modal elements.\n this.mainModal = document.getElementById('local_commander_modal');\n this.mainModalBackLayer = document.getElementById('local_commander_back_layer');\n this.mainModalCommand = document.getElementById('local_commander_command');\n\n // Set the modal height.\n this.setHeight();\n\n // Add event listener to the background layer to hide the modal.\n this.mainModalBackLayer.addEventListener('click', () => {\n this.hide();\n });\n\n // Optimize search with a timeout.\n this.mainModalCommand.addEventListener('keydown', (e) => {\n const keyboardCode = e.keyCode || e.which;\n Log.debug(`Code pressed: ${keyboardCode}`);\n\n if ([ESCAPE, ENTER, ARROWUP, ARROWDOWN].includes(keyboardCode)) {\n return;\n }\n\n Log.debug('Searching');\n\n clearTimeout(timer);\n timer = setTimeout(() => {\n this.search(this.mainModalCommand.value);\n }, 100);\n });\n\n // Load the menu content once.\n if (this.json === '') {\n this.loadMenu();\n }\n },\n\n /**\n * Start the commander.\n */\n start() {\n window.addEventListener('keydown', (e) => {\n const keyboardCode = e.keyCode || e.which;\n Log.debug(`Code pressed: ${keyboardCode}`);\n Log.debug(`Trigger keys: ${commanderAppOptions.keys}`);\n Log.debug(`Commander is visible: ${this.isShow}`);\n\n // Check for arrow keys when the modal is open.\n if (this.isShow) {\n switch (keyboardCode) {\n case ESCAPE:\n this.hide();\n break;\n case ENTER:\n e.preventDefault();\n this.goToCommand();\n break;\n case ARROWUP:\n e.preventDefault();\n this.arrowUp();\n break;\n case ARROWDOWN:\n e.preventDefault();\n this.arrowDown();\n break;\n }\n return;\n }\n\n // Check if the pressed key is one of the trigger keys.\n if (commanderAppOptions.keys.includes(keyboardCode.toString())) {\n Log.debug('Commander keyboard key triggered');\n\n // Validate that we're not in an editable area.\n const target = e.target;\n const tagName = target.tagName.toUpperCase();\n if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName) || target.isContentEditable) {\n Log.debug('Ignoring keypress in editable element');\n return;\n }\n\n e.preventDefault();\n\n // Render the modal if it hasn't been created yet.\n if (!this.mainModal) {\n this.render();\n }\n\n Log.debug('Opening commander');\n\n if (this.isShow) {\n this.hide();\n } else {\n this.show();\n }\n }\n });\n },\n\n /**\n * Highlight words in the menu items.\n * @param {Node} node\n * @param {string} word\n */\n highlightWord(node, word) {\n if (node.nodeType === Node.TEXT_NODE) {\n const pos = node.data.toUpperCase().indexOf(word);\n if (pos >= 0) {\n const spannode = document.createElement('span');\n spannode.className = 'highlight';\n const middlebit = node.splitText(pos);\n middlebit.splitText(word.length);\n const middleclone = middlebit.cloneNode(true);\n spannode.appendChild(middleclone);\n middlebit.parentNode.replaceChild(spannode, middlebit);\n }\n } else if (node.nodeType === Node.ELEMENT_NODE && node.childNodes && !['SCRIPT', 'STYLE'].includes(node.tagName)) {\n node.childNodes.forEach((child) => {\n this.highlightWord(child, word);\n });\n }\n },\n\n /**\n * Navigate up in the menu.\n */\n arrowUp() {\n Log.debug('Navigating up');\n const activeItem = this.mainModal.querySelector('ul li.active');\n let prevItem = null;\n\n if (activeItem) {\n activeItem.classList.remove('active');\n prevItem = activeItem.previousElementSibling;\n while (prevItem && prevItem.style.display === 'none') {\n prevItem = prevItem.previousElementSibling;\n }\n }\n\n if (prevItem) {\n prevItem.classList.add('active');\n } else if (activeItem) {\n activeItem.classList.add('active');\n }\n\n this.scrollToActiveItem();\n },\n\n /**\n * Navigate down in the menu.\n */\n arrowDown() {\n Log.debug('Navigating down');\n const activeItem = this.mainModal.querySelector('ul li.active');\n let nextItem = null;\n\n if (activeItem) {\n activeItem.classList.remove('active');\n nextItem = activeItem.nextElementSibling;\n while (nextItem && nextItem.style.display === 'none') {\n nextItem = nextItem.nextElementSibling;\n }\n }\n\n if (nextItem) {\n nextItem.classList.add('active');\n } else {\n const items = Array.from(this.mainModal.querySelectorAll('ul li'));\n const lastVisibleItem = items.reverse().find((item) => item.style.display !== 'none');\n if (lastVisibleItem) {\n lastVisibleItem.classList.add('active');\n }\n }\n\n this.scrollToActiveItem();\n },\n\n /**\n * Scroll to the active menu item.\n */\n scrollToActiveItem() {\n const container = this.mainModal.querySelector('.local_commander-body div');\n const activeItem = this.mainModal.querySelector('li.active');\n\n if (activeItem && container) {\n container.scrollTop = activeItem.offsetTop - container.offsetTop - 10;\n }\n },\n\n /**\n * Execute the selected command.\n */\n goToCommand() {\n Log.debug('Executing command');\n const activeLink = this.mainModal.querySelector('ul li.active a');\n if (activeLink) {\n const link = activeLink.getAttribute('href');\n if (link !== '#') {\n window.location.href = link;\n }\n }\n },\n\n /**\n * Load the menu from the server.\n */\n loadMenu() {\n fetch(`${M.cfg.wwwroot}/local/commander/ajax.php?courseid=${commanderAppOptions.courseid}`, {\n method: 'GET',\n credentials: 'same-origin',\n })\n .then((response) => response.json())\n .then((data) => {\n Log.debug(data);\n this.json = data;\n\n this.setMenu();\n this.setHeight();\n })\n .catch(() => {\n notification.alert('js:error_parsing', 'local_commander');\n });\n },\n\n /**\n * Search the menu for the input word.\n * @param {string} word\n */\n search(word) {\n // Remove active state and show all items.\n const listItems = this.mainModal.querySelectorAll('.local_commander-body ul li');\n listItems.forEach((li) => {\n li.style.display = '';\n li.classList.remove('active');\n });\n\n // Remove existing highlights.\n const highlights = this.mainModal.querySelectorAll('.highlight');\n highlights.forEach((span) => {\n this.removeHighlight(span.parentNode);\n });\n\n if (word !== '') {\n const wordUpper = word.toUpperCase();\n let firstHighlighted = null;\n\n this.liSet.forEach((li) => {\n this.highlightWord(li, wordUpper);\n if (!firstHighlighted && li.querySelector('.highlight')) {\n firstHighlighted = li;\n }\n });\n\n // Set the first matching item as active.\n if (firstHighlighted) {\n firstHighlighted.classList.add('active');\n }\n\n // Hide items that don't match.\n listItems.forEach((li) => {\n if (!li.querySelector('.highlight')) {\n li.style.display = 'none';\n }\n });\n }\n },\n\n /**\n * Build the command menu.\n */\n setMenu() {\n Log.debug('Setting up menu');\n\n let html = '';\n\n if (commanderAppOptions.courseid > 0) {\n Log.debug('Including course administration menu');\n html += this.renderMenuItems(this.json.courseadmin, '');\n }\n\n html += this.renderMenuItems(this.json.admin, '');\n\n const ulElement = this.mainModal.querySelector('.local_commander-body ul');\n ulElement.innerHTML = html;\n\n this.liSet = this.mainModal.querySelectorAll('.local_commander-body ul li');\n },\n\n /**\n * Render menu items recursively.\n * @param {object} item\n * @param {string} parentName\n * @returns {string}\n */\n renderMenuItems(item, parentName) {\n if (!item.name) {\n return '';\n }\n\n let html = '';\n\n const fullName = parentName ? `${parentName} → ${item.name}` : item.name;\n html += `
      • ${fullName}
      • `;\n\n if (item.haschildren) {\n item.children.forEach((child) => {\n html += this.renderMenuItems(child, fullName);\n });\n }\n\n return html;\n },\n\n /**\n * Show the modal.\n */\n show() {\n this.mainModal.style.display = 'block';\n this.mainModalBackLayer.style.display = 'block';\n this.isShow = true;\n\n // Focus on the search field.\n this.mainModalCommand.focus();\n },\n\n /**\n * Hide the modal.\n */\n hide() {\n this.mainModal.style.display = 'none';\n this.mainModalBackLayer.style.display = 'none';\n this.isShow = false;\n },\n\n /**\n * Set the modal height to 50% of the viewport.\n */\n setHeight() {\n const height = Math.round(window.innerHeight / 2);\n this.mainModal.style.height = `${height}px`;\n const bodyDiv = this.mainModal.querySelector('.local_commander-body div');\n if (bodyDiv) {\n bodyDiv.style.height = `${height - 100}px`;\n }\n },\n\n /**\n * Remove highlights from text nodes.\n * @param {Node} node\n */\n removeHighlight(node) {\n node.innerHTML = node.textContent;\n },\n};\n\n/**\n * Initialize the module.\n * @param {object} params\n */\nfunction init(params) {\n // Set the options.\n setOptions(params);\n\n // Wait for the DOM to be fully loaded.\n document.addEventListener('DOMContentLoaded', () => {\n Log.debug('DOM fully loaded - initializing commanderApp');\n Log.debug(commanderAppOptions);\n commanderApp.start();\n });\n}\n\nexport default {\n init,\n};\n"],"names":["ESCAPE","ENTER","ARROWUP","ARROWDOWN","commanderAppOptions","courseid","keys","commanderApp","mainModal","mainModalBackLayer","mainModalCommand","liSet","isShow","json","render","timer","debug","modalHtml","M","util","get_string","document","body","insertAdjacentHTML","getElementById","setHeight","addEventListener","hide","e","keyboardCode","keyCode","which","includes","clearTimeout","setTimeout","search","this","value","loadMenu","start","window","preventDefault","goToCommand","arrowUp","arrowDown","toString","target","tagName","toUpperCase","isContentEditable","show","highlightWord","node","word","nodeType","Node","TEXT_NODE","pos","data","indexOf","spannode","createElement","className","middlebit","splitText","length","middleclone","cloneNode","appendChild","parentNode","replaceChild","ELEMENT_NODE","childNodes","forEach","child","activeItem","querySelector","prevItem","classList","remove","previousElementSibling","style","display","add","scrollToActiveItem","nextItem","nextElementSibling","lastVisibleItem","Array","from","querySelectorAll","reverse","find","item","container","scrollTop","offsetTop","activeLink","link","getAttribute","location","href","fetch","cfg","wwwroot","method","credentials","then","response","setMenu","catch","alert","listItems","li","span","removeHighlight","wordUpper","firstHighlighted","html","renderMenuItems","courseadmin","admin","innerHTML","parentName","name","fullName","haschildren","children","focus","height","Math","round","innerHeight","bodyDiv","textContent","init","params","options","Object","key","hasOwnProperty","vartype","Boolean","Number","String"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/settings.min.js b/amd/build/settings.min.js index 82cb239..de22013 100644 --- a/amd/build/settings.min.js +++ b/amd/build/settings.min.js @@ -1,12 +1,12 @@ +define("local_commander/settings",["exports","core/str","core/notification","core/log"],(function(_exports,_str,_notification,_log){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** - * Helper for keycodes - * - * Tested in Moodle 3.8 - * - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright 2019 MFreak.nl - * @author Luuk Verhoeven - **/ -define("local_commander/settings",["jquery","core/str","core/notification","core/log"],(function($,str,Notification,Log){const commanderSettings_init=function(){let $el=$("#id_s_local_commander_keys");0!==$el.length&&(str.get_string("js:keycode_help","local_commander").then((function(message){return $el.before('
        '+message+"
        "),message})).fail(Notification.exception),$(document).on("keydown",(function(e){if("INPUT"===e.target.tagName||"SELECT"===e.target.tagName||"TEXTAREA"===e.target.tagName||e.target.isContentEditable)return void Log.debug("Hide when we are in an editable element");let keyboardCode=e.keyCode||e.which;$("#key-monitor div").text("key = "+e.key+" | code = "+keyboardCode)})))};return{init:function(){$(document).ready((function(){Log.debug("ready() - setting local commander v4.4"),commanderSettings_init()}))}}})); + * Helper for keycodes + * + * Tested in Moodle 3.8 + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @copyright 2019 MFreak.nl + * @author Luuk Verhoeven + **/function init(){const el=document.getElementById("id_s_local_commander_keys");el&&(_str.default.get_string("js:keycode_help","local_commander").then((message=>{el.insertAdjacentHTML("beforebegin",'\n
        \n '.concat(message,"\n
        \n
        \n "))})).catch(_notification.default.exception),document.addEventListener("keydown",(e=>{const tagName=e.target.tagName.toUpperCase();if("INPUT"===tagName||"SELECT"===tagName||"TEXTAREA"===tagName||e.target.isContentEditable)return void _log.default.debug("Key event ignored in editable element");const keyboardCode=e.keyCode||e.which,monitorDiv=document.querySelector("#key-monitor div");monitorDiv&&(monitorDiv.textContent="key = ".concat(e.key," | code = ").concat(keyboardCode))})))}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_str=_interopRequireDefault(_str),_notification=_interopRequireDefault(_notification),_log=_interopRequireDefault(_log),document.addEventListener("DOMContentLoaded",(()=>{_log.default.debug("DOM fully loaded - initializing commander settings"),init()}));var _default={init:init};return _exports.default=_default,_exports.default})); //# sourceMappingURL=settings.min.js.map \ No newline at end of file diff --git a/amd/build/settings.min.js.map b/amd/build/settings.min.js.map index c47b9ce..9930949 100644 --- a/amd/build/settings.min.js.map +++ b/amd/build/settings.min.js.map @@ -1 +1 @@ -{"version":3,"file":"settings.min.js","sources":["../src/settings.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper for keycodes\n *\n * Tested in Moodle 3.8\n *\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @copyright 2019 MFreak.nl\n * @author Luuk Verhoeven\n **/\n/* eslint-disable no-invalid-this */\ndefine(['jquery', 'core/str', 'core/notification', 'core/log'],\n function($, str, Notification, Log) {\n 'use strict';\n\n /**\n * commanderSettings\n */\n const commanderSettings = {\n\n /**\n * Init\n */\n init: function() {\n let $el = $('#id_s_local_commander_keys');\n\n if ($el.length === 0) {\n return;\n }\n\n str.get_string('js:keycode_help', 'local_commander').then(function(message) {\n $el.before('
        ' + message + '
        ');\n return message;\n }).fail(Notification.exception);\n\n $(document).on('keydown', function(e) {\n\n if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT'\n || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {\n Log.debug('Hide when we are in an editable element');\n return;\n }\n\n let keyboardCode = e.keyCode || e.which;\n $('#key-monitor div').text('key = ' + e.key + ' | code = ' + keyboardCode);\n });\n }\n };\n\n return {\n\n /**\n * Called from Moodle.\n */\n init: function() {\n\n /**\n * Wait for jQuery\n */\n $(document).ready(function() {\n Log.debug('ready() - setting local commander v4.4');\n commanderSettings.init();\n });\n }\n };\n});\n"],"names":["define","$","str","Notification","Log","commanderSettings","$el","length","get_string","then","message","before","fail","exception","document","on","e","target","tagName","isContentEditable","debug","keyboardCode","keyCode","which","text","key","init","ready"],"mappings":";;;;;;;;;AAyBAA,kCAAO,CAAC,SAAU,WAAY,oBAAqB,aAC/C,SAASC,EAAGC,IAAKC,aAAcC,WAMzBC,uBAKI,eACEC,IAAML,EAAE,8BAEO,IAAfK,IAAIC,SAIRL,IAAIM,WAAW,kBAAmB,mBAAmBC,MAAK,SAASC,gBAC/DJ,IAAIK,OAAO,qDAAuDD,QAAU,yBACrEA,WACRE,KAAKT,aAAaU,WAErBZ,EAAEa,UAAUC,GAAG,WAAW,SAASC,MAEN,UAArBA,EAAEC,OAAOC,SAA4C,WAArBF,EAAEC,OAAOC,SACjB,aAArBF,EAAEC,OAAOC,SAA0BF,EAAEC,OAAOE,8BAC/Cf,IAAIgB,MAAM,+CAIVC,aAAeL,EAAEM,SAAWN,EAAEO,MAClCtB,EAAE,oBAAoBuB,KAAK,SAAWR,EAAES,IAAM,aAAeJ,yBAKlE,CAKHK,KAAM,WAKFzB,EAAEa,UAAUa,OAAM,WACdvB,IAAIgB,MAAM,0CACVf"} \ No newline at end of file +{"version":3,"file":"settings.min.js","sources":["../src/settings.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Helper for keycodes\n *\n * Tested in Moodle 3.8\n *\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @copyright 2019 MFreak.nl\n * @author Luuk Verhoeven\n **/\n\nimport str from 'core/str';\nimport Notification from 'core/notification';\nimport Log from 'core/log';\n\n/**\n * Initialize the commander settings.\n */\nfunction init() {\n const el = document.getElementById('id_s_local_commander_keys');\n\n if (!el) {\n return;\n }\n\n str.get_string('js:keycode_help', 'local_commander')\n .then((message) => {\n el.insertAdjacentHTML('beforebegin', `\n
        \n ${message}\n
        \n
        \n `);\n })\n .catch(Notification.exception);\n\n document.addEventListener('keydown', (e) => {\n const tagName = e.target.tagName.toUpperCase();\n\n if (\n tagName === 'INPUT' ||\n tagName === 'SELECT' ||\n tagName === 'TEXTAREA' ||\n e.target.isContentEditable\n ) {\n Log.debug('Key event ignored in editable element');\n return;\n }\n\n const keyboardCode = e.keyCode || e.which;\n const monitorDiv = document.querySelector('#key-monitor div');\n if (monitorDiv) {\n monitorDiv.textContent = `key = ${e.key} | code = ${keyboardCode}`;\n }\n });\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n Log.debug('DOM fully loaded - initializing commander settings');\n init();\n});\n\nexport default {\n init,\n};\n"],"names":["init","el","document","getElementById","get_string","then","message","insertAdjacentHTML","catch","Notification","exception","addEventListener","e","tagName","target","toUpperCase","isContentEditable","debug","keyboardCode","keyCode","which","monitorDiv","querySelector","textContent","key"],"mappings":";;;;;;;;;eAgCSA,aACCC,GAAKC,SAASC,eAAe,6BAE9BF,kBAIDG,WAAW,kBAAmB,mBAC7BC,MAAMC,UACHL,GAAGM,mBAAmB,kHAETD,2FAKhBE,MAAMC,sBAAaC,WAExBR,SAASS,iBAAiB,WAAYC,UAC5BC,QAAUD,EAAEE,OAAOD,QAAQE,iBAGjB,UAAZF,SACY,WAAZA,SACY,aAAZA,SACAD,EAAEE,OAAOE,2CAELC,MAAM,+CAIRC,aAAeN,EAAEO,SAAWP,EAAEQ,MAC9BC,WAAanB,SAASoB,cAAc,oBACtCD,aACAA,WAAWE,4BAAuBX,EAAEY,yBAAgBN,2NAKhEhB,SAASS,iBAAiB,oBAAoB,kBACtCM,MAAM,sDACVjB,uBAGW,CACXA,KAAAA"} \ No newline at end of file diff --git a/amd/src/commander.js b/amd/src/commander.js index 3303e82..0183446 100644 --- a/amd/src/commander.js +++ b/amd/src/commander.js @@ -13,510 +13,476 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +// Initialize the module with imports. +import notification from 'core/notification'; +import Log from 'core/log'; + +/** + * Keyboard key codes. + */ +const ESCAPE = 27; +const ENTER = 13; +const ARROWUP = 38; +const ARROWDOWN = 40; + /** - * JS to the popup and interact with it. - * - * - * Tested in Moodle 3.8 - * - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @copyright 2018 MFreak.nl - * @author Luuk Verhoeven - **/ -/* eslint-disable no-invalid-this */ -define(['jquery', 'core/notification', 'core/log'], function($, notification, Log) { - 'use strict'; - - // Keyboard codes. - var ESCAPE = 27, - ENTER = 13, - ARROWUP = 38, - ARROWDOWN = 40; + * Options we can set from AMD. + */ +const commanderAppOptions = { + courseid: '', + keys: [], +}; + +/** + * Set options based on provided parameters. + * @param {object} options + */ +function setOptions(options) { + Object.keys(commanderAppOptions).forEach((key) => { + if (options.hasOwnProperty(key)) { + const vartype = typeof commanderAppOptions[key]; + if (vartype === 'boolean') { + commanderAppOptions[key] = Boolean(options[key]); + } else if (vartype === 'number') { + commanderAppOptions[key] = Number(options[key]); + } else if (vartype === 'string') { + commanderAppOptions[key] = String(options[key]); + } else { + commanderAppOptions[key] = options[key]; + } + } + }); +} +/** + * The main commander application. + */ +const commanderApp = { /** - * scroll to element. - * - * @param {string} elem - * @param {int} speed - * @returns {$} + * Modal DOM element instance. */ - $.fn.scrollTo = function(elem, speed) { - $(this).stop().animate({ - scrollTop: $(this).scrollTop() - $(this).offset().top + $(elem).offset().top - 10 - }, speed == undefined ? 1000 : speed); - return this; - }; + mainModal: null, /** - * Options we can set from amd. - * @type {{selector: string, blockid: number}} + * Modal background layer DOM element. */ - var commanderAppOptions = { - courseid: '', - keys: [], - }; + mainModalBackLayer: null, /** - * Set options base on listed options - * @param {object} options + * Input field element. */ - var setOptions = function(options) { - "use strict"; - var key, vartype; - for (key in commanderAppOptions) { - if (commanderAppOptions.hasOwnProperty(key) && options.hasOwnProperty(key)) { - - // Casting to prevent errors. - vartype = typeof commanderAppOptions[key]; - if (vartype === "boolean") { - commanderAppOptions[key] = Boolean(options[key]); - } else if (vartype === 'number') { - commanderAppOptions[key] = Number(options[key]); - } else if (vartype === 'string') { - commanderAppOptions[key] = String(options[key]); - } else { - commanderAppOptions[key] = options[key]; - } - } - } - }; + mainModalCommand: null, /** - * Commander plugin. - * @type {{}} + * Stores all list item elements. */ - const commanderApp = { - - /** - * Modal jQuery element instance. - */ - $mainModal: false, - - /** - * Modal BG jQuery element instance. - */ - $mainModalBackLayer: false, - - /** - * Input field - */ - $mainModalCommand: false, - - /** - * Stores all li elements - */ - $liSet: false, - - /** - * Flag to check if modal is open. - */ - isShow: false, - - /** - * Save response - */ - json: '', - - /** - * Render UI. - */ - render: function() { - "use strict"; - var timer = 0; - Log.debug('render UI'); - - // @TODO we should use mustache. - $('body').append('
        ' + - '

        ' + M.util.get_string('js:header', 'local_commander') + '

        ' + - '
        ' + - '
        ' + - '' + - '
        '); - - // Set references. - commanderApp.$mainModal = $('#local_commander_modal'); - commanderApp.$mainModalBackLayer = $('#local_commander_back_layer'); - commanderApp.$mainModalCommand = $('#local_commander_command'); - - commanderApp.setHeight(); - - commanderApp.$mainModalBackLayer.on('click', function() { - commanderApp.hide(); - }); + liSet: null, - // Search set some timeout optimize speed. - commanderApp.$mainModalCommand.on('keydown', function(e) { - var keyboardCode = e.keyCode || e.which; - Log.debug('Code pressed:' + keyboardCode); + /** + * Flag to check if the modal is open. + */ + isShow: false, + + /** + * Stores the response JSON. + */ + json: '', + + /** + * Render the UI. + */ + render() { + let timer = 0; + Log.debug('Rendering UI'); + + // Create the modal HTML. + const modalHtml = ` +
        +
        +

        ${M.util.get_string('js:header', 'local_commander')}

        +
        +
        +
          +
          + +
          +
          + `; + + // Append the modal to the body. + document.body.insertAdjacentHTML('beforeend', modalHtml); + + // Set references to the modal elements. + this.mainModal = document.getElementById('local_commander_modal'); + this.mainModalBackLayer = document.getElementById('local_commander_back_layer'); + this.mainModalCommand = document.getElementById('local_commander_command'); + + // Set the modal height. + this.setHeight(); + + // Add event listener to the background layer to hide the modal. + this.mainModalBackLayer.addEventListener('click', () => { + this.hide(); + }); + + // Optimize search with a timeout. + this.mainModalCommand.addEventListener('keydown', (e) => { + const keyboardCode = e.keyCode || e.which; + Log.debug(`Code pressed: ${keyboardCode}`); + + if ([ESCAPE, ENTER, ARROWUP, ARROWDOWN].includes(keyboardCode)) { + return; + } + + Log.debug('Searching'); + + clearTimeout(timer); + timer = setTimeout(() => { + this.search(this.mainModalCommand.value); + }, 100); + }); + // Load the menu content once. + if (this.json === '') { + this.loadMenu(); + } + }, + + /** + * Start the commander. + */ + start() { + window.addEventListener('keydown', (e) => { + const keyboardCode = e.keyCode || e.which; + Log.debug(`Code pressed: ${keyboardCode}`); + Log.debug(`Trigger keys: ${commanderAppOptions.keys}`); + Log.debug(`Commander is visible: ${this.isShow}`); + + // Check for arrow keys when the modal is open. + if (this.isShow) { switch (keyboardCode) { case ESCAPE: + this.hide(); + break; case ENTER: + e.preventDefault(); + this.goToCommand(); + break; case ARROWUP: + e.preventDefault(); + this.arrowUp(); + break; case ARROWDOWN: - return; + e.preventDefault(); + this.arrowDown(); + break; } + return; + } - Log.debug('Searching'); - - clearTimeout(timer); - timer = setTimeout(function() { - commanderApp.search(commanderApp.$mainModalCommand.val()); - }, 100); - }); + // Check if the pressed key is one of the trigger keys. + if (commanderAppOptions.keys.includes(keyboardCode.toString())) { + Log.debug('Commander keyboard key triggered'); - // Loading the menu content once. - if (commanderApp.json === '') { - commanderApp.loadMenu(); - } - }, - - /** - * Start the commander. - */ - start: function() { - // Set holders. - commanderApp.$mainModal = $('#local_commander_modal'); - - $(window).on('keydown', function(e) { - - var keyboardCode = e.keyCode || e.which; - Log.debug('Code pressed:', keyboardCode); - Log.debug('Trigger keys:', commanderAppOptions.keys); - Log.debug('Commander is visible:', commanderApp.isShow); - - // Check for arrow keys. - if (commanderApp.isShow) { - switch (keyboardCode) { - case ESCAPE: - commanderApp.hide(); - break; - - case ENTER: - e.preventDefault(); - commanderApp.goToCommand(); - break; - - case ARROWUP: - e.preventDefault(); - commanderApp.arrowUp(); - break; - - case ARROWDOWN: - e.preventDefault(); - commanderApp.arrowDown(); - break; - } + // Validate that we're not in an editable area. + const target = e.target; + const tagName = target.tagName.toUpperCase(); + if (['INPUT', 'SELECT', 'TEXTAREA'].includes(tagName) || target.isContentEditable) { + Log.debug('Ignoring keypress in editable element'); return; } - if (commanderAppOptions.keys.indexOf(keyboardCode.toString()) !== -1) { - - Log.debug('Commander keyboard key triggered'); - - // Validate we not triggered in an editable area. - if (e.target.tagName == 'INPUT' || e.target.tagName == 'SELECT' - || e.target.tagName == 'TEXTAREA' || e.target.isContentEditable) { - Log.debug('Hide when we are in an editable element'); - return; - } + e.preventDefault(); - e.preventDefault(); - - // Only render if needed. - if (commanderApp.$mainModal.length == 0) { - commanderApp.render(); - } - - Log.debug('Open commander.'); - - if (commanderApp.isShow) { - commanderApp.hide(); - } else { - commanderApp.show(); - } - } - }); - }, - - /** - * Highlight words - * - * @param {object} node - * @param {string} word - */ - highlightWord: function(node, word) { - if (node.nodeType == 3) { - var pos = node.data.toUpperCase().indexOf(word); - if (pos >= 0) { - var spannode = document.createElement('span'); - spannode.className = 'highlight'; - spannode.style.backgroundColor = '#f4bd21'; - var middlebit = node.splitText(pos); - var middleclone = middlebit.cloneNode(true); - spannode.appendChild(middleclone); - middlebit.parentNode.replaceChild(spannode, middlebit); + // Render the modal if it hasn't been created yet. + if (!this.mainModal) { + this.render(); } - } else if (node.nodeType == 1 && node.childNodes) { - for (var i = 0; i < node.childNodes.length; ++i) { - i += commanderApp.highlightWord(node.childNodes[i], word); + Log.debug('Opening commander'); + + if (this.isShow) { + this.hide(); + } else { + this.show(); } } - }, - - /** - * Action on keyboard arrow key UP. - */ - arrowUp: function() { - Log.debug('arrowUp'); - var $el = $('#local_commander_modal ul li.active'), - $prev = $el.closest("li").prevAll("li:visible").eq(0); - - if ($el.length) { - $el.removeClass('active'); + }); + }, + + /** + * Highlight words in the menu items. + * @param {Node} node + * @param {string} word + */ + highlightWord(node, word) { + if (node.nodeType === Node.TEXT_NODE) { + const pos = node.data.toUpperCase().indexOf(word); + if (pos >= 0) { + const spannode = document.createElement('span'); + spannode.className = 'highlight'; + const middlebit = node.splitText(pos); + middlebit.splitText(word.length); + const middleclone = middlebit.cloneNode(true); + spannode.appendChild(middleclone); + middlebit.parentNode.replaceChild(spannode, middlebit); } + } else if (node.nodeType === Node.ELEMENT_NODE && node.childNodes && !['SCRIPT', 'STYLE'].includes(node.tagName)) { + node.childNodes.forEach((child) => { + this.highlightWord(child, word); + }); + } + }, - if ($prev.length) { - $prev.addClass('active'); - } else { - $el.addClass('active'); + /** + * Navigate up in the menu. + */ + arrowUp() { + Log.debug('Navigating up'); + const activeItem = this.mainModal.querySelector('ul li.active'); + let prevItem = null; + + if (activeItem) { + activeItem.classList.remove('active'); + prevItem = activeItem.previousElementSibling; + while (prevItem && prevItem.style.display === 'none') { + prevItem = prevItem.previousElementSibling; } + } - // - commanderApp.scrollTo(); - }, + if (prevItem) { + prevItem.classList.add('active'); + } else if (activeItem) { + activeItem.classList.add('active'); + } - /** - * Action on keyboard arrow key DOWN. - */ - arrowDown: function() { - Log.debug('arrowDown'); - var $el = $('#local_commander_modal ul li.active'), - $next = $el.closest("li").nextAll("li:visible").eq(0); + this.scrollToActiveItem(); + }, - if ($el.length) { - $el.removeClass('active'); - } - if ($next.length) { - $next.addClass('active'); - } else { - $('#local_commander_modal ul li:visible').last().addClass('active'); + /** + * Navigate down in the menu. + */ + arrowDown() { + Log.debug('Navigating down'); + const activeItem = this.mainModal.querySelector('ul li.active'); + let nextItem = null; + + if (activeItem) { + activeItem.classList.remove('active'); + nextItem = activeItem.nextElementSibling; + while (nextItem && nextItem.style.display === 'none') { + nextItem = nextItem.nextElementSibling; } - // - commanderApp.scrollTo(); - }, - - /** - * Scroll to active item. - */ - scrollTo: function() { - $('#local_commander_modal .local_commander-body div').scrollTo('#local_commander_modal li.active', 200); - }, - - /** - * The command that we need to execute. - */ - goToCommand: function() { - Log.debug('goToCommand'); - // Check if there is a element selected. - // Check if the element has link. - // TODO maybe add way to execute other type of commands. - var $el = $('#local_commander_modal ul li.active a'); - if ($el) { - var link = $el.attr('href'); - if (link != '#') { - window.location = link; - } + } + + if (nextItem) { + nextItem.classList.add('active'); + } else { + const items = Array.from(this.mainModal.querySelectorAll('ul li')); + const lastVisibleItem = items.reverse().find((item) => item.style.display !== 'none'); + if (lastVisibleItem) { + lastVisibleItem.classList.add('active'); } - }, - - /** - * Load menu - */ - loadMenu: function() { - "use strict"; - - // TODO use the default webservice from Moodle instead. - $.ajax({ - url: M.cfg.wwwroot + '/local/commander/ajax.php', - method: "GET", - data: { - 'courseid': commanderAppOptions.courseid - }, - dataType: "json", - }).done(function(response) { - Log.debug(response); - commanderApp.json = response; - - commanderApp.setMenu(); - commanderApp.setHeight(); - }).fail(function() { - notification.alert('js:error_parsing', 'local_commander'); - }); - }, - - /** - * Search in the commands. - * @param {string} word - */ - search: function(word) { - "use strict"; - - // Remove active. - $('.local_commander-body ul li').show(); - commanderApp.$liSet.find('li.active').removeClass('active'); - - // Remove highlights. - commanderApp.$liSet.find("span.highlight").each(function() { - commanderApp.removeHighlight(this.parentNode); - }); + } - if (word !== '') { + this.scrollToActiveItem(); + }, - commanderApp.$liSet.children().each(function() { - commanderApp.highlightWord(this, word.toUpperCase()); - }); + /** + * Scroll to the active menu item. + */ + scrollToActiveItem() { + const container = this.mainModal.querySelector('.local_commander-body div'); + const activeItem = this.mainModal.querySelector('li.active'); - // Set active li item. - $('.local_commander-body span.highlight').first().parent().parent().addClass('active'); + if (activeItem && container) { + container.scrollTop = activeItem.offsetTop - container.offsetTop - 10; + } + }, - // Hide others. - $('.local_commander-body ul li:not(:has(span))').hide(); + /** + * Execute the selected command. + */ + goToCommand() { + Log.debug('Executing command'); + const activeLink = this.mainModal.querySelector('ul li.active a'); + if (activeLink) { + const link = activeLink.getAttribute('href'); + if (link !== '#') { + window.location.href = link; } - }, + } + }, - /** - * Build the ul command list. - */ - setMenu: function() { - "use strict"; - Log.debug('setMenu() '); + /** + * Load the menu from the server. + */ + loadMenu() { + fetch(`${M.cfg.wwwroot}/local/commander/ajax.php?courseid=${commanderAppOptions.courseid}`, { + method: 'GET', + credentials: 'same-origin', + }) + .then((response) => response.json()) + .then((data) => { + Log.debug(data); + this.json = data; + + this.setMenu(); + this.setHeight(); + }) + .catch(() => { + notification.alert('js:error_parsing', 'local_commander'); + }); + }, - var html = '
            '; + /** + * Search the menu for the input word. + * @param {string} word + */ + search(word) { + // Remove active state and show all items. + const listItems = this.mainModal.querySelectorAll('.local_commander-body ul li'); + listItems.forEach((li) => { + li.style.display = ''; + li.classList.remove('active'); + }); + + // Remove existing highlights. + const highlights = this.mainModal.querySelectorAll('.highlight'); + highlights.forEach((span) => { + this.removeHighlight(span.parentNode); + }); + + if (word !== '') { + const wordUpper = word.toUpperCase(); + let firstHighlighted = null; + + this.liSet.forEach((li) => { + this.highlightWord(li, wordUpper); + if (!firstHighlighted && li.querySelector('.highlight')) { + firstHighlighted = li; + } + }); - // Only do things when needed. - if (commanderAppOptions.courseid > 0) { - Log.debug('Has course param.'); - html += commanderApp.renderMenuItems(commanderApp.json.courseadmin, 1); + // Set the first matching item as active. + if (firstHighlighted) { + firstHighlighted.classList.add('active'); } - // Always try adding admin menu. - html += commanderApp.renderMenuItems(commanderApp.json.admin, 1); - - html += '
          '; - commanderApp.$mainModal.find('.local_commander-body').append(html); - - commanderApp.$liSet = $('.local_commander-body ul'); - }, - - /** - * Render items and add the correct attr. - * - * @param {object} child - * @param {int} depth - * @param {string} parentName - * - * @returns {string} - */ - renderMenuItems: function(child, depth, parentName) { - "use strict"; - var html = ''; - - // Check child. - if (!child.name) { - return html; - } + // Hide items that don't match. + listItems.forEach((li) => { + if (!li.querySelector('.highlight')) { + li.style.display = 'none'; + } + }); + } + }, - // Set parentName. - if (!parentName) { - parentName = ''; - } else { - parentName += ' → '; - } + /** + * Build the command menu. + */ + setMenu() { + Log.debug('Setting up menu'); - html += '
        • '; + let html = ''; - if (child.name) { - // Add the same to buffer. - // - html += '' + parentName + child.name + ''; - } + if (commanderAppOptions.courseid > 0) { + Log.debug('Including course administration menu'); + html += this.renderMenuItems(this.json.courseadmin, ''); + } - if (child.haschildren) { - $.each(child.children, function(i, el) { - html += commanderApp.renderMenuItems(el, depth + 1, parentName + child.name); - }); - } + html += this.renderMenuItems(this.json.admin, ''); - html += '
        • '; - return html; - }, - - /** - * Show the modal - */ - show: function() { - "use strict"; - commanderApp.$mainModal.show(); - commanderApp.$mainModalBackLayer.show(); - - commanderApp.isShow = true; - - // Focus on search field. - commanderApp.$mainModalCommand.focus(); - }, - - /** - * Hide the modal - */ - hide: function() { - commanderApp.$mainModal.hide(); - commanderApp.$mainModalBackLayer.hide(); - - commanderApp.isShow = false; - }, - - /** - * Set 50% of viewport height - */ - setHeight: function() { - var height = Math.round($(window).height() / 2); - commanderApp.$mainModal.height(height); - $('.local_commander-body div').height(height - 100); - }, - - /** - * Remove highlight - * @param {object} node - */ - removeHighlight: function(node) { - $(node).html($(node).text()); + const ulElement = this.mainModal.querySelector('.local_commander-body ul'); + ulElement.innerHTML = html; + + this.liSet = this.mainModal.querySelectorAll('.local_commander-body ul li'); + }, + + /** + * Render menu items recursively. + * @param {object} item + * @param {string} parentName + * @returns {string} + */ + renderMenuItems(item, parentName) { + if (!item.name) { + return ''; } - }; - - return { - - /** - * Called from Moodle. - * @param {array} params - */ - init: function(params) { - - /** - * Set the options. - */ - setOptions(params); - - /** - * Wait for jQuery - */ - $(document).ready(function() { - Log.debug('ready() - local commander v4.4'); - Log.debug(commanderAppOptions); - commanderApp.start(); + + let html = ''; + + const fullName = parentName ? `${parentName} → ${item.name}` : item.name; + html += `
        • ${fullName}
        • `; + + if (item.haschildren) { + item.children.forEach((child) => { + html += this.renderMenuItems(child, fullName); }); } - }; -}); + + return html; + }, + + /** + * Show the modal. + */ + show() { + this.mainModal.style.display = 'block'; + this.mainModalBackLayer.style.display = 'block'; + this.isShow = true; + + // Focus on the search field. + this.mainModalCommand.focus(); + }, + + /** + * Hide the modal. + */ + hide() { + this.mainModal.style.display = 'none'; + this.mainModalBackLayer.style.display = 'none'; + this.isShow = false; + }, + + /** + * Set the modal height to 50% of the viewport. + */ + setHeight() { + const height = Math.round(window.innerHeight / 2); + this.mainModal.style.height = `${height}px`; + const bodyDiv = this.mainModal.querySelector('.local_commander-body div'); + if (bodyDiv) { + bodyDiv.style.height = `${height - 100}px`; + } + }, + + /** + * Remove highlights from text nodes. + * @param {Node} node + */ + removeHighlight(node) { + node.innerHTML = node.textContent; + }, +}; + +/** + * Initialize the module. + * @param {object} params + */ +function init(params) { + // Set the options. + setOptions(params); + + // Wait for the DOM to be fully loaded. + document.addEventListener('DOMContentLoaded', () => { + Log.debug('DOM fully loaded - initializing commanderApp'); + Log.debug(commanderAppOptions); + commanderApp.start(); + }); +} + +export default { + init, +}; diff --git a/amd/src/settings.js b/amd/src/settings.js index 94fb476..18fb68a 100644 --- a/amd/src/settings.js +++ b/amd/src/settings.js @@ -22,59 +22,58 @@ * @copyright 2019 MFreak.nl * @author Luuk Verhoeven **/ -/* eslint-disable no-invalid-this */ -define(['jquery', 'core/str', 'core/notification', 'core/log'], - function($, str, Notification, Log) { - 'use strict'; - /** - * commanderSettings - */ - const commanderSettings = { +import str from 'core/str'; +import Notification from 'core/notification'; +import Log from 'core/log'; - /** - * Init - */ - init: function() { - let $el = $('#id_s_local_commander_keys'); - - if ($el.length === 0) { - return; - } +/** + * Initialize the commander settings. + */ +function init() { + const el = document.getElementById('id_s_local_commander_keys'); - str.get_string('js:keycode_help', 'local_commander').then(function(message) { - $el.before('
          ' + message + '
          '); - return message; - }).fail(Notification.exception); + if (!el) { + return; + } - $(document).on('keydown', function(e) { + str.get_string('js:keycode_help', 'local_commander') + .then((message) => { + el.insertAdjacentHTML('beforebegin', ` +
          + ${message} +
          +
          + `); + }) + .catch(Notification.exception); - if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' - || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) { - Log.debug('Hide when we are in an editable element'); - return; - } + document.addEventListener('keydown', (e) => { + const tagName = e.target.tagName.toUpperCase(); - let keyboardCode = e.keyCode || e.which; - $('#key-monitor div').text('key = ' + e.key + ' | code = ' + keyboardCode); - }); + if ( + tagName === 'INPUT' || + tagName === 'SELECT' || + tagName === 'TEXTAREA' || + e.target.isContentEditable + ) { + Log.debug('Key event ignored in editable element'); + return; } - }; - - return { - /** - * Called from Moodle. - */ - init: function() { - - /** - * Wait for jQuery - */ - $(document).ready(function() { - Log.debug('ready() - setting local commander v4.4'); - commanderSettings.init(); - }); + const keyboardCode = e.keyCode || e.which; + const monitorDiv = document.querySelector('#key-monitor div'); + if (monitorDiv) { + monitorDiv.textContent = `key = ${e.key} | code = ${keyboardCode}`; } - }; + }); +} + +document.addEventListener('DOMContentLoaded', () => { + Log.debug('DOM fully loaded - initializing commander settings'); + init(); }); + +export default { + init, +};