From 742b352229a9bf5a652d94c3239440a64b4fe2d3 Mon Sep 17 00:00:00 2001 From: Laurent David Date: Mon, 7 Apr 2025 11:21:45 +0200 Subject: [PATCH] MDL-81702 core_message: Add focustrap and backdrop * Add focustrap and backdrop to message drawer. --- lib/templates/drawer.mustache | 5 +- message/amd/build/message_drawer.min.js | 2 +- message/amd/build/message_drawer.min.js.map | 2 +- message/amd/build/message_popover.min.js | 2 +- message/amd/build/message_popover.min.js.map | 2 +- message/amd/src/message_drawer.js | 79 ++++++++++++++++++- message/amd/src/message_popover.js | 1 - message/templates/message_drawer.mustache | 10 ++- .../tests/behat/message_send_messages.feature | 2 +- 9 files changed, 92 insertions(+), 13 deletions(-) diff --git a/lib/templates/drawer.mustache b/lib/templates/drawer.mustache index 82ecf775c45..2c2d9f3a395 100644 --- a/lib/templates/drawer.mustache +++ b/lib/templates/drawer.mustache @@ -36,8 +36,9 @@ class="{{$drawerclasses}}{{/drawerclasses}} drawer bg-white {{^show}}hidden{{/show}}" {{^show}}aria-hidden="true"{{/show}} data-region="right-hand-drawer" - role="region" - tabindex="0" + role="{{$role}}region{{/role}}" + tabindex="{{$tabindex}}0{{/tabindex}}" + {{$drawerattributes}}{{/drawerattributes}} > {{$drawercontent}}{{/drawercontent}} diff --git a/message/amd/build/message_drawer.min.js b/message/amd/build/message_drawer.min.js index a6132abd0f1..037f09d6237 100644 --- a/message/amd/build/message_drawer.min.js +++ b/message/amd/build/message_drawer.min.js @@ -5,6 +5,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core_message/message_drawer",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_view_contact","core_message/message_drawer_view_contacts","core_message/message_drawer_view_conversation","core_message/message_drawer_view_group_info","core_message/message_drawer_view_overview","core_message/message_drawer_view_search","core_message/message_drawer_view_settings","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_events","core_message/message_drawer_helper","core/pending","core/drawer","core/toast","core/str","core/config","core/ajax"],(function($,CustomEvents,PubSub,ViewContact,ViewContacts,ViewConversation,ViewGroupInfo,ViewOverview,ViewSearch,ViewSettings,Router,Routes,Events,Helper,Pending,Drawer,Toast,Str,Config,Ajax){var SELECTORS_DRAWER='[data-region="right-hand-drawer"]',SELECTORS_JUMPTO='.popover-region [data-region="jumpto"]',SELECTORS_PANEL_BODY_CONTAINER='[data-region="panel-body-container"]',SELECTORS_PANEL_HEADER_CONTAINER='[data-region="panel-header-container"]',SELECTORS_VIEW_CONTACT='[data-region="view-contact"]',SELECTORS_VIEW_CONTACTS='[data-region="view-contacts"]',SELECTORS_VIEW_CONVERSATION='[data-region="view-conversation"]',SELECTORS_VIEW_CONVERSATION_WITH_ID='[data-region="view-conversation"][data-conversation-id]',SELECTORS_VIEW_CONVERSATION_WITH_USER='[data-region="view-conversation"][data-other-user-id]',SELECTORS_VIEW_GROUP_INFO='[data-region="view-group-info"]',SELECTORS_VIEW_OVERVIEW='[data-region="view-overview"]',SELECTORS_VIEW_SEARCH='[data-region="view-search"]',SELECTORS_VIEW_SETTINGS='[data-region="view-settings"]',SELECTORS_ROUTES="[data-route]",SELECTORS_ROUTES_BACK="[data-route-back]",SELECTORS_HEADER_CONTAINER='[data-region="header-container"]',SELECTORS_BODY_CONTAINER='[data-region="body-container"]',SELECTORS_FOOTER_CONTAINER='[data-region="footer-container"]',SELECTORS_CLOSE_BUTTON='[data-action="closedrawer"]',SELECTORS_MESSAGE_INDEX='[data-region="message-index"]',SELECTORS_MESSAGE_TEXT_AREA='[data-region="send-message-txt"]',routes=[[Routes.VIEW_CONTACT,SELECTORS_VIEW_CONTACT,ViewContact.show,ViewContact.description],[Routes.VIEW_CONTACTS,SELECTORS_VIEW_CONTACTS,ViewContacts.show,ViewContacts.description],[Routes.VIEW_CONVERSATION,SELECTORS_VIEW_CONVERSATION,ViewConversation.show,ViewConversation.description],[Routes.VIEW_GROUP_INFO,SELECTORS_VIEW_GROUP_INFO,ViewGroupInfo.show,ViewGroupInfo.description],[Routes.VIEW_OVERVIEW,SELECTORS_VIEW_OVERVIEW,ViewOverview.show,ViewOverview.description],[Routes.VIEW_SEARCH,SELECTORS_VIEW_SEARCH,ViewSearch.show,ViewSearch.description],[Routes.VIEW_SETTINGS,SELECTORS_VIEW_SETTINGS,ViewSettings.show,ViewSettings.description]],createRoutes=function(namespace,root){routes.forEach((function(route){Router.add(namespace,route[0],function(namespace,root,selector){var header=root.find(SELECTORS_HEADER_CONTAINER).find(selector);header.length||(header=root.find(SELECTORS_PANEL_HEADER_CONTAINER).find(selector));var body=root.find(SELECTORS_BODY_CONTAINER).find(selector);body.length||(body=root.find(SELECTORS_PANEL_BODY_CONTAINER).find(selector));var footer=root.find(SELECTORS_FOOTER_CONTAINER).find(selector);return[namespace,header.length?header:null,body.length?body:null,footer.length?footer:null]}(namespace,root,route[1]),route[2],route[3])}))},show=function(namespace,root){root.attr("data-shown")||(Router.go(namespace,Routes.VIEW_OVERVIEW),root.attr("data-shown",!0));var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&Drawer.show(drawerRoot)},hide=function(root){var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&Drawer.hide(drawerRoot)},setJumpFrom=function(buttonid){$(SELECTORS_DRAWER).attr("data-origin",buttonid)};const storeUnsentMessage=async()=>{const messageTextArea=document.querySelector(SELECTORS_MESSAGE_TEXT_AREA);if(messageTextArea.value.trim().length>0&&!messageTextArea.hasAttribute("data-unsent-message-viewed")){let message=messageTextArea.value,conversationid=0,otheruserid=0;if(document.querySelector(SELECTORS_VIEW_CONVERSATION_WITH_ID)){conversationid=messageTextArea.closest(SELECTORS_VIEW_CONVERSATION_WITH_ID).getAttribute("data-conversation-id")}if(document.querySelector(SELECTORS_VIEW_CONVERSATION_WITH_USER)){otheruserid=messageTextArea.closest(SELECTORS_VIEW_CONVERSATION_WITH_USER).getAttribute("data-other-user-id")}setStoredUnsentMessage(message,conversationid,otheruserid)}},setStoredUnsentMessage=(message,conversationid,otheruserid)=>{const method="core_message_set_unsent_message",requestUrl=new URL("".concat(Config.wwwroot,"/lib/ajax/service.php"));requestUrl.searchParams.set("sesskey",Config.sesskey),requestUrl.searchParams.set("info",method),navigator.sendBeacon(requestUrl,JSON.stringify([{index:0,methodname:method,args:{message:message,conversationid:conversationid,otheruserid:otheruserid}}]))},getUnsentMessage=async(uniqueId,root)=>{let type,messageRoot;if(null!==document.querySelector(SELECTORS_MESSAGE_INDEX)){if(type="index",messageRoot=document.getElementById("message-index-".concat(uniqueId)),!messageRoot)return}else type="drawer",messageRoot=document.getElementById("message-drawer-".concat(uniqueId));const storedMessage=await Ajax.call([{methodname:"core_message_get_unsent_message",args:{}}])[0],messageTextArea=messageRoot.querySelector(SELECTORS_MESSAGE_TEXT_AREA);storedMessage.message&&null!==messageTextArea&&showUnsentMessage(messageTextArea,storedMessage,type,uniqueId,root)},showUnsentMessage=(textArea,stored,type,uniqueId,root)=>{if(!textArea.hasAttribute("data-unsent-message-viewed")){switch(type){case"index":stored.conversationid?Router.go(uniqueId,Routes.VIEW_CONVERSATION,stored.conversationid,"frompanel"):stored.otheruserid&&Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid);break;case"drawer":if(stored.conversationid){let args={conversationid:stored.conversationid};Helper.showConversation(args)}else stored.otheruserid&&(show(uniqueId,root),Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid))}textArea.value=stored.message,textArea.setAttribute("data-unsent-message-viewed",1),Toast.add(Str.get_string("unsentmessagenotification","core_message"))}};var registerEventListeners=function(namespace,root,alwaysVisible){CustomEvents.define(root,[CustomEvents.events.activate]);var paramRegex=/^data-route-param-?(\d*)$/;root.on(CustomEvents.events.activate,SELECTORS_ROUTES,(function(e,data){for(var element=$(e.target).closest(SELECTORS_ROUTES),route=element.attr("data-route"),attributes=[],i=0;i1?aParts[1]:0,bIndex=bParts.length>1?bParts[1]:0;return aIndex{collapse.addEventListener("hide.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("hidden.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),root[0].querySelectorAll(".collapse").forEach((collapse=>{collapse.addEventListener("show.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("shown.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),$(SELECTORS_JUMPTO).focus((function(){var firstInput=root.find(SELECTORS_CLOSE_BUTTON);firstInput.length?firstInput.focus():$(SELECTORS_HEADER_CONTAINER).find(SELECTORS_ROUTES_BACK).focus()})),$(SELECTORS_DRAWER).focus((function(){var button=$(this).attr("data-origin");button&&$("#"+button).focus()})),alwaysVisible||(PubSub.subscribe(Events.SHOW,(function(){show(namespace,root)})),PubSub.subscribe(Events.HIDE,(function(){hide(root)})),PubSub.subscribe(Events.TOGGLE_VISIBILITY,(function(buttonid){const buttonElement=document.getElementById(buttonid);!function(root){var drawerRoot=Drawer.getDrawerRoot(root);return!drawerRoot.length||Drawer.isVisible(drawerRoot)}(root)?(show(namespace,root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!0),setJumpFrom(buttonid),$(SELECTORS_JUMPTO).attr("tabindex",0)):(hide(root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!1),$(SELECTORS_JUMPTO).attr("tabindex",-1))}))),PubSub.subscribe(Events.SHOW_CONVERSATION,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,args.conversationid)})),root.find(SELECTORS_CLOSE_BUTTON).on(CustomEvents.events.activate,(function(e,data){data.originalEvent.preventDefault();var button=$(SELECTORS_DRAWER).attr("data-origin");button&&$("#"+button).focus(),PubSub.publish(Events.TOGGLE_VISIBILITY,button)})),PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,null,"create",args.userid)})),PubSub.subscribe(Events.SHOW_SETTINGS,(function(){show(namespace,root),Router.go(namespace,Routes.VIEW_SETTINGS)})),PubSub.subscribe(Events.PREFERENCES_UPDATED,(function(preferences){var filteredPreferences=preferences.filter((function(preference){return"message_entertosend"==preference.type})),enterToSendPreference=filteredPreferences.length?filteredPreferences[0]:null;enterToSendPreference&&root.find(SELECTORS_FOOTER_CONTAINER).find(SELECTORS_VIEW_CONVERSATION).attr("data-enter-to-send",enterToSendPreference.value)}));const textArea=document.querySelector(SELECTORS_MESSAGE_TEXT_AREA);textArea&&textArea.addEventListener("keyup",(function(){textArea.removeAttribute("data-unsent-message-viewed")})),window.addEventListener("beforeunload",storeUnsentMessage)};return{init:function(root,uniqueId,alwaysVisible,route){if(root=$(root),createRoutes(uniqueId,root),registerEventListeners(uniqueId,root,alwaysVisible),alwaysVisible&&(show(uniqueId,root),route)){var routeParams=route.params||[];routeParams=[uniqueId,route.path].concat(routeParams),Router.go.apply(null,routeParams)}Helper.markDrawerReady(),getUnsentMessage(uniqueId,root)}}})); +define("core_message/message_drawer",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_view_contact","core_message/message_drawer_view_contacts","core_message/message_drawer_view_conversation","core_message/message_drawer_view_group_info","core_message/message_drawer_view_overview","core_message/message_drawer_view_search","core_message/message_drawer_view_settings","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_events","core_message/message_drawer_helper","core/pending","core/drawer","core/toast","core/str","core/config","core/ajax","core/local/aria/focuslock","core/modal_backdrop","core/templates"],(function($,CustomEvents,PubSub,ViewContact,ViewContacts,ViewConversation,ViewGroupInfo,ViewOverview,ViewSearch,ViewSettings,Router,Routes,Events,Helper,Pending,Drawer,Toast,Str,Config,Ajax,FocusLock,ModalBackdrop,Templates){var SELECTORS={DRAWER:'[data-region="right-hand-drawer"]',PANEL_BODY_CONTAINER:'[data-region="panel-body-container"]',PANEL_HEADER_CONTAINER:'[data-region="panel-header-container"]',VIEW_CONTACT:'[data-region="view-contact"]',VIEW_CONTACTS:'[data-region="view-contacts"]',VIEW_CONVERSATION:'[data-region="view-conversation"]',VIEW_CONVERSATION_WITH_ID:'[data-region="view-conversation"][data-conversation-id]',VIEW_CONVERSATION_WITH_USER:'[data-region="view-conversation"][data-other-user-id]',VIEW_GROUP_INFO:'[data-region="view-group-info"]',VIEW_OVERVIEW:'[data-region="view-overview"]',VIEW_SEARCH:'[data-region="view-search"]',VIEW_SETTINGS:'[data-region="view-settings"]',ROUTES:"[data-route]",ROUTES_BACK:"[data-route-back]",HEADER_CONTAINER:'[data-region="header-container"]',BODY_CONTAINER:'[data-region="body-container"]',FOOTER_CONTAINER:'[data-region="footer-container"]',CLOSE_BUTTON:'[data-action="closedrawer"]',MESSAGE_INDEX:'[data-region="message-index"]',MESSAGE_TEXT_AREA:'[data-region="send-message-txt"]'},routes=[[Routes.VIEW_CONTACT,SELECTORS.VIEW_CONTACT,ViewContact.show,ViewContact.description],[Routes.VIEW_CONTACTS,SELECTORS.VIEW_CONTACTS,ViewContacts.show,ViewContacts.description],[Routes.VIEW_CONVERSATION,SELECTORS.VIEW_CONVERSATION,ViewConversation.show,ViewConversation.description],[Routes.VIEW_GROUP_INFO,SELECTORS.VIEW_GROUP_INFO,ViewGroupInfo.show,ViewGroupInfo.description],[Routes.VIEW_OVERVIEW,SELECTORS.VIEW_OVERVIEW,ViewOverview.show,ViewOverview.description],[Routes.VIEW_SEARCH,SELECTORS.VIEW_SEARCH,ViewSearch.show,ViewSearch.description],[Routes.VIEW_SETTINGS,SELECTORS.VIEW_SETTINGS,ViewSettings.show,ViewSettings.description]],createRoutes=function(namespace,root){routes.forEach((function(route){Router.add(namespace,route[0],function(namespace,root,selector){var header=root.find(SELECTORS.HEADER_CONTAINER).find(selector);header.length||(header=root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector));var body=root.find(SELECTORS.BODY_CONTAINER).find(selector);body.length||(body=root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector));var footer=root.find(SELECTORS.FOOTER_CONTAINER).find(selector);return[namespace,header.length?header:null,body.length?body:null,footer.length?footer:null]}(namespace,root,route[1]),route[2],route[3])}))};let backdropPromise=null;var setFocus=function(root,hasFocus){var drawerRoot=Drawer.getDrawerRoot(root);if(!drawerRoot.length)return;backdropPromise||(backdropPromise=Templates.render("core/modal_backdrop",{}).then((html=>new ModalBackdrop(html))));const backdropWithAdjustments=backdropPromise.then((modalBackdrop=>{const messageDrawerZIndex=window.getComputedStyle(drawerRoot[0]).zIndex;return messageDrawerZIndex&&modalBackdrop.setZIndex(messageDrawerZIndex-1),modalBackdrop.getAttachmentPoint().get(0).addEventListener("click",(e=>{PubSub.publish(Events.HIDE,{}),e.preventDefault()})),modalBackdrop}));hasFocus?(FocusLock.trapFocus(root[0]),backdropWithAdjustments.then((modalBackdrop=>{if(modalBackdrop){modalBackdrop.show();document.getElementById("page").style.overflow="hidden";var closeButton=root.find(SELECTORS.CLOSE_BUTTON);closeButton.length&&closeButton.focus()}return modalBackdrop}))):backdropWithAdjustments.then((modalBackdrop=>{if(modalBackdrop){FocusLock.untrapFocus();var button=$(SELECTORS.DRAWER).attr("data-origin");button&&$("#"+button).focus(),modalBackdrop.hide();document.getElementById("page").style.overflow="visible"}return modalBackdrop}))},show=function(namespace,root){root.attr("data-shown")||(Router.go(namespace,Routes.VIEW_OVERVIEW),root.attr("data-shown",!0));var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&(setFocus(root,!0),Drawer.show(drawerRoot))},hide=function(root){var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&(setFocus(root,!1),Drawer.hide(drawerRoot))},setJumpFrom=function(buttonid){$(SELECTORS.DRAWER).attr("data-origin",buttonid)};const storeUnsentMessage=async()=>{const messageTextArea=document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);if(messageTextArea.value.trim().length>0&&!messageTextArea.hasAttribute("data-unsent-message-viewed")){let message=messageTextArea.value,conversationid=0,otheruserid=0;if(document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID)){conversationid=messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID).getAttribute("data-conversation-id")}if(document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER)){otheruserid=messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER).getAttribute("data-other-user-id")}setStoredUnsentMessage(message,conversationid,otheruserid)}},setStoredUnsentMessage=(message,conversationid,otheruserid)=>{const method="core_message_set_unsent_message",requestUrl=new URL("".concat(Config.wwwroot,"/lib/ajax/service.php"));requestUrl.searchParams.set("sesskey",Config.sesskey),requestUrl.searchParams.set("info",method),navigator.sendBeacon(requestUrl,JSON.stringify([{index:0,methodname:method,args:{message:message,conversationid:conversationid,otheruserid:otheruserid}}]))},getUnsentMessage=async(uniqueId,root)=>{let type,messageRoot;if(null!==document.querySelector(SELECTORS.MESSAGE_INDEX)){if(type="index",messageRoot=document.getElementById("message-index-".concat(uniqueId)),!messageRoot)return}else type="drawer",messageRoot=document.getElementById("message-drawer-".concat(uniqueId));const storedMessage=await Ajax.call([{methodname:"core_message_get_unsent_message",args:{}}])[0],messageTextArea=messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);storedMessage.message&&null!==messageTextArea&&showUnsentMessage(messageTextArea,storedMessage,type,uniqueId,root)},showUnsentMessage=(textArea,stored,type,uniqueId,root)=>{if(!textArea.hasAttribute("data-unsent-message-viewed")){switch(type){case"index":stored.conversationid?Router.go(uniqueId,Routes.VIEW_CONVERSATION,stored.conversationid,"frompanel"):stored.otheruserid&&Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid);break;case"drawer":if(stored.conversationid){let args={conversationid:stored.conversationid};Helper.showConversation(args)}else stored.otheruserid&&(show(uniqueId,root),Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid))}textArea.value=stored.message,textArea.setAttribute("data-unsent-message-viewed",1),Toast.add(Str.get_string("unsentmessagenotification","core_message"))}};var registerEventListeners=function(namespace,root,alwaysVisible){CustomEvents.define(root,[CustomEvents.events.activate,CustomEvents.events.escape]);var paramRegex=/^data-route-param-?(\d*)$/;root.on(CustomEvents.events.activate,SELECTORS.ROUTES,(function(e,data){for(var element=$(e.target).closest(SELECTORS.ROUTES),route=element.attr("data-route"),attributes=[],i=0;i1?aParts[1]:0,bIndex=bParts.length>1?bParts[1]:0;return aIndex{collapse.addEventListener("hide.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("hidden.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),root[0].querySelectorAll(".collapse").forEach((collapse=>{collapse.addEventListener("show.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("shown.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),$(SELECTORS.JUMPTO).focus((function(){var firstInput=root.find(SELECTORS.CLOSE_BUTTON);firstInput.length?firstInput.focus():$(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus()})),$(SELECTORS.DRAWER).focus((function(){var button=$(this).attr("data-origin");button&&$("#"+button).focus()})),alwaysVisible||(PubSub.subscribe(Events.SHOW,(function(){show(namespace,root)})),PubSub.subscribe(Events.HIDE,(function(){hide(root)})),PubSub.subscribe(Events.TOGGLE_VISIBILITY,(function(buttonid){const buttonElement=document.getElementById(buttonid);!function(root){var drawerRoot=Drawer.getDrawerRoot(root);return!drawerRoot.length||Drawer.isVisible(drawerRoot)}(root)?(show(namespace,root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!0),setJumpFrom(buttonid),$(SELECTORS.JUMPTO).attr("tabindex",0)):(hide(root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!1),$(SELECTORS.JUMPTO).attr("tabindex",-1))})),root.on(CustomEvents.events.escape,(function(){PubSub.publish(Events.HIDE,{})}))),PubSub.subscribe(Events.SHOW_CONVERSATION,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,args.conversationid)})),root.find(SELECTORS.CLOSE_BUTTON).on(CustomEvents.events.activate,(function(e,data){data.originalEvent.preventDefault(),setFocus(root,!1);var button=$(SELECTORS.DRAWER).attr("data-origin");button&&$("#"+button).focus(),PubSub.publish(Events.TOGGLE_VISIBILITY,button)})),PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,null,"create",args.userid)})),PubSub.subscribe(Events.SHOW_SETTINGS,(function(){show(namespace,root),Router.go(namespace,Routes.VIEW_SETTINGS)})),PubSub.subscribe(Events.PREFERENCES_UPDATED,(function(preferences){var filteredPreferences=preferences.filter((function(preference){return"message_entertosend"==preference.type})),enterToSendPreference=filteredPreferences.length?filteredPreferences[0]:null;enterToSendPreference&&root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION).attr("data-enter-to-send",enterToSendPreference.value)}));const textArea=document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);textArea&&textArea.addEventListener("keyup",(function(){textArea.removeAttribute("data-unsent-message-viewed")})),window.addEventListener("beforeunload",storeUnsentMessage)};return{init:function(root,uniqueId,alwaysVisible,route){if(root=$(root),createRoutes(uniqueId,root),registerEventListeners(uniqueId,root,alwaysVisible),alwaysVisible&&(show(uniqueId,root),route)){var routeParams=route.params||[];routeParams=[uniqueId,route.path].concat(routeParams),Router.go.apply(null,routeParams)}Helper.markDrawerReady(),getUnsentMessage(uniqueId,root)}}})); //# sourceMappingURL=message_drawer.min.js.map \ No newline at end of file diff --git a/message/amd/build/message_drawer.min.js.map b/message/amd/build/message_drawer.min.js.map index adbf71178e6..a4f6251f355 100644 --- a/message/amd/build/message_drawer.min.js.map +++ b/message/amd/build/message_drawer.min.js.map @@ -1 +1 @@ -{"version":3,"file":"message_drawer.min.js","sources":["../src/message_drawer.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 * Controls the message drawer.\n *\n * @module core_message/message_drawer\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_view_contact',\n 'core_message/message_drawer_view_contacts',\n 'core_message/message_drawer_view_conversation',\n 'core_message/message_drawer_view_group_info',\n 'core_message/message_drawer_view_overview',\n 'core_message/message_drawer_view_search',\n 'core_message/message_drawer_view_settings',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_helper',\n 'core/pending',\n 'core/drawer',\n 'core/toast',\n 'core/str',\n 'core/config',\n 'core/ajax',\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n ViewContact,\n ViewContacts,\n ViewConversation,\n ViewGroupInfo,\n ViewOverview,\n ViewSearch,\n ViewSettings,\n Router,\n Routes,\n Events,\n Helper,\n Pending,\n Drawer,\n Toast,\n Str,\n Config,\n Ajax,\n) {\n\n var SELECTORS = {\n DRAWER: '[data-region=\"right-hand-drawer\"]',\n JUMPTO: '.popover-region [data-region=\"jumpto\"]',\n PANEL_BODY_CONTAINER: '[data-region=\"panel-body-container\"]',\n PANEL_HEADER_CONTAINER: '[data-region=\"panel-header-container\"]',\n VIEW_CONTACT: '[data-region=\"view-contact\"]',\n VIEW_CONTACTS: '[data-region=\"view-contacts\"]',\n VIEW_CONVERSATION: '[data-region=\"view-conversation\"]',\n VIEW_CONVERSATION_WITH_ID: '[data-region=\"view-conversation\"][data-conversation-id]',\n VIEW_CONVERSATION_WITH_USER: '[data-region=\"view-conversation\"][data-other-user-id]',\n VIEW_GROUP_INFO: '[data-region=\"view-group-info\"]',\n VIEW_OVERVIEW: '[data-region=\"view-overview\"]',\n VIEW_SEARCH: '[data-region=\"view-search\"]',\n VIEW_SETTINGS: '[data-region=\"view-settings\"]',\n ROUTES: '[data-route]',\n ROUTES_BACK: '[data-route-back]',\n HEADER_CONTAINER: '[data-region=\"header-container\"]',\n BODY_CONTAINER: '[data-region=\"body-container\"]',\n FOOTER_CONTAINER: '[data-region=\"footer-container\"]',\n CLOSE_BUTTON: '[data-action=\"closedrawer\"]',\n MESSAGE_INDEX: '[data-region=\"message-index\"]',\n MESSAGE_TEXT_AREA: '[data-region=\"send-message-txt\"]',\n };\n\n /**\n * Get elements for route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n * @param {string} selector The route container.\n *\n * @return {array} elements Found route container objects.\n */\n var getParametersForRoute = function(namespace, root, selector) {\n\n var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);\n if (!header.length) {\n header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);\n }\n var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);\n if (!body.length) {\n body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector);\n }\n var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector);\n\n return [\n namespace,\n header.length ? header : null,\n body.length ? body : null,\n footer.length ? footer : null\n ];\n };\n\n var routes = [\n [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description],\n [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description],\n [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description],\n [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description],\n [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description],\n [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description],\n [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description]\n ];\n\n /**\n * Create routes.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n */\n var createRoutes = function(namespace, root) {\n routes.forEach(function(route) {\n Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);\n });\n };\n\n /**\n * Show the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n */\n var show = function(namespace, root) {\n if (!root.attr('data-shown')) {\n Router.go(namespace, Routes.VIEW_OVERVIEW);\n root.attr('data-shown', true);\n }\n\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n Drawer.show(drawerRoot);\n }\n };\n\n /**\n * Hide the message drawer.\n *\n * @param {Object} root The message drawer container.\n */\n var hide = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n Drawer.hide(drawerRoot);\n }\n };\n\n /**\n * Check if the drawer is visible.\n *\n * @param {Object} root The message drawer container.\n * @return {boolean}\n */\n var isVisible = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n return Drawer.isVisible(drawerRoot);\n }\n return true;\n };\n\n /**\n * Set Jump from button\n *\n * @param {String} buttonid The originating button id\n */\n var setJumpFrom = function(buttonid) {\n $(SELECTORS.DRAWER).attr('data-origin', buttonid);\n };\n\n /**\n * Store an unsent message.\n *\n * Don't store this if the user has already seen the unsent message.\n * This avoids spamming and ensures the user is only reminded once per unsent message.\n * If the unsent message is sent, this attribute is removed and notification is possible again (see sendMessage).\n */\n const storeUnsentMessage = async() => {\n const messageTextArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n\n if (messageTextArea.value.trim().length > 0 && !messageTextArea.hasAttribute('data-unsent-message-viewed')) {\n\n let message = messageTextArea.value;\n let conversationid = 0;\n let otheruserid = 0;\n\n // We don't always have a conversation to link the unsent message to, so let's check for that.\n const conversationId = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n if (conversationId) {\n const conversationWithId = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n conversationid = conversationWithId.getAttribute('data-conversation-id');\n }\n // Store the 'other' user id if it is there. This can be used to create conversations.\n const conversationUser = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n if (conversationUser) {\n const conversationWithUser = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n otheruserid = conversationWithUser.getAttribute('data-other-user-id');\n }\n\n setStoredUnsentMessage(message, conversationid, otheruserid);\n }\n };\n\n /**\n * Get the stored unsent message from the session via web service.\n *\n * @returns {Promise}\n */\n const getStoredUnsentMessage = () => Ajax.call([{\n methodname: 'core_message_get_unsent_message',\n args: {}\n }])[0];\n\n /**\n * Set the unsent message value in the session via web service.\n *\n * SendBeacon is used here because this is called on 'beforeunload'.\n *\n * @param {string} message The message string.\n * @param {number} conversationid The conversation id.\n * @param {number} otheruserid The other user id.\n * @returns {Promise}\n */\n const setStoredUnsentMessage = (message, conversationid, otheruserid) => {\n const method = 'core_message_set_unsent_message';\n const requestUrl = new URL(`${Config.wwwroot}/lib/ajax/service.php`);\n requestUrl.searchParams.set('sesskey', Config.sesskey);\n requestUrl.searchParams.set('info', method);\n\n navigator.sendBeacon(requestUrl, JSON.stringify([{\n index: 0,\n methodname: method,\n args: {\n message: message,\n conversationid: conversationid,\n otheruserid: otheruserid,\n }\n }]));\n };\n\n /**\n * Check for an unsent message.\n *\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const getUnsentMessage = async(uniqueId, root) => {\n let type;\n let messageRoot;\n\n // We need to check if we are on the message/index page.\n // This logic is needed to handle the two message widgets here and ensure we are targetting the right one.\n const messageIndex = document.querySelector(SELECTORS.MESSAGE_INDEX);\n if (messageIndex !== null) {\n type = 'index';\n messageRoot = document.getElementById(`message-index-${uniqueId}`);\n if (!messageRoot) {\n // This is not the correct widget.\n return;\n }\n\n } else {\n type = 'drawer';\n messageRoot = document.getElementById(`message-drawer-${uniqueId}`);\n }\n\n const storedMessage = await getStoredUnsentMessage();\n const messageTextArea = messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (storedMessage.message && messageTextArea !== null) {\n showUnsentMessage(messageTextArea, storedMessage, type, uniqueId, root);\n }\n };\n\n /**\n * Show an unsent message.\n *\n * There are two message widgets on the message/index page.\n * Because of that, we need to try and target the correct widget.\n *\n * @param {String} textArea The textarea element.\n * @param {Object} stored The stored message content.\n * @param {String} type Is this from the drawer or index page?\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const showUnsentMessage = (textArea, stored, type, uniqueId, root) => {\n // The user has already been notified.\n if (textArea.hasAttribute('data-unsent-message-viewed')) {\n return;\n }\n\n // Depending on the type, show the conversation with the data we have available.\n // A conversation can be continued if there is a conversationid.\n // If the user was messaging a new non-contact, we won't have a conversationid yet.\n // In that case, we use the otheruserid value to start a conversation with them.\n switch (type) {\n case 'index':\n // Show the conversation in the main panel on the message/index page.\n if (stored.conversationid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, stored.conversationid, 'frompanel');\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n\n case 'drawer':\n // Open the drawer and show the conversation.\n if (stored.conversationid) {\n let args = {\n conversationid: stored.conversationid\n };\n Helper.showConversation(args);\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n show(uniqueId, root);\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n }\n\n // Populate the text area.\n textArea.value = stored.message;\n textArea.setAttribute('data-unsent-message-viewed', 1);\n\n // Notify the user.\n Toast.add(Str.get_string('unsentmessagenotification', 'core_message'));\n };\n\n /**\n * Listen to and handle events for routing, showing and hiding the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n * @param {bool} alwaysVisible Is this messaging app always shown?\n */\n var registerEventListeners = function(namespace, root, alwaysVisible) {\n CustomEvents.define(root, [CustomEvents.events.activate]);\n var paramRegex = /^data-route-param-?(\\d*)$/;\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) {\n var element = $(e.target).closest(SELECTORS.ROUTES);\n var route = element.attr('data-route');\n var attributes = [];\n\n for (var i = 0; i < element[0].attributes.length; i++) {\n attributes.push(element[0].attributes[i]);\n }\n\n var paramAttributes = attributes.filter(function(attribute) {\n var name = attribute.nodeName;\n var match = paramRegex.test(name);\n return match;\n });\n paramAttributes.sort(function(a, b) {\n var aParts = paramRegex.exec(a.nodeName);\n var bParts = paramRegex.exec(b.nodeName);\n var aIndex = aParts.length > 1 ? aParts[1] : 0;\n var bIndex = bParts.length > 1 ? bParts[1] : 0;\n\n if (aIndex < bIndex) {\n return -1;\n } else if (bIndex < aIndex) {\n return 1;\n } else {\n return 0;\n }\n });\n\n var params = paramAttributes.map(function(attribute) {\n return attribute.nodeValue;\n });\n\n var routeParams = [namespace, route].concat(params);\n\n Router.go.apply(null, routeParams);\n\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {\n Router.back(namespace);\n\n data.originalEvent.preventDefault();\n });\n\n // These are theme-specific to help us fix random behat fails.\n // These events target those events defined in BS3 and BS4 onwards.\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('hide.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('hidden.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('show.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('shown.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n $(SELECTORS.JUMPTO).focus(function() {\n var firstInput = root.find(SELECTORS.CLOSE_BUTTON);\n if (firstInput.length) {\n firstInput.focus();\n } else {\n $(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus();\n }\n });\n\n $(SELECTORS.DRAWER).focus(function() {\n var button = $(this).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n });\n\n if (!alwaysVisible) {\n PubSub.subscribe(Events.SHOW, function() {\n show(namespace, root);\n });\n\n PubSub.subscribe(Events.HIDE, function() {\n hide(root);\n });\n\n PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) {\n const buttonElement = document.getElementById(buttonid);\n if (isVisible(root)) {\n hide(root);\n buttonElement?.setAttribute('aria-expanded', false);\n $(SELECTORS.JUMPTO).attr('tabindex', -1);\n } else {\n show(namespace, root);\n buttonElement?.setAttribute('aria-expanded', true);\n setJumpFrom(buttonid);\n $(SELECTORS.JUMPTO).attr('tabindex', 0);\n }\n });\n }\n\n PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid);\n });\n\n var closebutton = root.find(SELECTORS.CLOSE_BUTTON);\n closebutton.on(CustomEvents.events.activate, function(e, data) {\n data.originalEvent.preventDefault();\n\n var button = $(SELECTORS.DRAWER).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n PubSub.publish(Events.TOGGLE_VISIBILITY, button);\n });\n\n PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid);\n });\n\n PubSub.subscribe(Events.SHOW_SETTINGS, function() {\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_SETTINGS);\n });\n\n PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) {\n var filteredPreferences = preferences.filter(function(preference) {\n return preference.type == 'message_entertosend';\n });\n var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;\n\n if (enterToSendPreference) {\n var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);\n viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);\n }\n });\n\n // If our textarea is modified, remove the attribute which indicates the user has seen the unsent message notification.\n // This will allow the user to be notified again.\n const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (textArea) {\n textArea.addEventListener('keyup', function() {\n textArea.removeAttribute('data-unsent-message-viewed');\n });\n }\n\n // Catch any unsent messages and store them.\n window.addEventListener('beforeunload', storeUnsentMessage);\n };\n\n /**\n * Initialise the message drawer.\n *\n * @param {Object} root The message drawer container.\n * @param {String} uniqueId Unique identifier for the Routes\n * @param {bool} alwaysVisible Should we show the app now, or wait for the user?\n * @param {Object} route\n */\n var init = function(root, uniqueId, alwaysVisible, route) {\n root = $(root);\n createRoutes(uniqueId, root);\n registerEventListeners(uniqueId, root, alwaysVisible);\n\n if (alwaysVisible) {\n show(uniqueId, root);\n\n if (route) {\n var routeParams = route.params || [];\n routeParams = [uniqueId, route.path].concat(routeParams);\n Router.go.apply(null, routeParams);\n }\n }\n\n // Mark the drawer as ready.\n Helper.markDrawerReady();\n\n // Get and show any unsent message.\n getUnsentMessage(uniqueId, root);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","ViewContact","ViewContacts","ViewConversation","ViewGroupInfo","ViewOverview","ViewSearch","ViewSettings","Router","Routes","Events","Helper","Pending","Drawer","Toast","Str","Config","Ajax","SELECTORS","routes","VIEW_CONTACT","show","description","VIEW_CONTACTS","VIEW_CONVERSATION","VIEW_GROUP_INFO","VIEW_OVERVIEW","VIEW_SEARCH","VIEW_SETTINGS","createRoutes","namespace","root","forEach","route","add","selector","header","find","length","body","footer","getParametersForRoute","attr","go","drawerRoot","getDrawerRoot","hide","setJumpFrom","buttonid","storeUnsentMessage","async","messageTextArea","document","querySelector","value","trim","hasAttribute","message","conversationid","otheruserid","closest","getAttribute","setStoredUnsentMessage","method","requestUrl","URL","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index","methodname","args","getUnsentMessage","uniqueId","type","messageRoot","getElementById","storedMessage","call","showUnsentMessage","textArea","stored","showConversation","setAttribute","get_string","registerEventListeners","alwaysVisible","events","activate","paramRegex","on","e","data","element","target","attributes","i","push","paramAttributes","filter","attribute","name","nodeName","test","sort","a","b","aParts","exec","bParts","aIndex","bIndex","params","map","nodeValue","routeParams","concat","apply","originalEvent","preventDefault","back","querySelectorAll","collapse","addEventListener","pendingPromise","resolve","once","focus","firstInput","button","this","subscribe","SHOW","HIDE","TOGGLE_VISIBILITY","buttonElement","isVisible","SHOW_CONVERSATION","publish","CREATE_CONVERSATION_WITH_USER","userid","SHOW_SETTINGS","PREFERENCES_UPDATED","preferences","filteredPreferences","preference","enterToSendPreference","removeAttribute","window","init","path","markDrawerReady"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"message_drawer.min.js","sources":["../src/message_drawer.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 * Controls the message drawer.\n *\n * @module core_message/message_drawer\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_view_contact',\n 'core_message/message_drawer_view_contacts',\n 'core_message/message_drawer_view_conversation',\n 'core_message/message_drawer_view_group_info',\n 'core_message/message_drawer_view_overview',\n 'core_message/message_drawer_view_search',\n 'core_message/message_drawer_view_settings',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_helper',\n 'core/pending',\n 'core/drawer',\n 'core/toast',\n 'core/str',\n 'core/config',\n 'core/ajax',\n 'core/local/aria/focuslock',\n 'core/modal_backdrop',\n 'core/templates',\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n ViewContact,\n ViewContacts,\n ViewConversation,\n ViewGroupInfo,\n ViewOverview,\n ViewSearch,\n ViewSettings,\n Router,\n Routes,\n Events,\n Helper,\n Pending,\n Drawer,\n Toast,\n Str,\n Config,\n Ajax,\n FocusLock,\n ModalBackdrop,\n Templates,\n) {\n\n var SELECTORS = {\n DRAWER: '[data-region=\"right-hand-drawer\"]',\n PANEL_BODY_CONTAINER: '[data-region=\"panel-body-container\"]',\n PANEL_HEADER_CONTAINER: '[data-region=\"panel-header-container\"]',\n VIEW_CONTACT: '[data-region=\"view-contact\"]',\n VIEW_CONTACTS: '[data-region=\"view-contacts\"]',\n VIEW_CONVERSATION: '[data-region=\"view-conversation\"]',\n VIEW_CONVERSATION_WITH_ID: '[data-region=\"view-conversation\"][data-conversation-id]',\n VIEW_CONVERSATION_WITH_USER: '[data-region=\"view-conversation\"][data-other-user-id]',\n VIEW_GROUP_INFO: '[data-region=\"view-group-info\"]',\n VIEW_OVERVIEW: '[data-region=\"view-overview\"]',\n VIEW_SEARCH: '[data-region=\"view-search\"]',\n VIEW_SETTINGS: '[data-region=\"view-settings\"]',\n ROUTES: '[data-route]',\n ROUTES_BACK: '[data-route-back]',\n HEADER_CONTAINER: '[data-region=\"header-container\"]',\n BODY_CONTAINER: '[data-region=\"body-container\"]',\n FOOTER_CONTAINER: '[data-region=\"footer-container\"]',\n CLOSE_BUTTON: '[data-action=\"closedrawer\"]',\n MESSAGE_INDEX: '[data-region=\"message-index\"]',\n MESSAGE_TEXT_AREA: '[data-region=\"send-message-txt\"]',\n };\n\n /**\n * Get elements for route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n * @param {string} selector The route container.\n *\n * @return {array} elements Found route container objects.\n */\n var getParametersForRoute = function(namespace, root, selector) {\n\n var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);\n if (!header.length) {\n header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);\n }\n var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);\n if (!body.length) {\n body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector);\n }\n var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector);\n\n return [\n namespace,\n header.length ? header : null,\n body.length ? body : null,\n footer.length ? footer : null\n ];\n };\n\n var routes = [\n [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description],\n [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description],\n [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description],\n [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description],\n [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description],\n [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description],\n [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description]\n ];\n\n /**\n * Create routes.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n */\n var createRoutes = function(namespace, root) {\n routes.forEach(function(route) {\n Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);\n });\n };\n\n let backdropPromise = null;\n\n /**\n * Set the focus on the drawer.\n *\n * This method also creates or destroy any necessary backdrop zone and focus trap.\n *\n * @param {Object} root The message drawer container.\n * @param {Boolean} hasFocus Whether the drawer has focus or not.\n */\n var setFocus = function(root, hasFocus) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (!drawerRoot.length) {\n return;\n }\n if (!backdropPromise) {\n backdropPromise = Templates.render('core/modal_backdrop', {})\n .then(html => new ModalBackdrop(html));\n }\n const backdropWithAdjustments = backdropPromise.then(modalBackdrop => {\n const messageDrawerZIndex = window.getComputedStyle(drawerRoot[0]).zIndex;\n if (messageDrawerZIndex) {\n modalBackdrop.setZIndex(messageDrawerZIndex - 1);\n }\n modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => {\n PubSub.publish(Events.HIDE, {});\n e.preventDefault();\n });\n return modalBackdrop;\n });\n if (hasFocus) {\n FocusLock.trapFocus(root[0]);\n // eslint-disable-next-line promise/catch-or-return\n backdropWithAdjustments.then(modalBackdrop => {\n if (modalBackdrop) {\n modalBackdrop.show();\n const pageWrapper = document.getElementById('page');\n pageWrapper.style.overflow = 'hidden';\n // Set the focus on the close button so when we press enter, it closes the drawer as it did before\n var closeButton = root.find(SELECTORS.CLOSE_BUTTON);\n if (closeButton.length) {\n closeButton.focus();\n }\n }\n return modalBackdrop;\n });\n } else {\n // eslint-disable-next-line promise/catch-or-return\n backdropWithAdjustments.then(modalBackdrop => {\n if (modalBackdrop) {\n FocusLock.untrapFocus();\n var button = $(SELECTORS.DRAWER).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n modalBackdrop.hide();\n const pageWrapper = document.getElementById('page');\n pageWrapper.style.overflow = 'visible';\n }\n return modalBackdrop;\n });\n }\n };\n /**\n * Show the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n */\n var show = function(namespace, root) {\n if (!root.attr('data-shown')) {\n Router.go(namespace, Routes.VIEW_OVERVIEW);\n root.attr('data-shown', true);\n }\n\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n setFocus(root, true);\n Drawer.show(drawerRoot);\n }\n };\n\n /**\n * Hide the message drawer.\n *\n * @param {Object} root The message drawer container.\n */\n var hide = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n setFocus(root, false);\n Drawer.hide(drawerRoot);\n }\n };\n\n /**\n * Check if the drawer is visible.\n *\n * @param {Object} root The message drawer container.\n * @return {boolean}\n */\n var isVisible = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n return Drawer.isVisible(drawerRoot);\n }\n return true;\n };\n\n /**\n * Set Jump from button\n *\n * @param {String} buttonid The originating button id\n */\n var setJumpFrom = function(buttonid) {\n $(SELECTORS.DRAWER).attr('data-origin', buttonid);\n };\n\n /**\n * Store an unsent message.\n *\n * Don't store this if the user has already seen the unsent message.\n * This avoids spamming and ensures the user is only reminded once per unsent message.\n * If the unsent message is sent, this attribute is removed and notification is possible again (see sendMessage).\n */\n const storeUnsentMessage = async() => {\n const messageTextArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n\n if (messageTextArea.value.trim().length > 0 && !messageTextArea.hasAttribute('data-unsent-message-viewed')) {\n\n let message = messageTextArea.value;\n let conversationid = 0;\n let otheruserid = 0;\n\n // We don't always have a conversation to link the unsent message to, so let's check for that.\n const conversationId = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n if (conversationId) {\n const conversationWithId = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n conversationid = conversationWithId.getAttribute('data-conversation-id');\n }\n // Store the 'other' user id if it is there. This can be used to create conversations.\n const conversationUser = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n if (conversationUser) {\n const conversationWithUser = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n otheruserid = conversationWithUser.getAttribute('data-other-user-id');\n }\n\n setStoredUnsentMessage(message, conversationid, otheruserid);\n }\n };\n\n /**\n * Get the stored unsent message from the session via web service.\n *\n * @returns {Promise}\n */\n const getStoredUnsentMessage = () => Ajax.call([{\n methodname: 'core_message_get_unsent_message',\n args: {}\n }])[0];\n\n /**\n * Set the unsent message value in the session via web service.\n *\n * SendBeacon is used here because this is called on 'beforeunload'.\n *\n * @param {string} message The message string.\n * @param {number} conversationid The conversation id.\n * @param {number} otheruserid The other user id.\n * @returns {Promise}\n */\n const setStoredUnsentMessage = (message, conversationid, otheruserid) => {\n const method = 'core_message_set_unsent_message';\n const requestUrl = new URL(`${Config.wwwroot}/lib/ajax/service.php`);\n requestUrl.searchParams.set('sesskey', Config.sesskey);\n requestUrl.searchParams.set('info', method);\n\n navigator.sendBeacon(requestUrl, JSON.stringify([{\n index: 0,\n methodname: method,\n args: {\n message: message,\n conversationid: conversationid,\n otheruserid: otheruserid,\n }\n }]));\n };\n\n /**\n * Check for an unsent message.\n *\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const getUnsentMessage = async(uniqueId, root) => {\n let type;\n let messageRoot;\n\n // We need to check if we are on the message/index page.\n // This logic is needed to handle the two message widgets here and ensure we are targetting the right one.\n const messageIndex = document.querySelector(SELECTORS.MESSAGE_INDEX);\n if (messageIndex !== null) {\n type = 'index';\n messageRoot = document.getElementById(`message-index-${uniqueId}`);\n if (!messageRoot) {\n // This is not the correct widget.\n return;\n }\n\n } else {\n type = 'drawer';\n messageRoot = document.getElementById(`message-drawer-${uniqueId}`);\n }\n\n const storedMessage = await getStoredUnsentMessage();\n const messageTextArea = messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (storedMessage.message && messageTextArea !== null) {\n showUnsentMessage(messageTextArea, storedMessage, type, uniqueId, root);\n }\n };\n\n /**\n * Show an unsent message.\n *\n * There are two message widgets on the message/index page.\n * Because of that, we need to try and target the correct widget.\n *\n * @param {String} textArea The textarea element.\n * @param {Object} stored The stored message content.\n * @param {String} type Is this from the drawer or index page?\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const showUnsentMessage = (textArea, stored, type, uniqueId, root) => {\n // The user has already been notified.\n if (textArea.hasAttribute('data-unsent-message-viewed')) {\n return;\n }\n\n // Depending on the type, show the conversation with the data we have available.\n // A conversation can be continued if there is a conversationid.\n // If the user was messaging a new non-contact, we won't have a conversationid yet.\n // In that case, we use the otheruserid value to start a conversation with them.\n switch (type) {\n case 'index':\n // Show the conversation in the main panel on the message/index page.\n if (stored.conversationid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, stored.conversationid, 'frompanel');\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n\n case 'drawer':\n // Open the drawer and show the conversation.\n if (stored.conversationid) {\n let args = {\n conversationid: stored.conversationid\n };\n Helper.showConversation(args);\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n show(uniqueId, root);\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n }\n\n // Populate the text area.\n textArea.value = stored.message;\n textArea.setAttribute('data-unsent-message-viewed', 1);\n\n // Notify the user.\n Toast.add(Str.get_string('unsentmessagenotification', 'core_message'));\n };\n\n /**\n * Listen to and handle events for routing, showing and hiding the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n * @param {bool} alwaysVisible Is this messaging app always shown?\n */\n var registerEventListeners = function(namespace, root, alwaysVisible) {\n CustomEvents.define(root, [CustomEvents.events.activate, CustomEvents.events.escape]);\n var paramRegex = /^data-route-param-?(\\d*)$/;\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) {\n var element = $(e.target).closest(SELECTORS.ROUTES);\n var route = element.attr('data-route');\n var attributes = [];\n\n for (var i = 0; i < element[0].attributes.length; i++) {\n attributes.push(element[0].attributes[i]);\n }\n\n var paramAttributes = attributes.filter(function(attribute) {\n var name = attribute.nodeName;\n var match = paramRegex.test(name);\n return match;\n });\n paramAttributes.sort(function(a, b) {\n var aParts = paramRegex.exec(a.nodeName);\n var bParts = paramRegex.exec(b.nodeName);\n var aIndex = aParts.length > 1 ? aParts[1] : 0;\n var bIndex = bParts.length > 1 ? bParts[1] : 0;\n\n if (aIndex < bIndex) {\n return -1;\n } else if (bIndex < aIndex) {\n return 1;\n } else {\n return 0;\n }\n });\n\n var params = paramAttributes.map(function(attribute) {\n return attribute.nodeValue;\n });\n\n var routeParams = [namespace, route].concat(params);\n\n Router.go.apply(null, routeParams);\n\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {\n Router.back(namespace);\n\n data.originalEvent.preventDefault();\n });\n\n // These are theme-specific to help us fix random behat fails.\n // These events target those events defined in BS3 and BS4 onwards.\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('hide.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('hidden.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('show.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('shown.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n $(SELECTORS.JUMPTO).focus(function() {\n var firstInput = root.find(SELECTORS.CLOSE_BUTTON);\n if (firstInput.length) {\n firstInput.focus();\n } else {\n $(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus();\n }\n });\n\n $(SELECTORS.DRAWER).focus(function() {\n var button = $(this).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n });\n\n if (!alwaysVisible) {\n PubSub.subscribe(Events.SHOW, function() {\n show(namespace, root);\n });\n\n PubSub.subscribe(Events.HIDE, function() {\n hide(root);\n });\n\n PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) {\n const buttonElement = document.getElementById(buttonid);\n if (isVisible(root)) {\n hide(root);\n buttonElement?.setAttribute('aria-expanded', false);\n $(SELECTORS.JUMPTO).attr('tabindex', -1);\n } else {\n show(namespace, root);\n buttonElement?.setAttribute('aria-expanded', true);\n setJumpFrom(buttonid);\n $(SELECTORS.JUMPTO).attr('tabindex', 0);\n }\n });\n root.on(CustomEvents.events.escape, function() {\n PubSub.publish(Events.HIDE, {});\n });\n }\n\n PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid);\n });\n\n var closebutton = root.find(SELECTORS.CLOSE_BUTTON);\n closebutton.on(CustomEvents.events.activate, function(e, data) {\n data.originalEvent.preventDefault();\n setFocus(root, false);\n var button = $(SELECTORS.DRAWER).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n PubSub.publish(Events.TOGGLE_VISIBILITY, button);\n });\n\n PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid);\n });\n\n PubSub.subscribe(Events.SHOW_SETTINGS, function() {\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_SETTINGS);\n });\n\n PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) {\n var filteredPreferences = preferences.filter(function(preference) {\n return preference.type == 'message_entertosend';\n });\n var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;\n\n if (enterToSendPreference) {\n var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);\n viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);\n }\n });\n\n // If our textarea is modified, remove the attribute which indicates the user has seen the unsent message notification.\n // This will allow the user to be notified again.\n const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (textArea) {\n textArea.addEventListener('keyup', function() {\n textArea.removeAttribute('data-unsent-message-viewed');\n });\n }\n\n // Catch any unsent messages and store them.\n window.addEventListener('beforeunload', storeUnsentMessage);\n };\n\n /**\n * Initialise the message drawer.\n *\n * @param {Object} root The message drawer container.\n * @param {String} uniqueId Unique identifier for the Routes\n * @param {bool} alwaysVisible Should we show the app now, or wait for the user?\n * @param {Object} route\n */\n var init = function(root, uniqueId, alwaysVisible, route) {\n root = $(root);\n createRoutes(uniqueId, root);\n registerEventListeners(uniqueId, root, alwaysVisible);\n\n if (alwaysVisible) {\n show(uniqueId, root);\n\n if (route) {\n var routeParams = route.params || [];\n routeParams = [uniqueId, route.path].concat(routeParams);\n Router.go.apply(null, routeParams);\n }\n }\n\n // Mark the drawer as ready.\n Helper.markDrawerReady();\n\n // Get and show any unsent message.\n getUnsentMessage(uniqueId, root);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","ViewContact","ViewContacts","ViewConversation","ViewGroupInfo","ViewOverview","ViewSearch","ViewSettings","Router","Routes","Events","Helper","Pending","Drawer","Toast","Str","Config","Ajax","FocusLock","ModalBackdrop","Templates","SELECTORS","DRAWER","PANEL_BODY_CONTAINER","PANEL_HEADER_CONTAINER","VIEW_CONTACT","VIEW_CONTACTS","VIEW_CONVERSATION","VIEW_CONVERSATION_WITH_ID","VIEW_CONVERSATION_WITH_USER","VIEW_GROUP_INFO","VIEW_OVERVIEW","VIEW_SEARCH","VIEW_SETTINGS","ROUTES","ROUTES_BACK","HEADER_CONTAINER","BODY_CONTAINER","FOOTER_CONTAINER","CLOSE_BUTTON","MESSAGE_INDEX","MESSAGE_TEXT_AREA","routes","show","description","createRoutes","namespace","root","forEach","route","add","selector","header","find","length","body","footer","getParametersForRoute","backdropPromise","setFocus","hasFocus","drawerRoot","getDrawerRoot","render","then","html","backdropWithAdjustments","modalBackdrop","messageDrawerZIndex","window","getComputedStyle","zIndex","setZIndex","getAttachmentPoint","get","addEventListener","e","publish","HIDE","preventDefault","trapFocus","document","getElementById","style","overflow","closeButton","focus","untrapFocus","button","attr","hide","go","setJumpFrom","buttonid","storeUnsentMessage","async","messageTextArea","querySelector","value","trim","hasAttribute","message","conversationid","otheruserid","closest","getAttribute","setStoredUnsentMessage","method","requestUrl","URL","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index","methodname","args","getUnsentMessage","uniqueId","type","messageRoot","storedMessage","call","showUnsentMessage","textArea","stored","showConversation","setAttribute","get_string","registerEventListeners","alwaysVisible","events","activate","escape","paramRegex","on","data","element","target","attributes","i","push","paramAttributes","filter","attribute","name","nodeName","test","sort","a","b","aParts","exec","bParts","aIndex","bIndex","params","map","nodeValue","routeParams","concat","apply","originalEvent","back","querySelectorAll","collapse","pendingPromise","resolve","once","JUMPTO","firstInput","this","subscribe","SHOW","TOGGLE_VISIBILITY","buttonElement","isVisible","SHOW_CONVERSATION","CREATE_CONVERSATION_WITH_USER","userid","SHOW_SETTINGS","PREFERENCES_UPDATED","preferences","filteredPreferences","preference","enterToSendPreference","removeAttribute","init","path","markDrawerReady"],"mappings":"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"} \ No newline at end of file diff --git a/message/amd/build/message_popover.min.js b/message/amd/build/message_popover.min.js index 9647468aed2..fdcebfec230 100644 --- a/message/amd/build/message_popover.min.js +++ b/message/amd/build/message_popover.min.js @@ -5,6 +5,6 @@ * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("core_message/message_popover",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_events"],(function($,CustomEvents,PubSub,MessageDrawerEvents){var SELECTORS_COUNT_CONTAINER='[data-region="count-container"]',handleDecrementConversationCount=function(button){return function(){var countContainer=button.find(SELECTORS_COUNT_CONTAINER),count=parseInt(countContainer.text(),10);isNaN(count)||!count||count<2?countContainer.addClass("hidden"):(count-=1,countContainer.text(count))}},registerEventListeners=function(button){CustomEvents.define(button,[CustomEvents.events.activate]),button.on(CustomEvents.events.activate,(function(e,data){var buttonid;buttonid=button.attr("id"),PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY,buttonid),button.focus(),data.originalEvent.preventDefault()})),PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ,handleDecrementConversationCount(button)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,handleDecrementConversationCount(button)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,handleDecrementConversationCount(button))};return{init:function(button){button=$(button),registerEventListeners(button)}}})); +define("core_message/message_popover",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_events"],(function($,CustomEvents,PubSub,MessageDrawerEvents){var SELECTORS_COUNT_CONTAINER='[data-region="count-container"]',handleDecrementConversationCount=function(button){return function(){var countContainer=button.find(SELECTORS_COUNT_CONTAINER),count=parseInt(countContainer.text(),10);isNaN(count)||!count||count<2?countContainer.addClass("hidden"):(count-=1,countContainer.text(count))}},registerEventListeners=function(button){CustomEvents.define(button,[CustomEvents.events.activate]),button.on(CustomEvents.events.activate,(function(e,data){var buttonid;buttonid=button.attr("id"),PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY,buttonid),data.originalEvent.preventDefault()})),PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ,handleDecrementConversationCount(button)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,handleDecrementConversationCount(button)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,handleDecrementConversationCount(button))};return{init:function(button){button=$(button),registerEventListeners(button)}}})); //# sourceMappingURL=message_popover.min.js.map \ No newline at end of file diff --git a/message/amd/build/message_popover.min.js.map b/message/amd/build/message_popover.min.js.map index 541236926a2..30cbc31d4ec 100644 --- a/message/amd/build/message_popover.min.js.map +++ b/message/amd/build/message_popover.min.js.map @@ -1 +1 @@ -{"version":3,"file":"message_popover.min.js","sources":["../src/message_popover.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 * Controls the message popover in the nav bar.\n *\n * @module core_message/message_popover\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_events'\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n MessageDrawerEvents\n) {\n var SELECTORS = {\n COUNT_CONTAINER: '[data-region=\"count-container\"]'\n };\n\n /**\n * Toggle the message drawer visibility.\n *\n * @param {String} buttonid The button id for the popover.\n */\n var toggleMessageDrawerVisibility = function(buttonid) {\n PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY, buttonid);\n };\n\n /**\n * Decrement the unread conversation count in the nav bar if a conversation\n * is read. When there are no unread conversations then hide the counter.\n *\n * @param {Object} button The button element for the popover.\n * @return {Function}\n */\n var handleDecrementConversationCount = function(button) {\n return function() {\n var countContainer = button.find(SELECTORS.COUNT_CONTAINER);\n var count = parseInt(countContainer.text(), 10);\n\n if (isNaN(count)) {\n countContainer.addClass('hidden');\n } else if (!count || count < 2) {\n countContainer.addClass('hidden');\n } else {\n count = count - 1;\n countContainer.text(count);\n }\n };\n };\n\n /**\n * Add events listeners for when the popover icon is clicked and when conversations\n * are read.\n *\n * @param {Object} button The button element for the popover.\n */\n var registerEventListeners = function(button) {\n CustomEvents.define(button, [CustomEvents.events.activate]);\n\n button.on(CustomEvents.events.activate, function(e, data) {\n toggleMessageDrawerVisibility(button.attr('id'));\n button.focus();\n data.originalEvent.preventDefault();\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, handleDecrementConversationCount(button));\n };\n\n /**\n * Initialise the message popover.\n *\n * @param {Object} button The button element for the popover.\n */\n var init = function(button) {\n button = $(button);\n registerEventListeners(button);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","MessageDrawerEvents","SELECTORS","handleDecrementConversationCount","button","countContainer","find","count","parseInt","text","isNaN","addClass","registerEventListeners","events","activate","on","e","data","buttonid","attr","publish","TOGGLE_VISIBILITY","focus","originalEvent","preventDefault","subscribe","CONVERSATION_READ","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","init"],"mappings":";;;;;;;AAsBAA,sCACA,CACI,SACA,iCACA,cACA,uCAEJ,SACIC,EACAC,aACAC,OACAC,yBAEIC,0BACiB,kCAmBjBC,iCAAmC,SAASC,eACrC,eACCC,eAAiBD,OAAOE,KAAKJ,2BAC7BK,MAAQC,SAASH,eAAeI,OAAQ,IAExCC,MAAMH,SAEEA,OAASA,MAAQ,EADzBF,eAAeM,SAAS,WAIxBJ,OAAgB,EAChBF,eAAeI,KAAKF,UAW5BK,uBAAyB,SAASR,QAClCL,aAAaF,OAAOO,OAAQ,CAACL,aAAac,OAAOC,WAEjDV,OAAOW,GAAGhB,aAAac,OAAOC,UAAU,SAASE,EAAGC,MApCpB,IAASC,SAAAA,SAqCPd,OAAOe,KAAK,MApC9CnB,OAAOoB,QAAQnB,oBAAoBoB,kBAAmBH,UAqClDd,OAAOkB,QACPL,KAAKM,cAAcC,oBAGvBxB,OAAOyB,UAAUxB,oBAAoByB,kBAAmBvB,iCAAiCC,SACzFJ,OAAOyB,UAAUxB,oBAAoB0B,yBAA0BxB,iCAAiCC,SAChGJ,OAAOyB,UAAUxB,oBAAoB2B,yBAA0BzB,iCAAiCC,gBAa7F,CACHyB,KANO,SAASzB,QAChBA,OAASN,EAAEM,QACXQ,uBAAuBR"} \ No newline at end of file +{"version":3,"file":"message_popover.min.js","sources":["../src/message_popover.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 * Controls the message popover in the nav bar.\n *\n * @module core_message/message_popover\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_events'\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n MessageDrawerEvents\n) {\n var SELECTORS = {\n COUNT_CONTAINER: '[data-region=\"count-container\"]'\n };\n\n /**\n * Toggle the message drawer visibility.\n *\n * @param {String} buttonid The button id for the popover.\n */\n var toggleMessageDrawerVisibility = function(buttonid) {\n PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY, buttonid);\n };\n\n /**\n * Decrement the unread conversation count in the nav bar if a conversation\n * is read. When there are no unread conversations then hide the counter.\n *\n * @param {Object} button The button element for the popover.\n * @return {Function}\n */\n var handleDecrementConversationCount = function(button) {\n return function() {\n var countContainer = button.find(SELECTORS.COUNT_CONTAINER);\n var count = parseInt(countContainer.text(), 10);\n\n if (isNaN(count)) {\n countContainer.addClass('hidden');\n } else if (!count || count < 2) {\n countContainer.addClass('hidden');\n } else {\n count = count - 1;\n countContainer.text(count);\n }\n };\n };\n\n /**\n * Add events listeners for when the popover icon is clicked and when conversations\n * are read.\n *\n * @param {Object} button The button element for the popover.\n */\n var registerEventListeners = function(button) {\n CustomEvents.define(button, [CustomEvents.events.activate]);\n\n button.on(CustomEvents.events.activate, function(e, data) {\n toggleMessageDrawerVisibility(button.attr('id'));\n data.originalEvent.preventDefault();\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, handleDecrementConversationCount(button));\n };\n\n /**\n * Initialise the message popover.\n *\n * @param {Object} button The button element for the popover.\n */\n var init = function(button) {\n button = $(button);\n registerEventListeners(button);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","MessageDrawerEvents","SELECTORS","handleDecrementConversationCount","button","countContainer","find","count","parseInt","text","isNaN","addClass","registerEventListeners","events","activate","on","e","data","buttonid","attr","publish","TOGGLE_VISIBILITY","originalEvent","preventDefault","subscribe","CONVERSATION_READ","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","init"],"mappings":";;;;;;;AAsBAA,sCACA,CACI,SACA,iCACA,cACA,uCAEJ,SACIC,EACAC,aACAC,OACAC,yBAEIC,0BACiB,kCAmBjBC,iCAAmC,SAASC,eACrC,eACCC,eAAiBD,OAAOE,KAAKJ,2BAC7BK,MAAQC,SAASH,eAAeI,OAAQ,IAExCC,MAAMH,SAEEA,OAASA,MAAQ,EADzBF,eAAeM,SAAS,WAIxBJ,OAAgB,EAChBF,eAAeI,KAAKF,UAW5BK,uBAAyB,SAASR,QAClCL,aAAaF,OAAOO,OAAQ,CAACL,aAAac,OAAOC,WAEjDV,OAAOW,GAAGhB,aAAac,OAAOC,UAAU,SAASE,EAAGC,MApCpB,IAASC,SAAAA,SAqCPd,OAAOe,KAAK,MApC9CnB,OAAOoB,QAAQnB,oBAAoBoB,kBAAmBH,UAqClDD,KAAKK,cAAcC,oBAGvBvB,OAAOwB,UAAUvB,oBAAoBwB,kBAAmBtB,iCAAiCC,SACzFJ,OAAOwB,UAAUvB,oBAAoByB,yBAA0BvB,iCAAiCC,SAChGJ,OAAOwB,UAAUvB,oBAAoB0B,yBAA0BxB,iCAAiCC,gBAa7F,CACHwB,KANO,SAASxB,QAChBA,OAASN,EAAEM,QACXQ,uBAAuBR"} \ No newline at end of file diff --git a/message/amd/src/message_drawer.js b/message/amd/src/message_drawer.js index df6630cbfa6..140c0706b71 100644 --- a/message/amd/src/message_drawer.js +++ b/message/amd/src/message_drawer.js @@ -42,6 +42,9 @@ define( 'core/str', 'core/config', 'core/ajax', + 'core/local/aria/focuslock', + 'core/modal_backdrop', + 'core/templates', ], function( $, @@ -64,11 +67,13 @@ function( Str, Config, Ajax, + FocusLock, + ModalBackdrop, + Templates, ) { var SELECTORS = { DRAWER: '[data-region="right-hand-drawer"]', - JUMPTO: '.popover-region [data-region="jumpto"]', PANEL_BODY_CONTAINER: '[data-region="panel-body-container"]', PANEL_HEADER_CONTAINER: '[data-region="panel-header-container"]', VIEW_CONTACT: '[data-region="view-contact"]', @@ -141,6 +146,69 @@ function( }); }; + let backdropPromise = null; + + /** + * Set the focus on the drawer. + * + * This method also creates or destroy any necessary backdrop zone and focus trap. + * + * @param {Object} root The message drawer container. + * @param {Boolean} hasFocus Whether the drawer has focus or not. + */ + var setFocus = function(root, hasFocus) { + var drawerRoot = Drawer.getDrawerRoot(root); + if (!drawerRoot.length) { + return; + } + if (!backdropPromise) { + backdropPromise = Templates.render('core/modal_backdrop', {}) + .then(html => new ModalBackdrop(html)); + } + const backdropWithAdjustments = backdropPromise.then(modalBackdrop => { + const messageDrawerZIndex = window.getComputedStyle(drawerRoot[0]).zIndex; + if (messageDrawerZIndex) { + modalBackdrop.setZIndex(messageDrawerZIndex - 1); + } + modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => { + PubSub.publish(Events.HIDE, {}); + e.preventDefault(); + }); + return modalBackdrop; + }); + if (hasFocus) { + FocusLock.trapFocus(root[0]); + // eslint-disable-next-line promise/catch-or-return + backdropWithAdjustments.then(modalBackdrop => { + if (modalBackdrop) { + modalBackdrop.show(); + const pageWrapper = document.getElementById('page'); + pageWrapper.style.overflow = 'hidden'; + // Set the focus on the close button so when we press enter, it closes the drawer as it did before + var closeButton = root.find(SELECTORS.CLOSE_BUTTON); + if (closeButton.length) { + closeButton.focus(); + } + } + return modalBackdrop; + }); + } else { + // eslint-disable-next-line promise/catch-or-return + backdropWithAdjustments.then(modalBackdrop => { + if (modalBackdrop) { + FocusLock.untrapFocus(); + var button = $(SELECTORS.DRAWER).attr('data-origin'); + if (button) { + $('#' + button).focus(); + } + modalBackdrop.hide(); + const pageWrapper = document.getElementById('page'); + pageWrapper.style.overflow = 'visible'; + } + return modalBackdrop; + }); + } + }; /** * Show the message drawer. * @@ -155,6 +223,7 @@ function( var drawerRoot = Drawer.getDrawerRoot(root); if (drawerRoot.length) { + setFocus(root, true); Drawer.show(drawerRoot); } }; @@ -167,6 +236,7 @@ function( var hide = function(root) { var drawerRoot = Drawer.getDrawerRoot(root); if (drawerRoot.length) { + setFocus(root, false); Drawer.hide(drawerRoot); } }; @@ -361,7 +431,7 @@ function( * @param {bool} alwaysVisible Is this messaging app always shown? */ var registerEventListeners = function(namespace, root, alwaysVisible) { - CustomEvents.define(root, [CustomEvents.events.activate]); + CustomEvents.define(root, [CustomEvents.events.activate, CustomEvents.events.escape]); var paramRegex = /^data-route-param-?(\d*)$/; root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) { @@ -468,6 +538,9 @@ function( $(SELECTORS.JUMPTO).attr('tabindex', 0); } }); + root.on(CustomEvents.events.escape, function() { + PubSub.publish(Events.HIDE, {}); + }); } PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) { @@ -479,7 +552,7 @@ function( var closebutton = root.find(SELECTORS.CLOSE_BUTTON); closebutton.on(CustomEvents.events.activate, function(e, data) { data.originalEvent.preventDefault(); - + setFocus(root, false); var button = $(SELECTORS.DRAWER).attr('data-origin'); if (button) { $('#' + button).focus(); diff --git a/message/amd/src/message_popover.js b/message/amd/src/message_popover.js index 52b7fa0b4e3..df2f81d75cd 100644 --- a/message/amd/src/message_popover.js +++ b/message/amd/src/message_popover.js @@ -80,7 +80,6 @@ function( button.on(CustomEvents.events.activate, function(e, data) { toggleMessageDrawerVisibility(button.attr('id')); - button.focus(); data.originalEvent.preventDefault(); }); diff --git a/message/templates/message_drawer.mustache b/message/templates/message_drawer.mustache index f4bcfb5c70e..4cbef599139 100644 --- a/message/templates/message_drawer.mustache +++ b/message/templates/message_drawer.mustache @@ -34,9 +34,15 @@ }} {{< core/drawer}} + {{$tabindex}}-1{{/tabindex}} + {{$role}}dialog{{/role}} + {{$drawerattributes}} + aria-modal="true" + aria-labelledby="message-drawer-header-{{uniqid}}" + {{/drawerattributes}} {{$drawercontent}} -
-

{{#str}} messagingcategory,admin {{/str}}

+
+

{{#str}} messagingcategory,admin {{/str}}