first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,14 @@
define("core_message/default_notification_preferences",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;
/**
* Controls the default settings for the list of notification types on the
* notifications admin page
*
* @module core_message/default_notification_preferences
* @class default_notification_preferences
* @copyright 2021 Moodle
* @author Pau Ferrer Ocaña <pau@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const selectors_provider=".defaultmessageoutputs .provider_enabled",selectors_lockSetting=".locked_message_setting",selectors_allSettings=".locked_message_setting, .enabled_message_setting";var _default={init:()=>{(()=>{const toggleLockSetting=lockedElement=>{const isEnabled=lockedElement.checked||!1,enabledId=lockedElement.id.replace("_locked[","_enabled[");document.getElementById(enabledId).closest("div.custom-control").classList.toggle("dimmed_text",isEnabled)},toggleEnableProviderSettings=providerEnabledElement=>{const isEnabled=providerEnabledElement.checked||!1;providerEnabledElement.closest("tr").querySelectorAll(selectors_allSettings).forEach((element=>{element.toggleAttribute("disabled",!isEnabled)}))},container=document.querySelector(".preferences-container");container.querySelectorAll(selectors_provider).forEach((providerEnabledElement=>{providerEnabledElement.checked||toggleEnableProviderSettings(providerEnabledElement),providerEnabledElement.addEventListener("change",(e=>{toggleEnableProviderSettings(e.target)}))})),container.querySelectorAll(selectors_lockSetting).forEach((lockedElement=>{lockedElement.checked&&toggleLockSetting(lockedElement),lockedElement.addEventListener("change",(e=>{toggleLockSetting(e.target)}))}))})()}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=default_notification_preferences.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"default_notification_preferences.min.js","sources":["../src/default_notification_preferences.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Controls the default settings for the list of notification types on the\n * notifications admin page\n *\n * @module core_message/default_notification_preferences\n * @class default_notification_preferences\n * @copyright 2021 Moodle\n * @author Pau Ferrer Ocaña <pau@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst selectors = {\n provider: '.defaultmessageoutputs .provider_enabled',\n lockSetting: '.locked_message_setting',\n enabledSetting: '.enabled_message_setting',\n allSettings: '.locked_message_setting, .enabled_message_setting'\n};\n\n/**\n * Register event listeners for the default_notification_preferences page.\n */\nconst registerEventListeners = () => {\n\n /**\n * Update the dimmed status of the \"enabled\" toggle on the notification setting.\n *\n * @param {HTMLElement} lockedElement Element that receives the event.\n */\n const toggleLockSetting = (lockedElement) => {\n const isEnabled = lockedElement.checked || false;\n const enabledId = lockedElement.id.replace('_locked[', '_enabled[');\n\n const enabledElement = document.getElementById(enabledId).closest('div.custom-control');\n enabledElement.classList.toggle('dimmed_text', isEnabled);\n };\n\n /**\n * Enable/Disable all settings of the provider.\n *\n * @param {HTMLElement} providerEnabledElement Element that receives the event.\n */\n const toggleEnableProviderSettings = (providerEnabledElement) => {\n const isEnabled = providerEnabledElement.checked || false;\n const parentRow = providerEnabledElement.closest('tr');\n\n const elements = parentRow.querySelectorAll(selectors.allSettings);\n elements.forEach((element) => {\n element.toggleAttribute('disabled', !isEnabled);\n });\n };\n\n const container = document.querySelector('.preferences-container');\n\n container.querySelectorAll(selectors.provider).forEach((providerEnabledElement) => {\n // Set the initial statuses.\n if (!providerEnabledElement.checked) {\n toggleEnableProviderSettings(providerEnabledElement);\n }\n\n providerEnabledElement.addEventListener('change', (e) => {\n toggleEnableProviderSettings(e.target);\n });\n });\n\n container.querySelectorAll(selectors.lockSetting).forEach((lockedElement) => {\n // Set the initial statuses.\n if (lockedElement.checked) {\n toggleLockSetting(lockedElement);\n }\n\n lockedElement.addEventListener('change', (e) => {\n toggleLockSetting(e.target);\n });\n });\n};\n\n/**\n * Initialize the page.\n */\nconst init = () => {\n registerEventListeners();\n};\n\nexport default {\n init: init,\n};\n"],"names":["selectors","init","toggleLockSetting","lockedElement","isEnabled","checked","enabledId","id","replace","document","getElementById","closest","classList","toggle","toggleEnableProviderSettings","providerEnabledElement","querySelectorAll","forEach","element","toggleAttribute","container","querySelector","addEventListener","e","target","registerEventListeners"],"mappings":";;;;;;;;;;;MA0BMA,mBACQ,2CADRA,sBAEW,0BAFXA,sBAIW,iEAoEF,CACXC,KALS,KA1DkB,YAOrBC,kBAAqBC,sBACjBC,UAAYD,cAAcE,UAAW,EACrCC,UAAYH,cAAcI,GAAGC,QAAQ,WAAY,aAEhCC,SAASC,eAAeJ,WAAWK,QAAQ,sBACnDC,UAAUC,OAAO,cAAeT,YAQ7CU,6BAAgCC,+BAC5BX,UAAYW,uBAAuBV,UAAW,EAClCU,uBAAuBJ,QAAQ,MAEtBK,iBAAiBhB,uBACnCiB,SAASC,UACdA,QAAQC,gBAAgB,YAAaf,eAIvCgB,UAAYX,SAASY,cAAc,0BAEzCD,UAAUJ,iBAAiBhB,oBAAoBiB,SAASF,yBAE/CA,uBAAuBV,SACxBS,6BAA6BC,wBAGjCA,uBAAuBO,iBAAiB,UAAWC,IAC/CT,6BAA6BS,EAAEC,cAIvCJ,UAAUJ,iBAAiBhB,uBAAuBiB,SAASd,gBAEnDA,cAAcE,SACdH,kBAAkBC,eAGtBA,cAAcmB,iBAAiB,UAAWC,IACtCrB,kBAAkBqB,EAAEC,eAS5BC"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("core_message/message_drawer_events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={CREATE_CONVERSATION_WITH_USER:"message-drawer-create-conversation-with-user",CONTACT_BLOCKED:"message-drawer-contact-blocked",CONTACT_UNBLOCKED:"message-drawer-contact-unblocked",CONTACT_ADDED:"message-drawer-contact-added",CONTACT_REMOVED:"message-drawer-contact-removed",CONTACT_REQUEST_ACCEPTED:"message-drawer-contact-request-accepted",CONTACT_REQUEST_DECLINED:"message-drawer-contact-request-declined",CONVERSATION_CREATED:"message-drawer-conversation-created",CONVERSATION_NEW_LAST_MESSAGE:"message-drawer-conversation-new-last-message",CONVERSATION_DELETED:"message-drawer-conversation-deleted",CONVERSATION_READ:"message-drawer-conversation-read",CONVERSATION_SET_FAVOURITE:"message-drawer-conversation-set-favourite",CONVERSATION_SET_MUTED:"message-drawer-conversation-set-muted",CONVERSATION_UNSET_FAVOURITE:"message-drawer-conversation-unset-favourite",CONVERSATION_UNSET_MUTED:"message-drawer-conversation-unset-muted",PREFERENCES_UPDATED:"message-drawer-preferences-updated",READY:"message-drawer-ready",ROUTE_CHANGED:"message-drawer-route-change",SHOW:"message-drawer-show",HIDE:"message-drawer-hide",TOGGLE_VISIBILITY:"message-drawer-toggle",SHOW_CONVERSATION:"message-drawer-show-conversation",SHOW_SETTINGS:"message-drawer-show-settings"},_exports.default}));
//# sourceMappingURL=message_drawer_events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_drawer_events.min.js","sources":["../src/message_drawer_events.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Events for the message drawer.\n *\n * @module core_message/message_drawer_events\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default {\n CREATE_CONVERSATION_WITH_USER: 'message-drawer-create-conversation-with-user',\n CONTACT_BLOCKED: 'message-drawer-contact-blocked',\n CONTACT_UNBLOCKED: 'message-drawer-contact-unblocked',\n CONTACT_ADDED: 'message-drawer-contact-added',\n CONTACT_REMOVED: 'message-drawer-contact-removed',\n CONTACT_REQUEST_ACCEPTED: 'message-drawer-contact-request-accepted',\n CONTACT_REQUEST_DECLINED: 'message-drawer-contact-request-declined',\n CONVERSATION_CREATED: 'message-drawer-conversation-created',\n CONVERSATION_NEW_LAST_MESSAGE: 'message-drawer-conversation-new-last-message',\n CONVERSATION_DELETED: 'message-drawer-conversation-deleted',\n CONVERSATION_READ: 'message-drawer-conversation-read',\n CONVERSATION_SET_FAVOURITE: 'message-drawer-conversation-set-favourite',\n CONVERSATION_SET_MUTED: 'message-drawer-conversation-set-muted',\n CONVERSATION_UNSET_FAVOURITE: 'message-drawer-conversation-unset-favourite',\n CONVERSATION_UNSET_MUTED: 'message-drawer-conversation-unset-muted',\n PREFERENCES_UPDATED: 'message-drawer-preferences-updated',\n READY: 'message-drawer-ready',\n ROUTE_CHANGED: 'message-drawer-route-change',\n SHOW: 'message-drawer-show',\n HIDE: 'message-drawer-hide',\n TOGGLE_VISIBILITY: 'message-drawer-toggle',\n SHOW_CONVERSATION: 'message-drawer-show-conversation',\n SHOW_SETTINGS: 'message-drawer-show-settings',\n};\n"],"names":["CREATE_CONVERSATION_WITH_USER","CONTACT_BLOCKED","CONTACT_UNBLOCKED","CONTACT_ADDED","CONTACT_REMOVED","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","CONVERSATION_CREATED","CONVERSATION_NEW_LAST_MESSAGE","CONVERSATION_DELETED","CONVERSATION_READ","CONVERSATION_SET_FAVOURITE","CONVERSATION_SET_MUTED","CONVERSATION_UNSET_FAVOURITE","CONVERSATION_UNSET_MUTED","PREFERENCES_UPDATED","READY","ROUTE_CHANGED","SHOW","HIDE","TOGGLE_VISIBILITY","SHOW_CONVERSATION","SHOW_SETTINGS"],"mappings":"oLAsBe,CACXA,8BAA+B,+CAC/BC,gBAAiB,iCACjBC,kBAAmB,mCACnBC,cAAe,+BACfC,gBAAiB,iCACjBC,yBAA0B,0CAC1BC,yBAA0B,0CAC1BC,qBAAsB,sCACtBC,8BAA+B,+CAC/BC,qBAAsB,sCACtBC,kBAAmB,mCACnBC,2BAA4B,4CAC5BC,uBAAwB,wCACxBC,6BAA8B,8CAC9BC,yBAA0B,0CAC1BC,oBAAqB,qCACrBC,MAAO,uBACPC,cAAe,8BACfC,KAAM,sBACNC,KAAM,sBACNC,kBAAmB,wBACnBC,kBAAmB,mCACnBC,cAAe"}
+10
View File
@@ -0,0 +1,10 @@
define("core_message/message_drawer_helper",["exports","core/pubsub","core_message/message_drawer_events"],(function(_exports,_pubsub,_message_drawer_events){var obj;
/**
* Provides some helper functions to trigger actions in the message drawer.
*
* @module core_message/message_drawer_helper
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.waitForDrawerToLoad=_exports.showSettings=_exports.showConversation=_exports.show=_exports.markDrawerReady=_exports.hide=_exports.createConversationWithUser=void 0,_message_drawer_events=(obj=_message_drawer_events)&&obj.__esModule?obj:{default:obj};let drawerMarkedReady=!1;_exports.createConversationWithUser=async args=>{await waitForDrawerToLoad(),(0,_pubsub.publish)(_message_drawer_events.default.CREATE_CONVERSATION_WITH_USER,args)};_exports.hide=async()=>{await waitForDrawerToLoad(),(0,_pubsub.publish)(_message_drawer_events.default.HIDE)};_exports.show=async()=>{await waitForDrawerToLoad(),(0,_pubsub.publish)(_message_drawer_events.default.SHOW)};_exports.showConversation=async args=>{await waitForDrawerToLoad(),(0,_pubsub.publish)(_message_drawer_events.default.SHOW_CONVERSATION,args)};_exports.showSettings=async()=>{await waitForDrawerToLoad(),(0,_pubsub.publish)(_message_drawer_events.default.SHOW_SETTINGS)};const waitForDrawerToLoad=()=>new Promise((resolve=>{drawerMarkedReady?resolve():(0,_pubsub.subscribe)(_message_drawer_events.default.READY,resolve)}));_exports.waitForDrawerToLoad=waitForDrawerToLoad;_exports.markDrawerReady=()=>{drawerMarkedReady=!0,(0,_pubsub.publish)(_message_drawer_events.default.READY)}}));
//# sourceMappingURL=message_drawer_helper.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_drawer_helper.min.js","sources":["../src/message_drawer_helper.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Provides some helper functions to trigger actions in the message drawer.\n *\n * @module core_message/message_drawer_helper\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {publish, subscribe} from 'core/pubsub';\nimport MessageDrawerEvents from 'core_message/message_drawer_events';\n\n/** @property {boolean} Whether the drawer is ready or not */\nlet drawerMarkedReady = false;\n\n/**\n * Trigger an event to create a new conversation in the message drawer.\n *\n * @param {object} args\n * @param {Number} args.userId The user id to start a conversation.\n */\nexport const createConversationWithUser = async(args) => {\n await waitForDrawerToLoad();\n publish(MessageDrawerEvents.CREATE_CONVERSATION_WITH_USER, args);\n};\n\n/**\n * Trigger an event to hide the message drawer.\n */\nexport const hide = async() => {\n await waitForDrawerToLoad();\n publish(MessageDrawerEvents.HIDE);\n};\n\n/**\n * Trigger an event to show the message drawer.\n */\nexport const show = async() => {\n await waitForDrawerToLoad();\n publish(MessageDrawerEvents.SHOW);\n};\n\n/**\n * Trigger an event to show the given conversation.\n *\n * @param {object} args\n * @param {int} args.conversationId Id for the conversation to show.\n */\nexport const showConversation = async(args) => {\n await waitForDrawerToLoad();\n publish(MessageDrawerEvents.SHOW_CONVERSATION, args);\n};\n\n/**\n * Trigger an event to show messaging settings.\n */\nexport const showSettings = async() => {\n await waitForDrawerToLoad();\n publish(MessageDrawerEvents.SHOW_SETTINGS);\n};\n\n/**\n * Helper to wait for the drawer to be ready before performing an action.\n *\n * @returns {Promise<void>}\n */\nexport const waitForDrawerToLoad = () => new Promise((resolve) => {\n if (drawerMarkedReady) {\n resolve();\n } else {\n subscribe(MessageDrawerEvents.READY, resolve);\n }\n});\n\n/**\n * Helper to allow the drawer to mark itself as ready.\n */\nexport const markDrawerReady = () => {\n drawerMarkedReady = true;\n publish(MessageDrawerEvents.READY);\n};\n"],"names":["drawerMarkedReady","async","waitForDrawerToLoad","MessageDrawerEvents","CREATE_CONVERSATION_WITH_USER","args","HIDE","SHOW","SHOW_CONVERSATION","SHOW_SETTINGS","Promise","resolve","READY"],"mappings":";;;;;;;oUA2BIA,mBAAoB,sCAQkBC,MAAAA,aAChCC,0CACEC,+BAAoBC,8BAA+BC,qBAM3CJ,gBACVC,0CACEC,+BAAoBG,qBAMZL,gBACVC,0CACEC,+BAAoBI,iCASAN,MAAAA,aACtBC,0CACEC,+BAAoBK,kBAAmBH,6BAMvBJ,gBAClBC,0CACEC,+BAAoBM,sBAQnBP,oBAAsB,IAAM,IAAIQ,SAASC,UAC9CX,kBACAW,gCAEUR,+BAAoBS,MAAOD,sFAOd,KAC3BX,mBAAoB,sBACZG,+BAAoBS"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Lazy loaded list of items.
*
* @module core_message/message_drawer_lazy_load_list
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_lazy_load_list",["jquery","core/custom_interaction_events","core/pending"],(function($,CustomEvents,PendingPromise){var SELECTORS_ROOT='[data-region="lazy-load-list"]',SELECTORS_LOADING_ICON_CONTAINER='[data-region="loading-icon-container"]',SELECTORS_CONTENT_CONTAINER='[data-region="content-container"]',SELECTORS_EMPTY_MESSAGE='[data-region="empty-message-container"]',SELECTORS_PLACEHOLDER='[data-region="placeholder-container"]',stopLoading=function(root){root.attr("data-loading",!1)},getContentContainer=function(root){return root.find(SELECTORS_CONTENT_CONTAINER)},hideLoadingIcon=function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},showEmptyMessage=function(root){root.find(SELECTORS_EMPTY_MESSAGE).removeClass("hidden")},hidePlaceholder=function(root){root.find(SELECTORS_PLACEHOLDER).addClass("hidden")},showContent=function(root){getContentContainer(root).removeClass("hidden")},hideContent=function(root){getContentContainer(root).addClass("hidden")},setLoadedAll=function(root,value){root.attr("data-loaded-all",value)},loadAndRender=function(root,loadCallback,renderCallback){var userId=function(root){return root.attr("data-user-id")}(root);return function(root){root.attr("data-loading",!0)}(root),loadCallback(root,userId).then((function(items){if(items.length>0){var contentContainer=getContentContainer(root);return renderCallback(contentContainer,items,userId).then((function(){return items}))}return items})).then((function(items){return stopLoading(root),root.attr("data-seen",!0),items.length||setLoadedAll(root,!0),items})).catch((function(){stopLoading(root),root.attr("data-seen",!0)}))},initialLoadAndRender=function(root,loadCallback,renderCallback){const pendingPromise=new PendingPromise("initialLoadAndRender");return getContentContainer(root).empty(),function(root){root.find(SELECTORS_PLACEHOLDER).removeClass("hidden")}(root),hideContent(root),loadAndRender(root,loadCallback,renderCallback).then((function(items){hidePlaceholder(root),items.length?showContent(root):showEmptyMessage(root)})).catch((function(){hidePlaceholder(root),showContent(root)})).then((()=>{pendingPromise.resolve()}))},registerEventListeners=function(root,loadCallback,renderCallback){CustomEvents.define(root,[CustomEvents.events.scrollBottom]),root.on(CustomEvents.events.scrollBottom,(function(){(function(root){return!function(root){return"true"==root.attr("data-loaded-all")}(root)&&!function(root){return"true"===root.attr("data-loading")}(root)})(root)&&(!function(root){root.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")}(root),loadAndRender(root,loadCallback,renderCallback).then((function(){return hideLoadingIcon(root)})).catch((function(){return hideLoadingIcon(root)})))}))};return{show:function(root,loadCallback,renderCallback){(root=$(root)).attr("data-init")||(registerEventListeners(root,loadCallback,renderCallback),initialLoadAndRender(root,loadCallback,renderCallback),root.attr("data-init",!0))},getContentContainer:getContentContainer,getRoot:function(containerElement){return containerElement.find(SELECTORS_ROOT)},setLoadedAll:setLoadedAll,showEmptyMessage:showEmptyMessage,hideEmptyMessage:function(root){root.find(SELECTORS_EMPTY_MESSAGE).addClass("hidden")},showContent:showContent,hideContent:hideContent}}));
//# sourceMappingURL=message_drawer_lazy_load_list.min.js.map
File diff suppressed because one or more lines are too long
+14
View File
@@ -0,0 +1,14 @@
/**
* A simple router for the message drawer that allows navigating between
* the "pages" in the drawer.
*
* This module will maintain a linear history of the unique pages access
* to allow navigating back.
*
* @module core_message/message_drawer_router
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_router",["jquery","core/pubsub","core/str","core_message/message_drawer_events","core/aria","core/pending"],(function($,PubSub,Str,MessageDrawerEvents,Aria,PendingPromise){var routes={},history={},SELECTORS_CAN_RECEIVE_FOCUS='input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',SELECTORS_ROUTES_BACK="[data-route-back]",changeRoute=function(namespace,newRoute){var newConfig,pendingPromise=new PendingPromise("message-drawer-router-".concat(namespace,"-").concat(newRoute)),fromPanel=[].slice.call(arguments).some((function(arg){return"frompanel"==arg})),args=[].slice.call(arguments,2),renderPromise=$.Deferred().resolve().promise();if(Object.keys(routes[namespace]).forEach((function(route){var config=routes[namespace][route],isMatch=route===newRoute;isMatch&&(newConfig=config),config.parameters.forEach((function(element){"object"==typeof element&&null!==element&&(element.removeClass("previous"),element.attr("data-from-panel",!1),isMatch?(fromPanel&&element.attr("data-from-panel",!0),element.removeClass("hidden"),Aria.unhide(element.get())):element.attr("data-in-panel")&&"view-search"!=newRoute&&"view-overview"!=newRoute||(element.addClass("hidden"),Aria.hide(element.get())))}))})),newConfig&&newConfig.onGo){renderPromise=newConfig.onGo.apply(void 0,newConfig.parameters.concat(args));for(var currentFocusElement=$(document.activeElement),hasFocus=!1,firstFocusable=null,i=1;i<newConfig.parameters.length;i++){var element=newConfig.parameters[i];if("object"==typeof element&&null!==element&&(firstFocusable||(firstFocusable=element),element.has(currentFocusElement).length)){hasFocus=!0;break}}hasFocus||firstFocusable.find(SELECTORS_CAN_RECEIVE_FOCUS).filter(":visible").first().focus()}var record={route:newRoute,params:args,renderPromise:renderPromise};return PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED,record),renderPromise.then((()=>pendingPromise.resolve())),record},go=function(namespace){var currentFocusElement=$(document.activeElement),record=changeRoute.apply(namespace,arguments),inHistory=!1;history[namespace]||(history[namespace]=[]),history[namespace]=history[namespace].reduce((function(carry,previous){return previous.route===record.route&&(inHistory=!0),inHistory||carry.push(previous),carry}),[]);var historylength=history[namespace].length,previousRecord=historylength?history[namespace][historylength-1]:null;if(previousRecord){for(var prevConfig=routes[namespace][previousRecord.route],elements=prevConfig.parameters,i=1;i<elements.length;i++)"object"==typeof elements[i]&&null!==elements[i]&&elements[i].addClass("previous");previousRecord.focusElement=currentFocusElement,prevConfig.getDescription&&prevConfig.getDescription.apply(null,prevConfig.parameters.concat(previousRecord.params)).then((function(description){return Str.get_string("backto","core_message",description)})).then((function(label){return record.renderPromise.then((function(){routes[namespace][record.route].parameters.forEach((function(element){"object"==typeof element&&element&&element.find(SELECTORS_ROUTES_BACK).attr("aria-label",label)}))}))})).catch((function(){}))}return history[namespace].push(record),record};return{add:function(namespace,route,parameters,onGo,getDescription){routes[namespace]||(routes[namespace]=[]),routes[namespace][route]={parameters:parameters,onGo:onGo,getDescription:getDescription}},go:go,back:function(namespace){if(history[namespace].length){history[namespace].pop();var previous=history[namespace].pop();previous&&(go.apply(void 0,[namespace,previous.route].concat(previous.params)),window.setTimeout((function(){previous.focusElement.focus()}),50))}}}}));
//# sourceMappingURL=message_drawer_router.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Available routes for the message drawer.
*
* @module core_message/message_drawer_routes
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_routes",[],(function(){return{VIEW_CONTACT:"view-contact",VIEW_CONTACTS:"view-contacts",VIEW_CONVERSATION:"view-conversation",VIEW_GROUP_INFO:"view-group-info",VIEW_OVERVIEW:"view-overview",VIEW_SEARCH:"view-search",VIEW_SETTINGS:"view-settings"}}));
//# sourceMappingURL=message_drawer_routes.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_drawer_routes.min.js","sources":["../src/message_drawer_routes.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Available routes for the message drawer.\n *\n * @module core_message/message_drawer_routes\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n return {\n VIEW_CONTACT: 'view-contact',\n VIEW_CONTACTS: 'view-contacts',\n VIEW_CONVERSATION: 'view-conversation',\n VIEW_GROUP_INFO: 'view-group-info',\n VIEW_OVERVIEW: 'view-overview',\n VIEW_SEARCH: 'view-search',\n VIEW_SETTINGS: 'view-settings'\n };\n});\n"],"names":["define","VIEW_CONTACT","VIEW_CONTACTS","VIEW_CONVERSATION","VIEW_GROUP_INFO","VIEW_OVERVIEW","VIEW_SEARCH","VIEW_SETTINGS"],"mappings":";;;;;;;AAsBAA,4CAAO,IAAI,iBACA,CACHC,aAAc,eACdC,cAAe,gBACfC,kBAAmB,oBACnBC,gBAAiB,kBACjBC,cAAe,gBACfC,YAAa,cACbC,cAAe"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the contact page in the message drawer.
*
* @module core_message/message_drawer_view_contact
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_contact",["jquery","core/str","core/templates"],(function($,Str,Templates){var SELECTORS_CONTENT_CONTAINER='[data-region="content-container"]',TEMPLATES_CONTENT="core_message/message_drawer_view_contact_body_content",getContentContainer=function(root){return root.find(SELECTORS_CONTENT_CONTAINER)};return{show:function(namespace,header,body,footer,contact){var root=$(body);return getContentContainer(root).empty(),function(root,profile){return Templates.render(TEMPLATES_CONTENT,profile).then((function(html){return getContentContainer(root).append(html),html}))}(root,contact)},description:function(root,contact){return Str.get_string("messagedrawerviewcontact","core_message",contact.fullname)}}}));
//# sourceMappingURL=message_drawer_view_contact.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_drawer_view_contact.min.js","sources":["../src/message_drawer_view_contact.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Controls the contact page in the message drawer.\n *\n * @module core_message/message_drawer_view_contact\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/str',\n 'core/templates'\n],\nfunction(\n $,\n Str,\n Templates\n) {\n\n var SELECTORS = {\n CONTENT_CONTAINER: '[data-region=\"content-container\"]'\n };\n\n var TEMPLATES = {\n CONTENT: 'core_message/message_drawer_view_contact_body_content'\n };\n\n /**\n * Get the content container of the contact view container.\n *\n * @param {Object} root Contact container element.\n * @returns {Object} jQuery object\n */\n var getContentContainer = function(root) {\n return root.find(SELECTORS.CONTENT_CONTAINER);\n };\n\n /**\n * Render the contact profile in the content container.\n *\n * @param {Object} root Contact container element.\n * @param {Object} profile Contact profile details.\n * @returns {Object} jQuery promise\n */\n var render = function(root, profile) {\n return Templates.render(TEMPLATES.CONTENT, profile)\n .then(function(html) {\n getContentContainer(root).append(html);\n return html;\n });\n };\n\n /**\n * Setup the contact page.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Contact header element.\n * @param {Object} body Contact body container element.\n * @param {Object} footer Contact footer container element.\n * @param {Object} contact The contact object.\n * @returns {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, contact) {\n var root = $(body);\n\n getContentContainer(root).empty();\n return render(root, contact);\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @param {Object} root Contact container element.\n * @param {Object} contact The contact object.\n * @return {Object} jQuery promise\n */\n var description = function(root, contact) {\n return Str.get_string('messagedrawerviewcontact', 'core_message', contact.fullname);\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","Str","Templates","SELECTORS","TEMPLATES","getContentContainer","root","find","show","namespace","header","body","footer","contact","empty","profile","render","then","html","append","description","get_string","fullname"],"mappings":";;;;;;;AAsBAA,kDACA,CACI,SACA,WACA,mBAEJ,SACIC,EACAC,IACAC,eAGIC,4BACmB,oCAGnBC,kBACS,wDASTC,oBAAsB,SAASC,aACxBA,KAAKC,KAAKJ,oCA8Cd,CACHK,KAnBO,SAASC,UAAWC,OAAQC,KAAMC,OAAQC,aAC7CP,KAAON,EAAEW,aAEbN,oBAAoBC,MAAMQ,QArBjB,SAASR,KAAMS,gBACjBb,UAAUc,OAAOZ,kBAAmBW,SACtCE,MAAK,SAASC,aACXb,oBAAoBC,MAAMa,OAAOD,MAC1BA,QAkBRF,CAAOV,KAAMO,UAgBpBO,YANc,SAASd,KAAMO,gBACtBZ,IAAIoB,WAAW,2BAA4B,eAAgBR,QAAQS"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the contacts page of the message drawer.
*
* @module core_message/message_drawer_view_contacts
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_contacts",["jquery","core/pubsub","core/str","core_message/message_drawer_events","core_message/message_drawer_view_contacts_section_contacts","core_message/message_drawer_view_contacts_section_requests"],(function($,PubSub,Str,MessageDrawerEvents,ContactsSection,RequestsSection){var SELECTORS_ACTION_SHOW_CONTACTS_SECTION='[data-action="show-contacts-section"]',SELECTORS_ACTION_SHOW_REQUESTS_SECTION='[data-action="show-requests-section"]',SELECTORS_CONTACT_REQUEST_COUNT='[data-region="contact-request-count"]',SELECTORS_CONTACTS_SECTION_CONTAINER='[data-section="contacts"]',SELECTORS_REQUESTS_SECTION_CONTAINER='[data-section="requests"]',getContactsSectionContainer=function(body){return body.find(SELECTORS_CONTACTS_SECTION_CONTAINER)},getRequestsSectionContainer=function(body){return body.find(SELECTORS_REQUESTS_SECTION_CONTAINER)},getShowContactsAction=function(body){return body.find(SELECTORS_ACTION_SHOW_CONTACTS_SECTION)},getShowRequestsAction=function(body){return body.find(SELECTORS_ACTION_SHOW_REQUESTS_SECTION)},decrementContactRequestCount=function(body){return function(){var countContainer=body.find(SELECTORS_CONTACT_REQUEST_COUNT),count=parseInt(countContainer.text(),10);(count=isNaN(count)?0:count-1)<=0?countContainer.addClass("hidden"):countContainer.text(count)}};return{show:function(namespace,header,body,footer,tab){(body=$(body)).attr("data-contacts-init")||(!function(body){var contactsSection=getContactsSectionContainer(body),requestsSection=getRequestsSectionContainer(body),showContactsAction=getShowContactsAction(body),showRequestsAction=getShowRequestsAction(body);showContactsAction.on("show.bs.tab",(function(){ContactsSection.show(contactsSection)})),showRequestsAction.on("show.bs.tab",(function(){RequestsSection.show(requestsSection)})),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,decrementContactRequestCount(body)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,decrementContactRequestCount(body))}(body),body.attr("data-contacts-init",!0));var contactsSection=getContactsSectionContainer(body),requestsSection=getRequestsSectionContainer(body);if(tab){var showContactsAction=getShowContactsAction(body),showRequestsAction=getShowRequestsAction(body);"requests"==tab?(showContactsAction.removeClass("active"),contactsSection.removeClass("show active"),showRequestsAction.addClass("active"),requestsSection.addClass("show active")):(showRequestsAction.removeClass("active"),requestsSection.removeClass("show active"),showContactsAction.addClass("active"),contactsSection.addClass("show active"))}return contactsSection.hasClass("active")?ContactsSection.show(contactsSection):RequestsSection.show(requestsSection),$.Deferred().resolve().promise()},description:function(){return Str.get_string("messagedrawerviewcontacts","core_message")}}}));
//# sourceMappingURL=message_drawer_view_contacts.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
/**
* Controls the contacts section of the contacts page.
*
* @module core_message/message_drawer_view_contacts_section_contacts
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_contacts_section_contacts",["jquery","core/notification","core/pubsub","core/templates","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_lazy_load_list"],(function($,Notification,PubSub,Templates,MessageRepository,Events,LazyLoadList){var SELECTORS_BLOCK_ICON_CONTAINER='[data-region="block-icon-container"]',SELECTORS_CONTACT='[data-region="contact"]',TEMPLATES_CONTACTS_LIST="core_message/message_drawer_contacts_list",findContact=function(body,userId){return body.find('[data-contact-user-id="'+userId+'"]')},render=function(contentContainer,contacts){var formattedContacts=contacts.map((function(contact){return $.extend(contact,{id:contact.userid})}));return Templates.render(TEMPLATES_CONTACTS_LIST,{contacts:formattedContacts}).then((function(html){return contentContainer.append(html),html})).catch(Notification.exception)},registerEventListeners=function(root){PubSub.subscribe(Events.CONTACT_ADDED,(function(profile){var listContentContainer=LazyLoadList.getContentContainer(root);render(listContentContainer,[profile]),LazyLoadList.hideEmptyMessage(root),LazyLoadList.showContent(root)})),PubSub.subscribe(Events.CONTACT_REMOVED,(function(userId){!function(body,userId){findContact(body,userId).remove()}(root,userId),root.find(SELECTORS_CONTACT).length||(LazyLoadList.hideContent(root),LazyLoadList.showEmptyMessage(root))})),PubSub.subscribe(Events.CONTACT_BLOCKED,(function(userId){!function(body,userId){var contact=findContact(body,userId);contact.length&&contact.find(SELECTORS_BLOCK_ICON_CONTAINER).removeClass("hidden")}(root,userId)})),PubSub.subscribe(Events.CONTACT_UNBLOCKED,(function(userId){!function(body,userId){var contact=findContact(body,userId);contact.length&&contact.find(SELECTORS_BLOCK_ICON_CONTAINER).addClass("hidden")}(root,userId)}))};return{show:function(root){var offset;root.attr("data-contacts-init")||(registerEventListeners(root),root.attr("data-contacts-init",!0)),LazyLoadList.show(root,(offset=0,function(listRoot,userId){return MessageRepository.getContacts(userId,101,offset).then((function(result){return result})).then((function(contacts){return contacts.length>100?contacts.pop():LazyLoadList.setLoadedAll(listRoot,!0),contacts})).then((function(contacts){return offset+=100,contacts})).catch(Notification.exception)}),render)}}}));
//# sourceMappingURL=message_drawer_view_contacts_section_contacts.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
/**
* Controls the requests section of the contacts page.
*
* @module core_message/message_drawer_view_contacts_section_requests
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_contacts_section_requests",["jquery","core/notification","core/pubsub","core/templates","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_lazy_load_list"],(function($,Notification,PubSub,Templates,MessageRepository,MessageDrawerEvents,LazyLoadList){var SELECTORS_CONTACT_REQUEST='[data-region="contact-request"]',TEMPLATES_REQUESTS_LIST="core_message/message_drawer_view_contacts_body_section_requests_list",render=function(contentContainer,requests){var formattedRequests=requests.map((function(request){return{id:request.id,profileimageurl:request.profileimageurl,fullname:request.fullname}}));return Templates.render(TEMPLATES_REQUESTS_LIST,{requests:formattedRequests}).then((function(html){return contentContainer.append(html),html})).catch(Notification.exception)},load=function(listRoot,userId){return MessageRepository.getContactRequests(userId).then((function(requests){return LazyLoadList.setLoadedAll(listRoot,!0),requests})).catch(Notification.exception)},handleContactRequestProcessed=function(root){return function(request){root.find('[data-request-id="'+request.userid+'"]').remove(),root.find(SELECTORS_CONTACT_REQUEST).length||(LazyLoadList.showEmptyMessage(root),LazyLoadList.hideContent(root))}};return{show:function(root){root.attr("data-contacts-init")||(!function(root){PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,handleContactRequestProcessed(root)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,handleContactRequestProcessed(root))}(root),root.attr("data-contacts-init",!0)),LazyLoadList.show(root,load,render)}}}));
//# sourceMappingURL=message_drawer_view_contacts_section_requests.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the group info page of the message drawer.
*
* @module core_message/message_drawer_view_group_info
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_group_info",["jquery","core/str","core/templates","core_message/message_repository","core_message/message_drawer_lazy_load_list"],(function($,Str,Templates,Repository,LazyLoadList){var SELECTORS_CONTENT_CONTAINER='[data-region="group-info-content-container"]',TEMPLATES_CONTENT="core_message/message_drawer_view_group_info_body_content",TEMPLATES_MEMBERS_LIST="core_message/message_drawer_view_group_info_participants_list",getContentContainer=function(root){return root.find(SELECTORS_CONTENT_CONTAINER)},renderMembersCallback=function(contentContainer,members){return Templates.render(TEMPLATES_MEMBERS_LIST,{contacts:members}).then((function(html){return contentContainer.append(html),html}))};return{show:function(namespace,header,body,footer,conversation,loggedInUserId){var root=$(body);return getContentContainer(root).empty(),function(root,conversation,loggedInUserId){var placeholderCount=conversation.totalMemberCount>50?50:conversation.totalMemberCount,placeholders=Array.apply(null,Array(placeholderCount)).map((function(){return!0})),templateContext={name:conversation.name,subname:conversation.subname,imageurl:conversation.imageUrl,placeholders:placeholders,loggedinuser:{id:loggedInUserId}};return Templates.render(TEMPLATES_CONTENT,templateContext).then((function(html){return getContentContainer(root).append(html),html}))}(root,conversation,loggedInUserId).then((function(){var listRoot=LazyLoadList.getRoot(root);LazyLoadList.show(listRoot,function(conversation,limit,offset){return function(root,userId){return Repository.getConversationMembers(conversation.id,userId,limit+1,offset).then((function(members){return members.length>limit?members=members.slice(0,-1):LazyLoadList.setLoadedAll(root,!0),offset+=limit,members.filter((function(member){return member.id!=userId}))}))}}(conversation,50,0),renderMembersCallback)}))},description:function(root,conversation){return Str.get_string("messagedrawerviewgroupinfo","core_message",conversation.name)}}}));
//# sourceMappingURL=message_drawer_view_group_info.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the overview page of the message drawer.
*
* @module core_message/message_drawer_view_overview
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_overview",["jquery","core/key_codes","core/pubsub","core/str","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_events","core_message/message_drawer_view_overview_section","core_message/message_repository","core_message/message_drawer_view_conversation_constants"],(function($,KeyCodes,PubSub,Str,Router,Routes,MessageDrawerEvents,Section,MessageRepository,Constants){var SELECTORS_CONTACT_REQUEST_COUNT='[data-region="contact-request-count"]',SELECTORS_FAVOURITES='[data-region="view-overview-favourites"]',SELECTORS_GROUP_MESSAGES='[data-region="view-overview-group-messages"]',SELECTORS_MESSAGES='[data-region="view-overview-messages"]',SELECTORS_SEARCH_INPUT='[data-region="view-overview-search-input"]',SELECTORS_SECTION_TOGGLE_BUTTON="[data-toggle]",OVERVIEW_SECTION_TYPES={PRIVATE:[Constants.CONVERSATION_TYPES.PRIVATE,Constants.CONVERSATION_TYPES.SELF],PUBLIC:[Constants.CONVERSATION_TYPES.PUBLIC],FAVOURITE:null},loadAllCountsPromise=null,filterCountsByTypes=function(counts,types,includeFavourites){var total=0;return types&&types.length&&(total=types.reduce((function(carry,type){return carry+counts.types[type]}),total)),includeFavourites&&(total+=counts.favourites),total},getSearchInput=function(header){return header.find(SELECTORS_SEARCH_INPUT)},decrementContactRequestCount=function(header){return function(){var countContainer=header.find(SELECTORS_CONTACT_REQUEST_COUNT),count=parseInt(countContainer.text(),10);(count=isNaN(count)?0:count-1)<=0?countContainer.addClass("hidden"):countContainer.text(count)}};return{show:function(namespace,header,body){header.attr("data-init")||(!function(namespace,header){var searchInput=getSearchInput(header),ignoredKeys=[KeyCodes.tab,KeyCodes.shift,KeyCodes.ctrl,KeyCodes.alt];searchInput.on("click",(function(){Router.go(namespace,Routes.VIEW_SEARCH)})),searchInput.on("keydown",(function(e){ignoredKeys.indexOf(e.keyCode)<0&&"Meta"!=e.key&&Router.go(namespace,Routes.VIEW_SEARCH)})),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,decrementContactRequestCount(header)),PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,decrementContactRequestCount(header))}(namespace,header),header.attr("data-init",!0));var fromPanel=header.attr("data-in-panel")?"frompanel":null;getSearchInput(header).val("");var loggedInUserId=function(body){return body.attr("data-user-id")}(body),allCounts=function(loggedInUserId){return null===loadAllCountsPromise&&(loadAllCountsPromise=MessageRepository.getAllConversationCounts(loggedInUserId)),loadAllCountsPromise}(loggedInUserId),sections=[[body.find(SELECTORS_FAVOURITES),OVERVIEW_SECTION_TYPES.FAVOURITE,!0],[body.find(SELECTORS_GROUP_MESSAGES),OVERVIEW_SECTION_TYPES.PUBLIC,!1],[body.find(SELECTORS_MESSAGES),OVERVIEW_SECTION_TYPES.PRIVATE,!1]];return sections.forEach((function(args){var sectionRoot=args[0],sectionTypes=args[1],includeFavourites=args[2],totalCountPromise=allCounts.then((function(result){return filterCountsByTypes(result.total,sectionTypes,includeFavourites)})),unreadCountPromise=allCounts.then((function(result){return filterCountsByTypes(result.unread,sectionTypes,includeFavourites)}));Section.show(namespace,null,sectionRoot,null,sectionTypes,includeFavourites,totalCountPromise,unreadCountPromise,fromPanel)})),allCounts.then((function(result){return function(sections){sections.some((function(section){var sectionRoot=section[0];return Section.isVisible(sectionRoot)}))||(sections.sort((function(a,b){var aTotal=a[1],aUnread=a[2],bTotal=b[1],bUnread=b[2];return aUnread>0&&0==bUnread?-1:0==aUnread&&bUnread>0?1:aTotal>0&&0==bTotal?-1:0==aTotal&&bTotal>0?1:0})),sections[0][0].find(SELECTORS_SECTION_TOGGLE_BUTTON).click())}(sections.map((function(section){var sectionRoot=section[0],sectionTypes=section[1],includeFavourites=section[2];return[sectionRoot,filterCountsByTypes(result.total,sectionTypes,includeFavourites),filterCountsByTypes(result.unread,sectionTypes,includeFavourites)]})))}))},description:function(){return Str.get_string("messagedrawerviewoverview","core_message")}}}));
//# sourceMappingURL=message_drawer_view_overview.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the settings page in the message drawer.
*
* @module core_message/message_drawer_view_settings
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_drawer_view_settings",["jquery","core/notification","core/str","core/pubsub","core/templates","core_message/message_repository","core/custom_interaction_events","core_message/message_drawer_events"],(function($,Notification,Str,PubSub,Templates,Repository,CustomEvents,MessageDrawerEvents){var SELECTORS_CHECKBOX='input[type="checkbox"]',SELECTORS_SETTINGS='[data-region="settings"]',SELECTORS_PRIVACY_PREFERENCE='[data-preference="blocknoncontacts"] input[type="radio"]',SELECTORS_NOTIFICATIONS_PREFERENCE='[data-preference="notifications"] input[type="checkbox"]',SELECTORS_ENTER_TO_SEND_PREFERENCE='[data-preference="entertosend"] input[type="checkbox"]',SELECTORS_NOTIFICATION_PREFERENCES_CONTAINER='[data-region="notification-preference-container"]',SELECTORS_CONTENT_CONTAINER='[data-region="content-container"]',SELECTORS_PLACEHOLDER_CONTAINER='[data-region="placeholder-container"]',TEMPLATES_NOTIFICATION_PREFERENCES="core_message/message_drawer_view_settings_body_content_notification_preferences",savePreferences=function(loggedInUserId,preferences){return Repository.savePreferences(loggedInUserId,preferences).then((function(){PubSub.publish(MessageDrawerEvents.PREFERENCES_UPDATED,preferences)})).catch(Notification.exception)},init=function(body,loggedInUserId){Repository.getUserMessagePreferences(loggedInUserId).then((function(response){!function(body,value){body.find(SELECTORS_PRIVACY_PREFERENCE).each((function(index,input){(input=$(input)).val()==value?input.prop("checked",!0):input.prop("checked",!1)}))}(body,response.blocknoncontacts),function(body,value){var checkbox=body.find(SELECTORS_ENTER_TO_SEND_PREFERENCE);value?checkbox.prop("checked",!0):checkbox.prop("checked",!1)}(body,response.entertosend);var notificationProcessors=[];response.preferences.components.length&&response.preferences.components.forEach((function(component){if(component.notifications.length&&component.notifications.filter((function(notification){return"message_provider_moodle_instantmessage"==notification.preferencekey})).length){var configuration=component.notifications[0];notificationProcessors=configuration.processors.map((function(processor){var checked=processor.enabled;return{displayname:processor.displayname,name:processor.name,checked:checked,locked:processor.locked,lockedmessage:processor.lockedmessage||null}}))}}));var container=body.find(SELECTORS_NOTIFICATION_PREFERENCES_CONTAINER);return!notificationProcessors.length||(container.removeClass("hidden"),Templates.render(TEMPLATES_NOTIFICATION_PREFERENCES,{processors:notificationProcessors}).then((function(html){return container.append(html),html})))})).then((function(){body.find(SELECTORS_CONTENT_CONTAINER).removeClass("hidden"),body.find(SELECTORS_PLACEHOLDER_CONTAINER).addClass("hidden"),function(body,loggedInUserId){var settingsContainer=body.find(SELECTORS_SETTINGS);CustomEvents.define(settingsContainer,[CustomEvents.events.activate]),settingsContainer.on(CustomEvents.events.activate,SELECTORS_NOTIFICATIONS_PREFERENCE,(function(e){var checkboxes=$(e.target).closest(SELECTORS_NOTIFICATION_PREFERENCES_CONTAINER).find(SELECTORS_CHECKBOX);if(checkboxes.length){var values=checkboxes.toArray().reduce((function(carry,checkbox){return(checkbox=$(checkbox)).prop("checked")&&carry.push(checkbox.attr("data-name")),carry}),[]),newValue=values.length?values.join(","):"none";savePreferences(loggedInUserId,[{type:"message_provider_moodle_instantmessage_enabled",value:newValue}])}})),settingsContainer.on("change",SELECTORS_PRIVACY_PREFERENCE,(function(e){var newValue=$(e.target).val();savePreferences(loggedInUserId,[{type:"message_blocknoncontacts",value:newValue}])})),settingsContainer.on(CustomEvents.events.activate,SELECTORS_ENTER_TO_SEND_PREFERENCE,(function(e){var newValue=$(e.target).prop("checked");savePreferences(loggedInUserId,[{type:"message_entertosend",value:newValue}])}))}(body,loggedInUserId)})).catch(Notification.exception)};return{show:function(namespace,header,body,footer,loggedInUserId){return body.attr("data-init")||(init(body,loggedInUserId),body.attr("data-init",!0)),$.Deferred().resolve().promise()},description:function(){return Str.get_string("messagedrawerviewsettings","core_message")}}}));
//# sourceMappingURL=message_drawer_view_settings.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
/**
* Controls the preference for an individual notification type on the
* message preference page.
*
* @module core_message/message_notification_preference
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_notification_preference",["jquery","core_message/notification_preference"],(function($,NotificationPreference){var SELECTORS_PREFERENCE_KEY="[data-preference-key]",MessageNotificationPreference=function(element,userId){NotificationPreference.call(this,element,userId)};return(MessageNotificationPreference.prototype=Object.create(NotificationPreference.prototype)).constructor=MessageNotificationPreference,MessageNotificationPreference.prototype.getPreferenceKey=function(){return this.root.find(SELECTORS_PREFERENCE_KEY).attr("data-preference-key")},MessageNotificationPreference}));
//# sourceMappingURL=message_notification_preference.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_notification_preference.min.js","sources":["../src/message_notification_preference.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Controls the preference for an individual notification type on the\n * message preference page.\n *\n * @module core_message/message_notification_preference\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core_message/notification_preference'],\n function($, NotificationPreference) {\n\n var SELECTORS = {\n PREFERENCE_KEY: '[data-preference-key]',\n };\n\n /**\n * Constructor for the Preference.\n *\n * @class\n * @param {object} element jQuery object root element of the preference\n * @param {int} userId The current user id\n */\n var MessageNotificationPreference = function(element, userId) {\n NotificationPreference.call(this, element, userId);\n };\n\n /**\n * Clone the parent prototype.\n */\n MessageNotificationPreference.prototype = Object.create(NotificationPreference.prototype);\n\n /**\n * Set constructor.\n */\n MessageNotificationPreference.prototype.constructor = MessageNotificationPreference;\n\n /**\n * Get the unique prefix key that identifies this user preference.\n *\n * @method getPreferenceKey\n * @return {string}\n */\n MessageNotificationPreference.prototype.getPreferenceKey = function() {\n return this.root.find(SELECTORS.PREFERENCE_KEY).attr('data-preference-key');\n };\n\n return MessageNotificationPreference;\n});\n"],"names":["define","$","NotificationPreference","SELECTORS","MessageNotificationPreference","element","userId","call","this","prototype","Object","create","constructor","getPreferenceKey","root","find","attr"],"mappings":";;;;;;;;AAuBAA,sDAAO,CAAC,SAAU,yCACV,SAASC,EAAGC,4BAEZC,yBACgB,wBAUhBC,8BAAgC,SAASC,QAASC,QAClDJ,uBAAuBK,KAAKC,KAAMH,QAASC,gBAM/CF,8BAA8BK,UAAYC,OAAOC,OAAOT,uBAAuBO,YAKvCG,YAAcR,8BAQtDA,8BAA8BK,UAAUI,iBAAmB,kBAChDL,KAAKM,KAAKC,KAAKZ,0BAA0Ba,KAAK,wBAGlDZ"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the message popover in the nav bar.
*
* @module core_message/message_popover
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @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)}}}));
//# sourceMappingURL=message_popover.min.js.map
@@ -0,0 +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 <http://www.gnu.org/licenses/>.\n\n/**\n * Controls the message popover in the nav bar.\n *\n * @module core_message/message_popover\n * @copyright 2018 Ryan Wyllie <ryan@moodle.com>\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"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Controls the message preference page.
*
* @module core_message/message_preferences
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_preferences",["jquery","core/ajax","core/notification","core_message/message_notification_preference","core/custom_interaction_events"],(function($,Ajax,Notification,MessageNotificationPreference,CustomEvents){var SELECTORS_PREFERENCE="[data-state]",SELECTORS_PREFERENCES_CONTAINER='[data-region="preferences-container"]',SELECTORS_CONTACTABLE_PRIVACY_CONTAINER='[data-region="privacy-setting-container"]',MessagePreferences=function(element){this.root=$(element),this.userId=this.root.find(SELECTORS_PREFERENCES_CONTAINER).attr("data-user-id"),this.registerEventListeners()};return MessagePreferences.prototype.preferencesDisabled=function(){return this.root.find(SELECTORS_PREFERENCES_CONTAINER).hasClass("disabled")},MessagePreferences.prototype.saveContactablePrivacySetting=function(){var container=this.root.find(SELECTORS_CONTACTABLE_PRIVACY_CONTAINER),value=$("input[type='radio']:checked").val();if(container.hasClass("loading"))return $.Deferred().resolve();container.addClass("loading");var request={methodname:"core_user_update_user_preferences",args:{userid:this.userId,preferences:[{type:container.attr("data-preference-key"),value:value}]}};return Ajax.call([request])[0].fail(Notification.exception).always((function(){container.removeClass("loading")}))},MessagePreferences.prototype.registerEventListeners=function(){CustomEvents.define(this.root,[CustomEvents.events.activate]),this.root.on("change",function(e){if("message_blocknoncontacts"==e.target.name)this.saveContactablePrivacySetting();else if(!this.preferencesDisabled()){var preferencesContainer=$(e.target).closest(SELECTORS_PREFERENCES_CONTAINER),preferenceElement=$(e.target).closest(SELECTORS_PREFERENCE),messagePreference=new MessageNotificationPreference(preferencesContainer,this.userId);preferenceElement.addClass("loading"),messagePreference.save().always((function(){preferenceElement.removeClass("loading")}))}}.bind(this))},MessagePreferences}));
//# sourceMappingURL=message_preferences.min.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
define("core_message/message_send_bulk",["exports","core/str","core/modal_save_cancel","core/templates","core/modal_events","core/ajax","core/notification"],(function(_exports,_str,_modal_save_cancel,_templates,_modal_events,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* Send bulk message to the given user ids.
*
* @module core_message/message_send_bulk
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showModal=_exports.sendMessage=void 0,_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_templates=_interopRequireDefault(_templates),_modal_events=_interopRequireDefault(_modal_events),_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);_exports.showModal=function(users){let callback=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;if(!users.length)return Promise.resolve();let titlePromise=null;return titlePromise=1==users.length?(0,_str.get_string)("sendbulkmessagesingle","core_message"):(0,_str.get_string)("sendbulkmessage","core_message",users.length),_modal_save_cancel.default.create({body:_templates.default.render("core_message/send_bulk_message",{}),title:titlePromise,show:!0,buttons:{save:titlePromise}}).then((function(modal){return modal.getRoot().on(_modal_events.default.hidden,(function(){callback&&callback(),modal.getRoot().remove()})),modal.getRoot().on(_modal_events.default.save,(function(){let messageText=modal.getRoot().find("form textarea").val();sendMessage(messageText,users)})),modal}))};const sendMessage=(messageText,users)=>{let messages=[];return users.forEach((user=>{messages.push({touserid:user,text:messageText})})),_ajax.default.call([{methodname:"core_message_send_instant_messages",args:{messages:messages}}])[0].then((function(messageIds){return 1==messageIds.length?(0,_str.get_string)("sendbulkmessagesentsingle","core_message"):(0,_str.get_string)("sendbulkmessagesent","core_message",messageIds.length)})).then((function(msg){return _notification.default.addNotification({message:msg,type:"success"}),!0})).catch(_notification.default.exception)};_exports.sendMessage=sendMessage}));
//# sourceMappingURL=message_send_bulk.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"message_send_bulk.min.js","sources":["../src/message_send_bulk.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Send bulk message to the given user ids.\n *\n * @module core_message/message_send_bulk\n * @copyright 2019 Shamim Rezaie <shamim@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {get_string} from 'core/str';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport Templates from 'core/templates';\nimport ModalEvents from 'core/modal_events';\nimport Ajax from 'core/ajax';\nimport Notification from 'core/notification';\n\n/**\n * Show the send message popup.\n *\n * @method showModal\n * @param {int[]} users\n * @param {Function} callback A callback to apply after the form is closed.\n * @returns {Promise}\n */\nexport const showModal = (users, callback = null) => {\n if (!users.length) {\n // Nothing to do.\n return Promise.resolve();\n }\n let titlePromise = null;\n if (users.length == 1) {\n titlePromise = get_string('sendbulkmessagesingle', 'core_message');\n } else {\n titlePromise = get_string('sendbulkmessage', 'core_message', users.length);\n }\n\n return ModalSaveCancel.create({\n body: Templates.render('core_message/send_bulk_message', {}),\n title: titlePromise,\n show: true,\n buttons: {\n save: titlePromise,\n },\n })\n .then(function(modal) {\n // When the dialog is closed, perform the callback (if provided).\n modal.getRoot().on(ModalEvents.hidden, function() {\n if (callback) {\n callback();\n }\n modal.getRoot().remove();\n });\n\n modal.getRoot().on(ModalEvents.save, function() {\n let messageText = modal.getRoot().find('form textarea').val();\n sendMessage(messageText, users);\n });\n\n return modal;\n });\n};\n\n/**\n * Send a message to these users.\n *\n * @method sendMessage\n * @param {String} messageText\n * @param {Number[]} users\n * @returns {Promise}\n */\nexport const sendMessage = (messageText, users) => {\n let messages = [];\n\n users.forEach(user => {\n messages.push({\n touserid: user,\n text: messageText\n });\n });\n\n return Ajax.call([{\n methodname: 'core_message_send_instant_messages',\n args: {messages: messages}\n }])[0]\n .then(function(messageIds) {\n if (messageIds.length == 1) {\n return get_string('sendbulkmessagesentsingle', 'core_message');\n } else {\n return get_string('sendbulkmessagesent', 'core_message', messageIds.length);\n }\n })\n .then(function(msg) {\n Notification.addNotification({\n message: msg,\n type: \"success\"\n });\n return true;\n })\n .catch(Notification.exception);\n};\n"],"names":["users","callback","length","Promise","resolve","titlePromise","ModalSaveCancel","create","body","Templates","render","title","show","buttons","save","then","modal","getRoot","on","ModalEvents","hidden","remove","messageText","find","val","sendMessage","messages","forEach","user","push","touserid","text","Ajax","call","methodname","args","messageIds","msg","addNotification","message","type","catch","Notification","exception"],"mappings":";;;;;;;uXAqCyB,SAACA,WAAOC,gEAAW,SACnCD,MAAME,cAEAC,QAAQC,cAEfC,aAAe,YAEfA,aADgB,GAAhBL,MAAME,QACS,mBAAW,wBAAyB,iBAEpC,mBAAW,kBAAmB,eAAgBF,MAAME,QAGhEI,2BAAgBC,OAAO,CAC1BC,KAAMC,mBAAUC,OAAO,iCAAkC,IACzDC,MAAON,aACPO,MAAM,EACNC,QAAS,CACLC,KAAMT,gBAGbU,MAAK,SAASC,cAEXA,MAAMC,UAAUC,GAAGC,sBAAYC,QAAQ,WAC/BnB,UACAA,WAEJe,MAAMC,UAAUI,YAGpBL,MAAMC,UAAUC,GAAGC,sBAAYL,MAAM,eAC7BQ,YAAcN,MAAMC,UAAUM,KAAK,iBAAiBC,MACxDC,YAAYH,YAAatB,UAGtBgB,gBAYFS,YAAc,CAACH,YAAatB,aACjC0B,SAAW,UAEf1B,MAAM2B,SAAQC,OACVF,SAASG,KAAK,CACVC,SAAUF,KACVG,KAAMT,iBAIPU,cAAKC,KAAK,CAAC,CACdC,WAAY,qCACZC,KAAM,CAACT,SAAUA,aACjB,GACHX,MAAK,SAASqB,mBACc,GAArBA,WAAWlC,QACJ,mBAAW,4BAA6B,iBAExC,mBAAW,sBAAuB,eAAgBkC,WAAWlC,WAG3Ea,MAAK,SAASsB,kCACEC,gBAAgB,CACzBC,QAASF,IACTG,KAAM,aAEH,KAEVC,MAAMC,sBAAaC"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Module to message a user from their profile page.
*
* @module core_message/message_user_button
* @copyright 2019 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/message_user_button",["jquery","core/custom_interaction_events","core_message/message_drawer_helper","core/templates"],(function($,CustomEvents,MessageDrawerHelper,Templates){var SELECTORS_MESSAGE_TEXTAREA='[data-region="send-message-txt"]',SELECTORS_MESSAGE_USER_BUTTON="#message-user-button",TEMPLATES_CONTENT="core_message/message_jumpto",getUserId=function(element){return parseInt(element.attr("data-userid"))},getConversationId=function(element){return parseInt(element.attr("data-conversationid"))};return{send:function(element){element=$(element);var args={conversationid:getConversationId(element),buttonid:$(element).attr("id"),userid:getUserId(element)};Templates.render(TEMPLATES_CONTENT,{}).then((function(html){element.after(html)})).then((function(){$(SELECTORS_MESSAGE_USER_BUTTON).next().focus((function(){$(SELECTORS_MESSAGE_TEXTAREA).focus()}))})),CustomEvents.define(element,[CustomEvents.events.activate]),element.on(CustomEvents.events.activate,(function(e,data){$(e.target).hasClass("active")?(MessageDrawerHelper.hide(),$(SELECTORS_MESSAGE_USER_BUTTON).next().attr("tabindex",-1)):($(SELECTORS_MESSAGE_USER_BUTTON).next().attr("tabindex",0),args.conversationid?MessageDrawerHelper.showConversation(args):MessageDrawerHelper.createConversationWithUser(args)),$(e.target).focus(),$(e.target).toggleClass("active"),e.preventDefault(),data.originalEvent.preventDefault()}))}}}));
//# sourceMappingURL=message_user_button.min.js.map
File diff suppressed because one or more lines are too long
+11
View File
@@ -0,0 +1,11 @@
/**
* Controls the preference for an individual notification type on the
* message preference page.
*
* @module core_message/notification_preference
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/notification_preference",["jquery","core/ajax","core/notification","core_message/notification_processor"],(function($,Ajax,Notification,NotificationProcessor){const SELECTORS_PROCESSOR="[data-processor-name]",SELECTORS_STATE_INPUTS="[data-state] input",NotificationPreference=function(element,userId){this.root=$(element),this.userId=userId};return NotificationPreference.prototype.getPreferenceKey=function(){return this.root.attr("data-preference-key")},NotificationPreference.prototype.getEnabledPreferenceKey=function(){return this.getPreferenceKey()+"_enabled"},NotificationPreference.prototype.getProcessors=function(){return this.root.find(SELECTORS_PROCESSOR).map((function(index,element){return new NotificationProcessor($(element))}))},NotificationPreference.prototype.startLoading=function(){this.root.addClass("loading"),this.root.find(SELECTORS_STATE_INPUTS).prop("disabled",!0)},NotificationPreference.prototype.stopLoading=function(){this.root.removeClass("loading"),this.root.find(SELECTORS_STATE_INPUTS).prop("disabled",!1)},NotificationPreference.prototype.isLoading=function(){return this.root.hasClass("loading")},NotificationPreference.prototype.save=function(){if(this.isLoading())return $.Deferred().resolve();this.startLoading();let enabledValue="";this.getProcessors().each((function(index,processor){processor.isEnabled()&&(""===enabledValue?enabledValue=processor.getName():enabledValue+=","+processor.getName())})),""===enabledValue&&(enabledValue="none");const request={methodname:"core_user_update_user_preferences",args:{userid:this.userId,preferences:[{type:this.getEnabledPreferenceKey(),value:enabledValue}]}};return Ajax.call([request])[0].fail(Notification.exception).always(function(){this.stopLoading()}.bind(this))},NotificationPreference}));
//# sourceMappingURL=notification_preference.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Represents the notification processor (e.g. email, popup, jabber)
*
* @module core_message/notification_processor
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/notification_processor",["jquery"],(function($){const SELECTORS_STATE_INPUTS=".preference-state input.notification_enabled",NotificationProcessor=function(element){this.root=$(element)};return NotificationProcessor.prototype.getName=function(){return this.root.attr("data-processor-name")},NotificationProcessor.prototype.isEnabled=function(){return this.root.find(SELECTORS_STATE_INPUTS).prop("checked")},NotificationProcessor}));
//# sourceMappingURL=notification_processor.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"notification_processor.min.js","sources":["../src/notification_processor.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Represents the notification processor (e.g. email, popup, jabber)\n *\n * @module core_message/notification_processor\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n const SELECTORS = {\n STATE_INPUTS: '.preference-state input.notification_enabled'\n };\n\n /**\n * Constructor for the notification processor.\n *\n * @class\n * @param {object} element jQuery object root element of the processor\n */\n const NotificationProcessor = function(element) {\n this.root = $(element);\n };\n\n /**\n * Get the processor name.\n *\n * @method getName\n * @return {string}\n */\n NotificationProcessor.prototype.getName = function() {\n return this.root.attr('data-processor-name');\n };\n\n /**\n * Check if the processor is enabled when the user is logged in.\n *\n * @method isLoggedInEnabled\n * @return {bool}\n */\n NotificationProcessor.prototype.isEnabled = function() {\n const enabled = this.root.find(SELECTORS.STATE_INPUTS);\n\n return enabled.prop('checked');\n };\n\n return NotificationProcessor;\n});\n"],"names":["define","$","SELECTORS","NotificationProcessor","element","root","prototype","getName","this","attr","isEnabled","find","prop"],"mappings":";;;;;;;AAsBAA,6CAAO,CAAC,WAAW,SAASC,SAClBC,uBACY,+CASZC,sBAAwB,SAASC,cAC9BC,KAAOJ,EAAEG,iBASlBD,sBAAsBG,UAAUC,QAAU,kBAC/BC,KAAKH,KAAKI,KAAK,wBAS1BN,sBAAsBG,UAAUI,UAAY,kBACxBF,KAAKH,KAAKM,KAAKT,wBAEhBU,KAAK,YAGjBT"}
@@ -0,0 +1,3 @@
define("core_message/notification_processor_settings",["exports","jquery","core/ajax","core/str","core/notification","core/custom_interaction_events","core/modal","core/fragment"],(function(_exports,_jquery,Ajax,Str,Notification,CustomEvents,_modal,Fragment){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),Ajax=_interopRequireWildcard(Ajax),Str=_interopRequireWildcard(Str),Notification=_interopRequireWildcard(Notification),CustomEvents=_interopRequireWildcard(CustomEvents),_modal=_interopRequireDefault(_modal),Fragment=_interopRequireWildcard(Fragment);const SELECTORS_SAVE_BUTTON='[data-action="save"]',SELECTORS_CANCEL_BUTTON='[data-action="cancel"]',SELECTORS_PROCESSOR="[data-processor-name]",SELECTORS_PREFERENCE_ROW='[data-region="preference-row"]';class NotificationProcessorSettings extends _modal.default{constructor(root){super(root),this.name=null,this.userId=null,this.contextId=null,this.element=null,this.saveButton=this.getFooter().find(SELECTORS_SAVE_BUTTON),this.cancelButton=this.getFooter().find(SELECTORS_CANCEL_BUTTON)}setUserId(id){this.userId=id}getUserId(){return this.userId}setElement(element){this.element=element}getElement(){return this.element}setName(name){this.name=name}getName(){return this.name}setContextId(id){this.contextId=id}getContextId(){return this.contextId}getForm(){return this.getBody().find("form")}disableButtons(){this.saveButton.prop("disabled",!0),this.cancelButton.prop("disabled",!0)}enableButtons(){this.saveButton.prop("disabled",!1),this.cancelButton.prop("disabled",!1)}loadTitleContent(){return this.titlePromise=Str.get_string("processorsettings","message"),this.setTitle(this.titlePromise),this.titlePromise}loadBodyContent(){this.disableButtons();const args={userid:this.getUserId(),type:this.getName()};return this.bodyPromise=Fragment.loadFragment("message","processor_settings",this.getContextId(),args),this.setBody(this.bodyPromise),this.bodyPromise.then((()=>{this.enableButtons()})).catch(Notification.exception),this.bodyPromise}loadAllContent(){return _jquery.default.when(this.loadTitleContent(),this.loadBodyContent())}show(){this.loadAllContent(),super.show(this)}hide(){super.hide(this),this.setContextId(null),this.setName(null),this.setUserId(null)}updateConfiguredStatus(){const processorHeader=(0,_jquery.default)(this.getElement()).closest(SELECTORS_PROCESSOR);if(!processorHeader.hasClass("unconfigured"))return!1;const processorName=processorHeader.attr("data-processor-name"),request={methodname:"core_message_get_message_processor",args:{name:processorName,userid:this.userId}};return Ajax.call([request])[0].then((result=>{if(result.userconfigured){const notifications=(0,_jquery.default)(SELECTORS_PREFERENCE_ROW+' [data-processor-name="'+processorName+'"]');processorHeader.removeClass("unconfigured"),notifications.removeClass("disabled")}return result}))}registerEventListeners(){super.registerEventListeners(this),this.getModal().on(CustomEvents.events.activate,SELECTORS_SAVE_BUTTON,((e,data)=>{this.getForm().submit(),data.originalEvent.preventDefault()})),this.getModal().on("mpp:formsubmitted",(e=>{this.hide(),this.updateConfiguredStatus(),e.stopPropagation()})),this.getModal().on(CustomEvents.events.activate,SELECTORS_CANCEL_BUTTON,((e,data)=>{this.hide(),data.originalEvent.preventDefault(),e.stopPropagation()}))}}return _exports.default=NotificationProcessorSettings,_defineProperty(NotificationProcessorSettings,"TYPE","core_message-notification_processor_settings"),_defineProperty(NotificationProcessorSettings,"TEMPLATE","core/modal_save_cancel"),NotificationProcessorSettings.registerModalType(),_exports.default}));
//# sourceMappingURL=notification_processor_settings.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
/**
* Controls the preferences for the list of notification types on the
* message preference page
*
* @module core_message/preferences_notifications_list_controller
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/preferences_notifications_list_controller",["jquery","core/ajax","core/notification","core/custom_interaction_events","core_message/notification_preference","core_message/notification_processor_settings"],(function($,Ajax,Notification,CustomEvents,NotificationPreference,NotificationProcessorSettings){var SELECTORS_DISABLE_NOTIFICATIONS='[data-region="disable-notification-container"] [data-disable-notifications]',SELECTORS_DISABLE_NOTIFICATIONS_CONTAINER='[data-region="disable-notification-container"]',SELECTORS_PREFERENCE=".preference-state",SELECTORS_PREFERENCE_ROW='[data-region="preference-row"]',SELECTORS_PREFERENCE_INPUT=".preference-state input",SELECTORS_PROCESSOR_SETTING="[data-processor-setting]",PreferencesController=function(element){this.root=$(element),this.userId=this.root.attr("data-user-id"),this.registerEventListeners()};return PreferencesController.prototype.isDisabled=function(){return this.root.hasClass("disabled")},PreferencesController.prototype.setDisabled=function(){this.root.addClass("disabled"),this.root.find(SELECTORS_PREFERENCE_INPUT).prop("disabled",!0)},PreferencesController.prototype.setEnabled=function(){this.root.removeClass("disabled"),this.root.find(SELECTORS_PREFERENCE_INPUT).prop("disabled",!1)},PreferencesController.prototype.toggleDisableAllStatus=function(){var checkbox=$(SELECTORS_DISABLE_NOTIFICATIONS),container=$(SELECTORS_DISABLE_NOTIFICATIONS_CONTAINER),ischecked=checkbox.prop("checked");if(container.hasClass("loading"))return $.Deferred().resolve();container.addClass("loading");var request={methodname:"core_user_update_user_preferences",args:{userid:this.userId,emailstop:ischecked?1:0}};return Ajax.call([request])[0].done(function(){ischecked?this.setDisabled():this.setEnabled()}.bind(this)).always((function(){container.removeClass("loading")})).fail(Notification.exception)},PreferencesController.prototype.registerEventListeners=function(){var disabledNotificationsElement=$(SELECTORS_DISABLE_NOTIFICATIONS);CustomEvents.define(this.root,[CustomEvents.events.activate]),this.root.on("change",function(e){if(!this.isDisabled()){var preferenceElement=$(e.target).closest(SELECTORS_PREFERENCE),preferenceRow=$(e.target).closest(SELECTORS_PREFERENCE_ROW),preference=new NotificationPreference(preferenceRow,this.userId);preferenceElement.addClass("loading"),preference.save().always((function(){preferenceElement.removeClass("loading")}))}}.bind(this));var eventFormPromise=NotificationProcessorSettings.create({});this.root.on(CustomEvents.events.activate,SELECTORS_PROCESSOR_SETTING,(function(e,data){var element=$(e.target).closest(SELECTORS_PROCESSOR_SETTING);data.originalEvent.preventDefault(),eventFormPromise.then((function(modal){modal.setUserId($(element).attr("data-user-id")),modal.setName($(element).attr("data-name")),modal.setContextId($(element).attr("data-context-id")),modal.setElement(element),modal.show(),e.stopImmediatePropagation()})).catch(Notification.exception)})),CustomEvents.define(disabledNotificationsElement,[CustomEvents.events.activate]),disabledNotificationsElement.on(CustomEvents.events.activate,function(){this.toggleDisableAllStatus()}.bind(this))},PreferencesController}));
//# sourceMappingURL=preferences_notifications_list_controller.min.js.map
File diff suppressed because one or more lines are too long
+10
View File
@@ -0,0 +1,10 @@
/**
* Manages the processor form on the message preferences page.
*
* @module core_message/preferences_processor_form
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/preferences_processor_form",["jquery","core/ajax","core/notification"],(function($,Ajax,Notification){var ProcessorForm=function(element){this.root=$(element),this.userId=this.root.attr("data-user-id"),this.name=this.root.attr("data-processor-name"),this.root.find("form").on("submit",function(e){e.preventDefault(),this.save().done((function(){$(element).trigger("mpp:formsubmitted")}))}.bind(this))};return ProcessorForm.prototype.startLoading=function(){this.root.addClass("loading")},ProcessorForm.prototype.stopLoading=function(){this.root.removeClass("loading")},ProcessorForm.prototype.isLoading=function(){return this.root.hasClass("loading")},ProcessorForm.prototype.save=function(){if(this.isLoading())return $.Deferred();this.startLoading();var data=this.root.find("form").serializeArray(),request={methodname:"core_message_message_processor_config_form",args:{userid:this.userId,name:this.name,formvalues:data}};return Ajax.call([request])[0].fail(Notification.exception).always(function(){this.stopLoading()}.bind(this))},ProcessorForm}));
//# sourceMappingURL=preferences_processor_form.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"preferences_processor_form.min.js","sources":["../src/preferences_processor_form.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 <http://www.gnu.org/licenses/>.\n\n/**\n * Manages the processor form on the message preferences page.\n *\n * @module core_message/preferences_processor_form\n * @copyright 2016 Ryan Wyllie <ryan@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/notification'],\n function($, Ajax, Notification) {\n /**\n * Constructor for the ProcessorForm.\n *\n * @class\n * @param {object} element jQuery object root element of the preference\n */\n var ProcessorForm = function(element) {\n this.root = $(element);\n this.userId = this.root.attr('data-user-id');\n this.name = this.root.attr('data-processor-name');\n\n this.root.find('form').on('submit', function(e) {\n e.preventDefault();\n this.save().done(function() {\n $(element).trigger('mpp:formsubmitted');\n });\n }.bind(this));\n };\n\n /**\n * Flag the processor as loading.\n *\n * @method startLoading\n */\n ProcessorForm.prototype.startLoading = function() {\n this.root.addClass('loading');\n };\n\n /**\n * Remove the loading flag for this processor.\n *\n * @method stopLoading\n */\n ProcessorForm.prototype.stopLoading = function() {\n this.root.removeClass('loading');\n };\n\n /**\n * Check if this processor is loading.\n *\n * @method isLoading\n * @return {bool}\n */\n ProcessorForm.prototype.isLoading = function() {\n return this.root.hasClass('loading');\n };\n\n /**\n * Persist the processor configuration.\n *\n * @method save\n * @return {object} jQuery promise\n */\n ProcessorForm.prototype.save = function() {\n if (this.isLoading()) {\n return $.Deferred();\n }\n\n this.startLoading();\n\n var data = this.root.find('form').serializeArray();\n var request = {\n methodname: 'core_message_message_processor_config_form',\n args: {\n userid: this.userId,\n name: this.name,\n formvalues: data,\n }\n };\n\n return Ajax.call([request])[0]\n .fail(Notification.exception)\n .always(function() {\n this.stopLoading();\n }.bind(this));\n };\n\n return ProcessorForm;\n});\n"],"names":["define","$","Ajax","Notification","ProcessorForm","element","root","userId","this","attr","name","find","on","e","preventDefault","save","done","trigger","bind","prototype","startLoading","addClass","stopLoading","removeClass","isLoading","hasClass","Deferred","data","serializeArray","request","methodname","args","userid","formvalues","call","fail","exception","always"],"mappings":";;;;;;;AAsBAA,iDAAO,CAAC,SAAU,YAAa,sBACvB,SAASC,EAAGC,KAAMC,kBAOlBC,cAAgB,SAASC,cACpBC,KAAOL,EAAEI,cACTE,OAASC,KAAKF,KAAKG,KAAK,qBACxBC,KAAOF,KAAKF,KAAKG,KAAK,4BAEtBH,KAAKK,KAAK,QAAQC,GAAG,SAAU,SAASC,GACzCA,EAAEC,sBACGC,OAAOC,MAAK,WACbf,EAAEI,SAASY,QAAQ,yBAEzBC,KAAKV,eAQXJ,cAAce,UAAUC,aAAe,gBAC9Bd,KAAKe,SAAS,YAQvBjB,cAAce,UAAUG,YAAc,gBAC7BhB,KAAKiB,YAAY,YAS1BnB,cAAce,UAAUK,UAAY,kBACzBhB,KAAKF,KAAKmB,SAAS,YAS9BrB,cAAce,UAAUJ,KAAO,cACvBP,KAAKgB,mBACEvB,EAAEyB,gBAGRN,mBAEDO,KAAOnB,KAAKF,KAAKK,KAAK,QAAQiB,iBAC9BC,QAAU,CACVC,WAAY,6CACZC,KAAM,CACFC,OAAQxB,KAAKD,OACbG,KAAMF,KAAKE,KACXuB,WAAYN,cAIbzB,KAAKgC,KAAK,CAACL,UAAU,GACvBM,KAAKhC,aAAaiC,WAClBC,OAAO,gBACCf,eACPJ,KAAKV,QAGRJ"}
+10
View File
@@ -0,0 +1,10 @@
/**
* Module to add/remove contact using ajax.
*
* @module core_message/toggle_contact_button
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define("core_message/toggle_contact_button",["jquery","core/ajax","core/templates","core/notification","core/custom_interaction_events"],(function($,Ajax,Templates,Notification,CustomEvents){let isRequested=element=>"1"==element.attr("data-is-requested");var getUserId=function(element){return element.attr("data-userid")},getCurrentUserId=function(element){return element.attr("data-currentuserid")},displayTextLabel=function(element){return"1"==element.attr("data-display-text-label")},isLoading=function(element){return element.hasClass("loading")||element.attr("disabled")},sendRequest=function(element,request){return isLoading(element)?$.Deferred():(element.addClass("loading"),element.attr("disabled","disabled"),Ajax.call([request])[0].fail(Notification.exception).always((function(){element.removeClass("loading"),element.removeAttr("disabled")})))};return{enhance:function(element){(element=$(element)).children(".loading-icon").length||isRequested(element)||Templates.render("core/loading",{}).done((function(html,js){element.append(html,js)})),CustomEvents.define(element,[CustomEvents.events.activate]),element.on(CustomEvents.events.activate,(function(e,data){!function(element){return"1"==element.attr("data-is-contact")}(element)?isRequested(element)||function(element){if(!isLoading(element)){var request={methodname:"core_message_create_contact_request",args:{userid:getCurrentUserId(element),requesteduserid:getUserId(element)}};sendRequest(element,request).done((function(){!function(element){element.attr("data-is-requested","1")}(element),element.addClass("disabled");const templateContext={displaytextlabel:displayTextLabel(element)};Templates.render("message/contact_request_sent",templateContext).done((function(html,js){Templates.replaceNodeContents(element,html,js)}))}))}}(element):function(element){if(!isLoading(element)){var request={methodname:"core_message_delete_contacts",args:{userids:[getUserId(element)]}};sendRequest(element,request).done((function(){!function(element){element.attr("data-is-contact","0")}(element);const templateContext={displaytextlabel:displayTextLabel(element)};Templates.render("message/add_contact_button",templateContext).done((function(html,js){Templates.replaceNodeContents(element,html,js)}))}))}}(element),e.preventDefault(),data.originalEvent.preventDefault()}))}}}));
//# sourceMappingURL=toggle_contact_button.min.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,101 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the default settings for the list of notification types on the
* notifications admin page
*
* @module core_message/default_notification_preferences
* @class default_notification_preferences
* @copyright 2021 Moodle
* @author Pau Ferrer Ocaña <pau@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const selectors = {
provider: '.defaultmessageoutputs .provider_enabled',
lockSetting: '.locked_message_setting',
enabledSetting: '.enabled_message_setting',
allSettings: '.locked_message_setting, .enabled_message_setting'
};
/**
* Register event listeners for the default_notification_preferences page.
*/
const registerEventListeners = () => {
/**
* Update the dimmed status of the "enabled" toggle on the notification setting.
*
* @param {HTMLElement} lockedElement Element that receives the event.
*/
const toggleLockSetting = (lockedElement) => {
const isEnabled = lockedElement.checked || false;
const enabledId = lockedElement.id.replace('_locked[', '_enabled[');
const enabledElement = document.getElementById(enabledId).closest('div.custom-control');
enabledElement.classList.toggle('dimmed_text', isEnabled);
};
/**
* Enable/Disable all settings of the provider.
*
* @param {HTMLElement} providerEnabledElement Element that receives the event.
*/
const toggleEnableProviderSettings = (providerEnabledElement) => {
const isEnabled = providerEnabledElement.checked || false;
const parentRow = providerEnabledElement.closest('tr');
const elements = parentRow.querySelectorAll(selectors.allSettings);
elements.forEach((element) => {
element.toggleAttribute('disabled', !isEnabled);
});
};
const container = document.querySelector('.preferences-container');
container.querySelectorAll(selectors.provider).forEach((providerEnabledElement) => {
// Set the initial statuses.
if (!providerEnabledElement.checked) {
toggleEnableProviderSettings(providerEnabledElement);
}
providerEnabledElement.addEventListener('change', (e) => {
toggleEnableProviderSettings(e.target);
});
});
container.querySelectorAll(selectors.lockSetting).forEach((lockedElement) => {
// Set the initial statuses.
if (lockedElement.checked) {
toggleLockSetting(lockedElement);
}
lockedElement.addEventListener('change', (e) => {
toggleLockSetting(e.target);
});
});
};
/**
* Initialize the page.
*/
const init = () => {
registerEventListeners();
};
export default {
init: init,
};
+366
View File
@@ -0,0 +1,366 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the message drawer.
*
* @module core_message/message_drawer
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'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',
],
function(
$,
CustomEvents,
PubSub,
ViewContact,
ViewContacts,
ViewConversation,
ViewGroupInfo,
ViewOverview,
ViewSearch,
ViewSettings,
Router,
Routes,
Events,
Helper,
Pending,
Drawer
) {
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"]',
VIEW_CONTACTS: '[data-region="view-contacts"]',
VIEW_CONVERSATION: '[data-region="view-conversation"]',
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"]'
};
/**
* Get elements for route.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} root The message drawer container.
* @param {string} selector The route container.
*
* @return {array} elements Found route container objects.
*/
var getParametersForRoute = function(namespace, root, selector) {
var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);
if (!header.length) {
header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);
}
var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);
if (!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
];
};
var 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]
];
/**
* Create routes.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} root The message drawer container.
*/
var createRoutes = function(namespace, root) {
routes.forEach(function(route) {
Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);
});
};
/**
* Show the message drawer.
*
* @param {string} namespace The route namespace.
* @param {Object} root The message drawer container.
*/
var show = function(namespace, root) {
if (!root.attr('data-shown')) {
Router.go(namespace, Routes.VIEW_OVERVIEW);
root.attr('data-shown', true);
}
var drawerRoot = Drawer.getDrawerRoot(root);
if (drawerRoot.length) {
Drawer.show(drawerRoot);
}
};
/**
* Hide the message drawer.
*
* @param {Object} root The message drawer container.
*/
var hide = function(root) {
var drawerRoot = Drawer.getDrawerRoot(root);
if (drawerRoot.length) {
Drawer.hide(drawerRoot);
}
};
/**
* Check if the drawer is visible.
*
* @param {Object} root The message drawer container.
* @return {boolean}
*/
var isVisible = function(root) {
var drawerRoot = Drawer.getDrawerRoot(root);
if (drawerRoot.length) {
return Drawer.isVisible(drawerRoot);
}
return true;
};
/**
* Set Jump from button
*
* @param {String} buttonid The originating button id
*/
var setJumpFrom = function(buttonid) {
$(SELECTORS.DRAWER).attr('data-origin', buttonid);
};
/**
* Listen to and handle events for routing, showing and hiding the message drawer.
*
* @param {string} namespace The route namespace.
* @param {Object} root The message drawer container.
* @param {bool} alwaysVisible Is this messaging app always shown?
*/
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) {
var element = $(e.target).closest(SELECTORS.ROUTES);
var route = element.attr('data-route');
var attributes = [];
for (var i = 0; i < element[0].attributes.length; i++) {
attributes.push(element[0].attributes[i]);
}
var paramAttributes = attributes.filter(function(attribute) {
var name = attribute.nodeName;
var match = paramRegex.test(name);
return match;
});
paramAttributes.sort(function(a, b) {
var aParts = paramRegex.exec(a.nodeName);
var bParts = paramRegex.exec(b.nodeName);
var aIndex = aParts.length > 1 ? aParts[1] : 0;
var bIndex = bParts.length > 1 ? bParts[1] : 0;
if (aIndex < bIndex) {
return -1;
} else if (bIndex < aIndex) {
return 1;
} else {
return 0;
}
});
var params = paramAttributes.map(function(attribute) {
return attribute.nodeValue;
});
var routeParams = [namespace, route].concat(params);
Router.go.apply(null, routeParams);
data.originalEvent.preventDefault();
});
root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {
Router.back(namespace);
data.originalEvent.preventDefault();
});
// These are theme-specific to help us fix random behat fails.
// These events target those events defined in BS3 and BS4 onwards.
root.on('hide.bs.collapse', '.collapse', function(e) {
var pendingPromise = new Pending();
$(e.target).one('hidden.bs.collapse', function() {
pendingPromise.resolve();
});
});
root.on('show.bs.collapse', '.collapse', function(e) {
var pendingPromise = new Pending();
$(e.target).one('shown.bs.collapse', function() {
pendingPromise.resolve();
});
});
$(SELECTORS.JUMPTO).focus(function() {
var firstInput = root.find(SELECTORS.CLOSE_BUTTON);
if (firstInput.length) {
firstInput.focus();
} else {
$(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus();
}
});
$(SELECTORS.DRAWER).focus(function() {
var button = $(this).attr('data-origin');
if (button) {
$('#' + button).focus();
}
});
if (!alwaysVisible) {
PubSub.subscribe(Events.SHOW, function() {
show(namespace, root);
});
PubSub.subscribe(Events.HIDE, function() {
hide(root);
});
PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) {
if (isVisible(root)) {
hide(root);
$(SELECTORS.JUMPTO).attr('tabindex', -1);
} else {
show(namespace, root);
setJumpFrom(buttonid);
$(SELECTORS.JUMPTO).attr('tabindex', 0);
}
});
}
PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) {
setJumpFrom(args.buttonid);
show(namespace, root);
Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid);
});
var closebutton = root.find(SELECTORS.CLOSE_BUTTON);
closebutton.on(CustomEvents.events.activate, function(e, data) {
data.originalEvent.preventDefault();
var button = $(SELECTORS.DRAWER).attr('data-origin');
if (button) {
$('#' + button).focus();
}
PubSub.publish(Events.TOGGLE_VISIBILITY);
});
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 preference.type == 'message_entertosend';
});
var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;
if (enterToSendPreference) {
var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);
viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);
}
});
};
/**
* Initialise the message drawer.
*
* @param {Object} root The message drawer container.
* @param {String} uniqueId Unique identifier for the Routes
* @param {bool} alwaysVisible Should we show the app now, or wait for the user?
* @param {Object} route
*/
var init = function(root, uniqueId, alwaysVisible, route) {
root = $(root);
createRoutes(uniqueId, root);
registerEventListeners(uniqueId, root, alwaysVisible);
if (alwaysVisible) {
show(uniqueId, root);
if (route) {
var routeParams = route.params || [];
routeParams = [uniqueId, route.path].concat(routeParams);
Router.go.apply(null, routeParams);
}
}
// Mark the drawer as ready.
Helper.markDrawerReady();
};
return {
init: init,
};
});
+47
View File
@@ -0,0 +1,47 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Events for the message drawer.
*
* @module core_message/message_drawer_events
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
CREATE_CONVERSATION_WITH_USER: 'message-drawer-create-conversation-with-user',
CONTACT_BLOCKED: 'message-drawer-contact-blocked',
CONTACT_UNBLOCKED: 'message-drawer-contact-unblocked',
CONTACT_ADDED: 'message-drawer-contact-added',
CONTACT_REMOVED: 'message-drawer-contact-removed',
CONTACT_REQUEST_ACCEPTED: 'message-drawer-contact-request-accepted',
CONTACT_REQUEST_DECLINED: 'message-drawer-contact-request-declined',
CONVERSATION_CREATED: 'message-drawer-conversation-created',
CONVERSATION_NEW_LAST_MESSAGE: 'message-drawer-conversation-new-last-message',
CONVERSATION_DELETED: 'message-drawer-conversation-deleted',
CONVERSATION_READ: 'message-drawer-conversation-read',
CONVERSATION_SET_FAVOURITE: 'message-drawer-conversation-set-favourite',
CONVERSATION_SET_MUTED: 'message-drawer-conversation-set-muted',
CONVERSATION_UNSET_FAVOURITE: 'message-drawer-conversation-unset-favourite',
CONVERSATION_UNSET_MUTED: 'message-drawer-conversation-unset-muted',
PREFERENCES_UPDATED: 'message-drawer-preferences-updated',
READY: 'message-drawer-ready',
ROUTE_CHANGED: 'message-drawer-route-change',
SHOW: 'message-drawer-show',
HIDE: 'message-drawer-hide',
TOGGLE_VISIBILITY: 'message-drawer-toggle',
SHOW_CONVERSATION: 'message-drawer-show-conversation',
SHOW_SETTINGS: 'message-drawer-show-settings',
};
+95
View File
@@ -0,0 +1,95 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Provides some helper functions to trigger actions in the message drawer.
*
* @module core_message/message_drawer_helper
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {publish, subscribe} from 'core/pubsub';
import MessageDrawerEvents from 'core_message/message_drawer_events';
/** @property {boolean} Whether the drawer is ready or not */
let drawerMarkedReady = false;
/**
* Trigger an event to create a new conversation in the message drawer.
*
* @param {object} args
* @param {Number} args.userId The user id to start a conversation.
*/
export const createConversationWithUser = async(args) => {
await waitForDrawerToLoad();
publish(MessageDrawerEvents.CREATE_CONVERSATION_WITH_USER, args);
};
/**
* Trigger an event to hide the message drawer.
*/
export const hide = async() => {
await waitForDrawerToLoad();
publish(MessageDrawerEvents.HIDE);
};
/**
* Trigger an event to show the message drawer.
*/
export const show = async() => {
await waitForDrawerToLoad();
publish(MessageDrawerEvents.SHOW);
};
/**
* Trigger an event to show the given conversation.
*
* @param {object} args
* @param {int} args.conversationId Id for the conversation to show.
*/
export const showConversation = async(args) => {
await waitForDrawerToLoad();
publish(MessageDrawerEvents.SHOW_CONVERSATION, args);
};
/**
* Trigger an event to show messaging settings.
*/
export const showSettings = async() => {
await waitForDrawerToLoad();
publish(MessageDrawerEvents.SHOW_SETTINGS);
};
/**
* Helper to wait for the drawer to be ready before performing an action.
*
* @returns {Promise<void>}
*/
export const waitForDrawerToLoad = () => new Promise((resolve) => {
if (drawerMarkedReady) {
resolve();
} else {
subscribe(MessageDrawerEvents.READY, resolve);
}
});
/**
* Helper to allow the drawer to mark itself as ready.
*/
export const markDrawerReady = () => {
drawerMarkedReady = true;
publish(MessageDrawerEvents.READY);
};
@@ -0,0 +1,333 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Lazy loaded list of items.
*
* @module core_message/message_drawer_lazy_load_list
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/custom_interaction_events',
'core/pending',
],
function(
$,
CustomEvents,
PendingPromise,
) {
var SELECTORS = {
ROOT: '[data-region="lazy-load-list"]',
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
CONTENT_CONTAINER: '[data-region="content-container"]',
EMPTY_MESSAGE: '[data-region="empty-message-container"]',
PLACEHOLDER: '[data-region="placeholder-container"]'
};
/**
* Flag element as loading.
*
* @param {Object} root The section container element.
*/
var startLoading = function(root) {
root.attr('data-loading', true);
};
/**
* Flag element as not loading.
*
* @param {Object} root The section container element.
*/
var stopLoading = function(root) {
root.attr('data-loading', false);
};
/**
* Check if the element is loading.
*
* @param {Object} root The section container element.
* @return {Bool}
*/
var isLoading = function(root) {
return root.attr('data-loading') === 'true';
};
/**
* Get user id
*
* @param {Object} root The section container element.
* @return {Number} Logged in user id.
*/
var getUserId = function(root) {
return root.attr('data-user-id');
};
/**
* Get the section content container element.
*
* @param {Object} root The section container element.
* @return {Object} The section content container element.
*/
var getContentContainer = function(root) {
return root.find(SELECTORS.CONTENT_CONTAINER);
};
/**
* Get the root element.
*
* @param {Object} containerElement The container element to search in.
* @return {Object} The list root element.
*/
var getRoot = function(containerElement) {
return containerElement.find(SELECTORS.ROOT);
};
/**
* Show the loading icon.
*
* @param {Object} root The section container element.
*/
var showLoadingIcon = function(root) {
root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');
};
/**
* Hide the loading icon.
*
* @param {Object} root The section container element.
*/
var hideLoadingIcon = function(root) {
root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
};
/**
* Show the empty message.
*
* @param {Object} root The section container element.
*/
var showEmptyMessage = function(root) {
root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');
};
/**
* Hide the empty message.
*
* @param {Object} root The section container element.
*/
var hideEmptyMessage = function(root) {
root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');
};
/**
* Show the placeholder element.
*
* @param {Object} root The section container element.
*/
var showPlaceholder = function(root) {
root.find(SELECTORS.PLACEHOLDER).removeClass('hidden');
};
/**
* Hide the placeholder element.
*
* @param {Object} root The section container element.
*/
var hidePlaceholder = function(root) {
root.find(SELECTORS.PLACEHOLDER).addClass('hidden');
};
/**
* Show the section content container.
*
* @param {Object} root The section container element.
*/
var showContent = function(root) {
getContentContainer(root).removeClass('hidden');
};
/**
* Hide the section content container.
*
* @param {Object} root The section container element.
*/
var hideContent = function(root) {
getContentContainer(root).addClass('hidden');
};
/**
* If the section has loaded all content.
*
* @param {Object} root The section container element.
* @return {Bool}
*/
var hasLoadedAll = function(root) {
return root.attr('data-loaded-all') == 'true';
};
/**
* If the section has loaded all content.
*
* @param {Object} root The section container element.
* @param {Bool} value If all items have been loaded.
*/
var setLoadedAll = function(root, value) {
root.attr('data-loaded-all', value);
};
/**
* If the section can load more items.
*
* @param {Object} root The section container element.
* @return {Bool}
*/
var canLoadMore = function(root) {
return !hasLoadedAll(root) && !isLoading(root);
};
/**
* Load all items in this container from callback and render them.
*
* @param {Object} root The section container element.
* @param {Function} loadCallback The callback to load items.
* @param {Function} renderCallback The callback to render the results.
* @return {Object} jQuery promise
*/
var loadAndRender = function(root, loadCallback, renderCallback) {
var userId = getUserId(root);
startLoading(root);
return loadCallback(root, userId)
.then(function(items) {
if (items.length > 0) {
var contentContainer = getContentContainer(root);
return renderCallback(contentContainer, items, userId)
.then(function() {
return items;
});
} else {
return items;
}
})
.then(function(items) {
stopLoading(root);
root.attr('data-seen', true);
if (!items.length) {
setLoadedAll(root, true);
}
return items;
})
.catch(function() {
stopLoading(root);
root.attr('data-seen', true);
return;
});
};
/**
* First load of this section.
*
* @param {Object} root The section container element.
* @param {Function} loadCallback The callback to load items.
* @param {Function} renderCallback The callback to render the results.
* @return {Object} promise
*/
var initialLoadAndRender = function(root, loadCallback, renderCallback) {
const pendingPromise = new PendingPromise('initialLoadAndRender');
getContentContainer(root).empty();
showPlaceholder(root);
hideContent(root);
return loadAndRender(root, loadCallback, renderCallback)
.then(function(items) {
hidePlaceholder(root);
if (!items.length) {
showEmptyMessage(root);
} else {
showContent(root);
}
return;
})
.catch(function() {
hidePlaceholder(root);
showContent(root);
return;
})
.then(() => {
pendingPromise.resolve();
return;
});
};
/**
* Listen to, and handle events in this section.
*
* @param {Object} root The section container element.
* @param {Function} loadCallback The callback to load items.
* @param {Function} renderCallback The callback to render the results.
*/
var registerEventListeners = function(root, loadCallback, renderCallback) {
CustomEvents.define(root, [
CustomEvents.events.scrollBottom
]);
root.on(CustomEvents.events.scrollBottom, function() {
if (canLoadMore(root)) {
showLoadingIcon(root);
loadAndRender(root, loadCallback, renderCallback)
.then(function() {
return hideLoadingIcon(root);
})
.catch(function() {
return hideLoadingIcon(root);
});
}
});
};
/**
* Setup the section.
*
* @param {Object} root The section container element.
* @param {Function} loadCallback The callback to load items.
* @param {Function} renderCallback The callback to render the results.
*/
var show = function(root, loadCallback, renderCallback) {
root = $(root);
if (!root.attr('data-init')) {
registerEventListeners(root, loadCallback, renderCallback);
initialLoadAndRender(root, loadCallback, renderCallback);
root.attr('data-init', true);
}
};
return {
show: show,
getContentContainer: getContentContainer,
getRoot: getRoot,
setLoadedAll: setLoadedAll,
showEmptyMessage: showEmptyMessage,
hideEmptyMessage: hideEmptyMessage,
showContent: showContent,
hideContent: hideContent
};
});
+290
View File
@@ -0,0 +1,290 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A simple router for the message drawer that allows navigating between
* the "pages" in the drawer.
*
* This module will maintain a linear history of the unique pages access
* to allow navigating back.
*
* @module core_message/message_drawer_router
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/pubsub',
'core/str',
'core_message/message_drawer_events',
'core/aria',
'core/pending',
],
function(
$,
PubSub,
Str,
MessageDrawerEvents,
Aria,
PendingPromise,
) {
/* @var {object} routes Message drawer route elements and callbacks. */
var routes = {};
/* @var {object} history Store for route objects history. */
var history = {};
var SELECTORS = {
CAN_RECEIVE_FOCUS: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
ROUTES_BACK: '[data-route-back]'
};
/**
* Add a route.
*
* @param {String} namespace Unique identifier for the Routes
* @param {string} route Route config name.
* @param {array} parameters Route parameters.
* @param {callback} onGo Route initialization function.
* @param {callback} getDescription Route initialization function.
*/
var add = function(namespace, route, parameters, onGo, getDescription) {
if (!routes[namespace]) {
routes[namespace] = [];
}
routes[namespace][route] =
{
parameters: parameters,
onGo: onGo,
getDescription: getDescription
};
};
/**
* Go to a defined route and run the route callbacks.
*
* @param {String} namespace Unique identifier for the Routes
* @param {string} newRoute Route config name.
* @return {object} record Current route record with route config name and parameters.
*/
var changeRoute = function(namespace, newRoute) {
var newConfig;
var pendingPromise = new PendingPromise(`message-drawer-router-${namespace}-${newRoute}`);
// Check if the Route change call is made from an element in the app panel.
var fromPanel = [].slice.call(arguments).some(function(arg) {
return arg == 'frompanel';
});
// Get the rest of the arguments, if any.
var args = [].slice.call(arguments, 2);
var renderPromise = $.Deferred().resolve().promise();
Object.keys(routes[namespace]).forEach(function(route) {
var config = routes[namespace][route];
var isMatch = route === newRoute;
if (isMatch) {
newConfig = config;
}
config.parameters.forEach(function(element) {
// Some parameters may be null, or not an element.
if (typeof element !== 'object' || element === null) {
return;
}
element.removeClass('previous');
element.attr('data-from-panel', false);
if (isMatch) {
if (fromPanel) {
// Set this attribute to let the conversation renderer know not to show a back button.
element.attr('data-from-panel', true);
}
element.removeClass('hidden');
Aria.unhide(element.get());
} else {
// For the message index page elements in the left panel should not be hidden.
if (!element.attr('data-in-panel')) {
element.addClass('hidden');
Aria.hide(element.get());
} else if (newRoute == 'view-search' || newRoute == 'view-overview') {
element.addClass('hidden');
Aria.hide(element.get());
}
}
});
});
if (newConfig) {
if (newConfig.onGo) {
renderPromise = newConfig.onGo.apply(undefined, newConfig.parameters.concat(args));
var currentFocusElement = $(document.activeElement);
var hasFocus = false;
var firstFocusable = null;
// No need to start at 0 as we know that is the namespace.
for (var i = 1; i < newConfig.parameters.length; i++) {
var element = newConfig.parameters[i];
// Some parameters may be null, or not an element.
if (typeof element !== 'object' || element === null) {
continue;
}
if (!firstFocusable) {
firstFocusable = element;
}
if (element.has(currentFocusElement).length) {
hasFocus = true;
break;
}
}
if (!hasFocus) {
// This page doesn't have focus yet so focus the first focusable
// element in the new view.
firstFocusable.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();
}
}
}
var record = {
route: newRoute,
params: args,
renderPromise: renderPromise
};
PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED, record);
renderPromise.then(() => pendingPromise.resolve());
return record;
};
/**
* Go to a defined route and store the route history.
*
* @param {String} namespace Unique identifier for the Routes
* @return {object} record Current route record with route config name and parameters.
*/
var go = function(namespace) {
var currentFocusElement = $(document.activeElement);
var record = changeRoute.apply(namespace, arguments);
var inHistory = false;
if (!history[namespace]) {
history[namespace] = [];
}
// History stores a unique list of routes. Check to see if the new route
// is already in the history, if it is then forget all history after it.
// This ensures there are no duplicate routes in history and that it represents
// a linear path of routes (it never stores something like [foo, bar, foo])).
history[namespace] = history[namespace].reduce(function(carry, previous) {
if (previous.route === record.route) {
inHistory = true;
}
if (!inHistory) {
carry.push(previous);
}
return carry;
}, []);
var historylength = history[namespace].length;
var previousRecord = historylength ? history[namespace][historylength - 1] : null;
if (previousRecord) {
var prevConfig = routes[namespace][previousRecord.route];
var elements = prevConfig.parameters;
// The first one will be the namespace, skip it.
for (var i = 1; i < elements.length; i++) {
// Some parameters may be null, or not an element.
if (typeof elements[i] !== 'object' || elements[i] === null) {
continue;
}
elements[i].addClass('previous');
}
previousRecord.focusElement = currentFocusElement;
if (prevConfig.getDescription) {
// If the route has a description then set it on the back button for
// the new page we're displaying.
prevConfig.getDescription.apply(null, prevConfig.parameters.concat(previousRecord.params))
.then(function(description) {
return Str.get_string('backto', 'core_message', description);
})
.then(function(label) {
// Wait for the new page to finish rendering so that we know
// that the back button is visible.
return record.renderPromise.then(function() {
// Find the elements for the new route we displayed.
routes[namespace][record.route].parameters.forEach(function(element) {
// Some parameters may be null, or not an element.
if (typeof element !== 'object' || !element) {
return;
}
// Update the aria label for the back button.
element.find(SELECTORS.ROUTES_BACK).attr('aria-label', label);
});
});
})
.catch(function() {
// Silently ignore.
});
}
}
history[namespace].push(record);
return record;
};
/**
* Go back to the previous route record stored in history.
*
* @param {String} namespace Unique identifier for the Routes
*/
var back = function(namespace) {
if (history[namespace].length) {
// Remove the current route.
history[namespace].pop();
var previous = history[namespace].pop();
if (previous) {
// If we have a previous route then show it.
go.apply(undefined, [namespace, previous.route].concat(previous.params));
// Delay the focus 50 milliseconds otherwise it doesn't correctly
// focus the element for some reason...
window.setTimeout(function() {
previous.focusElement.focus();
}, 50);
}
}
};
return {
add: add,
go: go,
back: back
};
});
+33
View File
@@ -0,0 +1,33 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Available routes for the message drawer.
*
* @module core_message/message_drawer_routes
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
VIEW_CONTACT: 'view-contact',
VIEW_CONTACTS: 'view-contacts',
VIEW_CONVERSATION: 'view-conversation',
VIEW_GROUP_INFO: 'view-group-info',
VIEW_OVERVIEW: 'view-overview',
VIEW_SEARCH: 'view-search',
VIEW_SETTINGS: 'view-settings'
};
});
@@ -0,0 +1,100 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the contact page in the message drawer.
*
* @module core_message/message_drawer_view_contact
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/str',
'core/templates'
],
function(
$,
Str,
Templates
) {
var SELECTORS = {
CONTENT_CONTAINER: '[data-region="content-container"]'
};
var TEMPLATES = {
CONTENT: 'core_message/message_drawer_view_contact_body_content'
};
/**
* Get the content container of the contact view container.
*
* @param {Object} root Contact container element.
* @returns {Object} jQuery object
*/
var getContentContainer = function(root) {
return root.find(SELECTORS.CONTENT_CONTAINER);
};
/**
* Render the contact profile in the content container.
*
* @param {Object} root Contact container element.
* @param {Object} profile Contact profile details.
* @returns {Object} jQuery promise
*/
var render = function(root, profile) {
return Templates.render(TEMPLATES.CONTENT, profile)
.then(function(html) {
getContentContainer(root).append(html);
return html;
});
};
/**
* Setup the contact page.
*
* @param {string} namespace The route namespace.
* @param {Object} header Contact header element.
* @param {Object} body Contact body container element.
* @param {Object} footer Contact footer container element.
* @param {Object} contact The contact object.
* @returns {Object} jQuery promise
*/
var show = function(namespace, header, body, footer, contact) {
var root = $(body);
getContentContainer(root).empty();
return render(root, contact);
};
/**
* String describing this page used for aria-labels.
*
* @param {Object} root Contact container element.
* @param {Object} contact The contact object.
* @return {Object} jQuery promise
*/
var description = function(root, contact) {
return Str.get_string('messagedrawerviewcontact', 'core_message', contact.fullname);
};
return {
show: show,
description: description
};
});
@@ -0,0 +1,207 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the contacts page of the message drawer.
*
* @module core_message/message_drawer_view_contacts
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/pubsub',
'core/str',
'core_message/message_drawer_events',
'core_message/message_drawer_view_contacts_section_contacts',
'core_message/message_drawer_view_contacts_section_requests'
],
function(
$,
PubSub,
Str,
MessageDrawerEvents,
ContactsSection,
RequestsSection
) {
var SELECTORS = {
ACTION_SHOW_CONTACTS_SECTION: '[data-action="show-contacts-section"]',
ACTION_SHOW_REQUESTS_SECTION: '[data-action="show-requests-section"]',
CONTACT_REQUEST_COUNT: '[data-region="contact-request-count"]',
CONTACTS_SECTION_CONTAINER: '[data-section="contacts"]',
REQUESTS_SECTION_CONTAINER: '[data-section="requests"]',
};
/**
* Get the container element for the contacts section.
*
* @param {Object} body Contacts page body element.
* @return {Object}
*/
var getContactsSectionContainer = function(body) {
return body.find(SELECTORS.CONTACTS_SECTION_CONTAINER);
};
/**
* Get the container element for the requests section.
*
* @param {Object} body Contacts page body element.
* @return {Object}
*/
var getRequestsSectionContainer = function(body) {
return body.find(SELECTORS.REQUESTS_SECTION_CONTAINER);
};
/**
* Get the element that triggers showing the contacts section.
*
* @param {Object} body Contacts page body element.
* @return {Object}
*/
var getShowContactsAction = function(body) {
return body.find(SELECTORS.ACTION_SHOW_CONTACTS_SECTION);
};
/**
* Get the element that triggers showing the requests section.
*
* @param {Object} body Contacts page body element.
* @return {Object}
*/
var getShowRequestsAction = function(body) {
return body.find(SELECTORS.ACTION_SHOW_REQUESTS_SECTION);
};
/**
* Check if the given section is visible.
*
* @param {Object} sectionRoot The root element for the section
* @return {Bool}
*/
var isSectionVisible = function(sectionRoot) {
return sectionRoot.hasClass('active');
};
/**
* Decrement the contact request count. If the count is zero or below then
* hide the count.
*
* @param {Object} body Conversation body container element.
* @return {Function} A function to handle decrementing the count.
*/
var decrementContactRequestCount = function(body) {
return function() {
var countContainer = body.find(SELECTORS.CONTACT_REQUEST_COUNT);
var count = parseInt(countContainer.text(), 10);
count = isNaN(count) ? 0 : count - 1;
if (count <= 0) {
countContainer.addClass('hidden');
} else {
countContainer.text(count);
}
};
};
/**
* Listen to and handle events for contacts.
*
* @param {Object} body Contacts body container element.
*/
var registerEventListeners = function(body) {
var contactsSection = getContactsSectionContainer(body);
var requestsSection = getRequestsSectionContainer(body);
var showContactsAction = getShowContactsAction(body);
var showRequestsAction = getShowRequestsAction(body);
showContactsAction.on('show.bs.tab', function() {
ContactsSection.show(contactsSection);
});
showRequestsAction.on('show.bs.tab', function() {
RequestsSection.show(requestsSection);
});
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, decrementContactRequestCount(body));
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, decrementContactRequestCount(body));
};
/**
* Setup the contact page.
*
* @param {string} namespace The route namespace.
* @param {Object} header Contacts header container element.
* @param {Object} body Contacts body container element.
* @param {Object} footer Contacts footer container element.
* @param {String|null} tab Tab to show, either 'requests' or 'contacts', if any.
* @return {Object} jQuery promise
*/
var show = function(namespace, header, body, footer, tab) {
body = $(body);
if (!body.attr('data-contacts-init')) {
registerEventListeners(body);
body.attr('data-contacts-init', true);
}
var contactsSection = getContactsSectionContainer(body);
var requestsSection = getRequestsSectionContainer(body);
if (tab) {
var showContactsAction = getShowContactsAction(body);
var showRequestsAction = getShowRequestsAction(body);
// Unfortunately we need to hardcode the class changes here rather than trigger
// the bootstrap tab functionality because the bootstrap JS doesn't appear to be
// loaded by this point which means the tab plugin isn't added and the event listeners
// haven't been set up so we can't just trigger a click either.
if (tab == 'requests') {
showContactsAction.removeClass('active');
contactsSection.removeClass('show active');
showRequestsAction.addClass('active');
requestsSection.addClass('show active');
} else {
showRequestsAction.removeClass('active');
requestsSection.removeClass('show active');
showContactsAction.addClass('active');
contactsSection.addClass('show active');
}
}
if (isSectionVisible(contactsSection)) {
ContactsSection.show(contactsSection);
} else {
RequestsSection.show(requestsSection);
}
return $.Deferred().resolve().promise();
};
/**
* String describing this page used for aria-labels.
*
* @return {Object} jQuery promise
*/
var description = function() {
return Str.get_string('messagedrawerviewcontacts', 'core_message');
};
return {
show: show,
description: description
};
});
@@ -0,0 +1,200 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the contacts section of the contacts page.
*
* @module core_message/message_drawer_view_contacts_section_contacts
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/notification',
'core/pubsub',
'core/templates',
'core_message/message_repository',
'core_message/message_drawer_events',
'core_message/message_drawer_lazy_load_list'
],
function(
$,
Notification,
PubSub,
Templates,
MessageRepository,
Events,
LazyLoadList
) {
var limit = 100;
var initialOffset = 0;
var SELECTORS = {
BLOCK_ICON_CONTAINER: '[data-region="block-icon-container"]',
CONTACT: '[data-region="contact"]',
CONTENT_CONTAINER: '[data-region="contacts-content-container"]'
};
var TEMPLATES = {
CONTACTS_LIST: 'core_message/message_drawer_contacts_list'
};
/**
* Find a contact element.
*
* @param {Object} body Contacts body container element.
* @param {Number} userId User id of contact.
* @return {Object} contact element.
*/
var findContact = function(body, userId) {
return body.find('[data-contact-user-id="' + userId + '"]');
};
/**
* Render the contacts in the content container.
*
* @param {Object} contentContainer Content container element.
* @param {Array} contacts List of contacts.
* @return {Object} jQuery promise
*/
var render = function(contentContainer, contacts) {
var formattedContacts = contacts.map(function(contact) {
return $.extend(contact, {id: contact.userid});
});
return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: formattedContacts})
.then(function(html) {
contentContainer.append(html);
return html;
})
.catch(Notification.exception);
};
/**
* Load the user contacts and call the renderer.
*
* @param {Number} offset The offset to use for loading contacts
* @return {Function} the callback.
*/
var getLoadFunction = function(offset) {
return function(listRoot, userId) {
return MessageRepository.getContacts(userId, (limit + 1), offset)
.then(function(result) {
return result;
})
.then(function(contacts) {
if (contacts.length > limit) {
contacts.pop();
} else {
LazyLoadList.setLoadedAll(listRoot, true);
}
return contacts;
})
.then(function(contacts) {
offset = offset + limit;
return contacts;
})
.catch(Notification.exception);
};
};
/**
* Remove contact from view.
*
* @param {Object} body Contacts body container element.
* @param {Number} userId Contact userid.
*/
var removeContact = function(body, userId) {
findContact(body, userId).remove();
};
/**
* Show the contact has been blocked.
*
* @param {Object} body Contacts body container element.
* @param {Number} userId Contact userid.
*/
var showContactBlocked = function(body, userId) {
var contact = findContact(body, userId);
if (contact.length) {
contact.find(SELECTORS.BLOCK_ICON_CONTAINER).removeClass('hidden');
}
};
/**
* Show the contact has been unblocked.
*
* @param {Object} body Contacts body container element.
* @param {Number} userId Contact userid.
*/
var showContactUnblocked = function(body, userId) {
var contact = findContact(body, userId);
if (contact.length) {
contact.find(SELECTORS.BLOCK_ICON_CONTAINER).addClass('hidden');
}
};
/**
* Listen to and handle events for contacts.
*
* @param {Object} root Contacts section container element.
*/
var registerEventListeners = function(root) {
PubSub.subscribe(Events.CONTACT_ADDED, function(profile) {
var listContentContainer = LazyLoadList.getContentContainer(root);
render(listContentContainer, [profile]);
LazyLoadList.hideEmptyMessage(root);
LazyLoadList.showContent(root);
});
PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) {
removeContact(root, userId);
var contacts = root.find(SELECTORS.CONTACT);
if (!contacts.length) {
LazyLoadList.hideContent(root);
LazyLoadList.showEmptyMessage(root);
}
});
PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) {
showContactBlocked(root, userId);
});
PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) {
showContactUnblocked(root, userId);
});
};
/**
* Setup the contacts section.
*
* @param {Object} root Contacts section container.
*/
var show = function(root) {
if (!root.attr('data-contacts-init')) {
registerEventListeners(root);
root.attr('data-contacts-init', true);
}
// The root element is already the lazy loaded list root.
LazyLoadList.show(root, getLoadFunction(initialOffset), render);
};
return {
show: show,
};
});
@@ -0,0 +1,138 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the requests section of the contacts page.
*
* @module core_message/message_drawer_view_contacts_section_requests
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/notification',
'core/pubsub',
'core/templates',
'core_message/message_repository',
'core_message/message_drawer_events',
'core_message/message_drawer_lazy_load_list'
],
function(
$,
Notification,
PubSub,
Templates,
MessageRepository,
MessageDrawerEvents,
LazyLoadList
) {
var SELECTORS = {
CONTACT_REQUEST: '[data-region="contact-request"]'
};
var TEMPLATES = {
REQUESTS_LIST: 'core_message/message_drawer_view_contacts_body_section_requests_list'
};
/**
* Render the requests in the content container.
*
* @param {Object} contentContainer List container element.
* @param {Array} requests List of requests.
* @return {Object} jQuery promise
*/
var render = function(contentContainer, requests) {
var formattedRequests = requests.map(function(request) {
return {
// This is actually the user id.
id: request.id,
profileimageurl: request.profileimageurl,
fullname: request.fullname
};
});
return Templates.render(TEMPLATES.REQUESTS_LIST, {requests: formattedRequests})
.then(function(html) {
contentContainer.append(html);
return html;
})
.catch(Notification.exception);
};
/**
* Load the user contacts and call the renderer.
*
* @param {Object} listRoot The lazy loaded list root element
* @param {Integer} userId The logged in user id.
* @return {Object} jQuery promise
*/
var load = function(listRoot, userId) {
return MessageRepository.getContactRequests(userId)
.then(function(requests) {
LazyLoadList.setLoadedAll(listRoot, true);
return requests;
})
.catch(Notification.exception);
};
/**
* Handle when a contact request is accepted or declined by removing the contact
* list from the page.
*
* @param {Object} root The section root element
* @return {Function} The event handler function
*/
var handleContactRequestProcessed = function(root) {
return function(request) {
root.find('[data-request-id="' + request.userid + '"]').remove();
var contactRequests = root.find(SELECTORS.CONTACT_REQUEST);
if (!contactRequests.length) {
LazyLoadList.showEmptyMessage(root);
LazyLoadList.hideContent(root);
}
};
};
/**
* Listen for any events that might affect the requests section.
*
* @param {Object} root The section root element
*/
var registerEventListeners = function(root) {
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, handleContactRequestProcessed(root));
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, handleContactRequestProcessed(root));
};
/**
* Setup the requests section.
*
* @param {Object} root Requests section container.
*/
var show = function(root) {
if (!root.attr('data-contacts-init')) {
registerEventListeners(root);
root.attr('data-contacts-init', true);
}
// The root element is already the lazy loaded list root.
LazyLoadList.show(root, load, render);
};
return {
show: show,
};
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,128 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Constant values for the conversation page in the message drawer.
*
* @module core_message/message_drawer_view_conversation_constants
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
var SELECTORS = {
ACTION_ACCEPT_CONTACT_REQUEST: '[data-action="accept-contact-request"]',
ACTION_CANCEL_CONFIRM: '[data-action="cancel-confirm"]',
ACTION_OKAY_CONFIRM: '[data-action="okay-confirm"]',
ACTION_CANCEL_EDIT_MODE: '[data-action="cancel-edit-mode"]',
ACTION_CONFIRM_ADD_CONTACT: '[data-action="confirm-add-contact"]',
ACTION_CONFIRM_BLOCK: '[data-action="confirm-block"]',
ACTION_CONFIRM_DELETE_SELECTED_MESSAGES: '[data-action="confirm-delete-selected-messages"]',
ACTION_CONFIRM_DELETE_CONVERSATION: '[data-action="confirm-delete-conversation"]',
ACTION_CONFIRM_FAVOURITE: '[data-action="confirm-favourite"]',
ACTION_CONFIRM_MUTE: '[data-action="confirm-mute"]',
ACTION_CONFIRM_UNFAVOURITE: '[data-action="confirm-unfavourite"]',
ACTION_CONFIRM_REMOVE_CONTACT: '[data-action="confirm-remove-contact"]',
ACTION_CONFIRM_UNBLOCK: '[data-action="confirm-unblock"]',
ACTION_CONFIRM_UNMUTE: '[data-action="confirm-unmute"]',
ACTION_DECLINE_CONTACT_REQUEST: '[data-action="decline-contact-request"]',
ACTION_REQUEST_ADD_CONTACT: '[data-action="request-add-contact"]',
ACTION_REQUEST_BLOCK: '[data-action="request-block"]',
ACTION_REQUEST_DELETE_CONVERSATION: '[data-action="request-delete-conversation"]',
ACTION_REQUEST_DELETE_SELECTED_MESSAGES: '[data-action="delete-selected-messages"]',
ACTION_REQUEST_REMOVE_CONTACT: '[data-action="request-remove-contact"]',
ACTION_REQUEST_UNBLOCK: '[data-action="request-unblock"]',
ACTION_VIEW_CONTACT: '[data-action="view-contact"]',
ACTION_VIEW_GROUP_INFO: '[data-action="view-group-info"]',
CAN_RECEIVE_FOCUS: 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]',
CONFIRM_DIALOGUE: '[data-region="confirm-dialogue"]',
CONFIRM_DIALOGUE_BUTTON_TEXT: '[data-region="dialogue-button-text"]',
CONFIRM_DIALOGUE_CANCEL_BUTTON: '[data-action="cancel-confirm"]',
CONFIRM_DIALOGUE_CONTAINER: '[data-region="confirm-dialogue-container"]',
CONFIRM_DIALOGUE_HEADER: '[data-region="dialogue-header"]',
CONFIRM_DIALOGUE_OKAY_BUTTON: '[data-action="okay-confirm"]',
CONFIRM_DIALOGUE_TEXT: '[data-region="dialogue-text"]',
CONTACT_REQUEST_SENT_MESSAGE_CONTAINER: '[data-region="contact-request-sent-message-container"]',
CONTENT_PLACEHOLDER_CONTAINER: '[data-region="content-placeholder"]',
CONTENT_CONTAINER: '[data-region="content-container"]',
CONTENT_MESSAGES_CONTAINER: '[data-region="content-message-container"]',
CONTENT_MESSAGES_FOOTER_CONTAINER: '[data-region="content-messages-footer-container"]',
CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER: '[data-region="content-messages-footer-edit-mode-container"]',
CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER: '[data-region="content-messages-footer-require-contact-container"]',
CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER: '[data-region="content-messages-footer-require-unblock-container"]',
CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER: '[data-region="content-messages-footer-unable-to-message"]',
DAY_MESSAGES_CONTAINER: '[data-region="day-messages-container"]',
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE: '[data-region="delete-messages-for-all-users-toggle"]',
DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER: '[data-region="delete-messages-for-all-users-toggle-container"]',
EMOJI_AUTO_COMPLETE_CONTAINER: '[data-region="emoji-auto-complete-container"]',
EMOJI_PICKER_CONTAINER: '[data-region="emoji-picker-container"]',
EMOJI_PICKER: '[data-region="emoji-picker"]',
EMOJI_PICKER_SEARCH_INPUT: '[data-region="search-input"]',
ERROR_MESSAGE_CONTAINER: '[data-region="error-message-container"]',
ERROR_MESSAGE: '[data-region="error-message"]',
FAVOURITE_ICON_CONTAINER: '[data-region="favourite-icon-container"]',
FOOTER_CONTAINER: '[data-region="content-messages-footer-container"]',
HEADER: '[data-region="header-content"]',
HEADER_EDIT_MODE: '[data-region="header-edit-mode"]',
HEADER_PLACEHOLDER_CONTAINER: '[data-region="header-placeholder"]',
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
MESSAGE: '[data-region="message"]',
MESSAGE_NOT_SELECTED: '[data-region="message"][aria-checked="false"]',
MESSAGE_NOT_SELECTED_ICON: '[data-region="not-selected-icon"]',
MESSAGE_SELECTED_ICON: '[data-region="selected-icon"]',
MESSAGES: '[data-region="content-message-container"]',
MESSAGES_CONTAINER: '[data-region="content-message-container"]',
MESSAGES_SELECTED_COUNT: '[data-region="message-selected-court"]',
MESSAGE_TEXT_AREA: '[data-region="send-message-txt"]',
MORE_MESSAGES_LOADING_ICON_CONTAINER: '[data-region="more-messages-loading-icon-container"]',
MUTED_ICON_CONTAINER: '[data-region="muted-icon-container"]',
PLACEHOLDER_CONTAINER: '[data-region="placeholder-container"]',
RETRY_SEND: '[data-region="retry-send"]',
SELF_CONVERSATION_MESSAGE_CONTAINER: '[data-region="self-conversation-message-container"]',
SEND_MESSAGE_BUTTON: '[data-action="send-message"]',
SEND_MESSAGE_ICON_CONTAINER: '[data-region="send-icon-container"]',
TEXT: '[data-region="text"]',
TEXT_CONTAINER: '[data-region="text-container"]',
TIME_CREATED: '[data-region="time-created"]',
TITLE: '[data-region="title"]',
TOGGLE_EMOJI_PICKER_BUTTON: '[data-action="toggle-emoji-picker"]'
};
var TEMPLATES = {
HEADER_PRIVATE: 'core_message/message_drawer_view_conversation_header_content_type_private',
HEADER_PRIVATE_NO_CONTROLS: 'core_message/message_drawer_view_conversation_header_content_type_private_no_controls',
HEADER_PUBLIC: 'core_message/message_drawer_view_conversation_header_content_type_public',
HEADER_SELF: 'core_message/message_drawer_view_conversation_header_content_type_self',
DAY: 'core_message/message_drawer_view_conversation_body_day',
MESSAGE: 'core_message/message_drawer_view_conversation_body_message',
MESSAGES: 'core_message/message_drawer_view_conversation_body_messages'
};
// Conversation types. They must have the same values defined in \core_message\api.
var CONVERSATION_TYPES = {
PRIVATE: 1,
PUBLIC: 2,
SELF: 3
};
return {
SELECTORS: SELECTORS,
TEMPLATES: TEMPLATES,
CONVERSATION_TYPES: CONVERSATION_TYPES,
NEWEST_MESSAGES_FIRST: true,
LOAD_MESSAGE_LIMIT: 100,
MILLISECONDS_IN_SEC: 1000
};
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,881 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module operates on the view states from the message_drawer_view_conversation module.
* It exposes functions that can be used to generate new version of the state.
*
* Important notes for this module:
* 1.) The existing state is always immutable. It should never be modified.
* 2.) All functions that operate on the state should always clone the state and
* modify the cloned state before returning it.
*
* It's important that the states remain immutable because they are diff'd in
* the message_drawer_view_conversation_patcher module in order to work out what
* has changed.
*
* @module core_message/message_drawer_view_conversation_state_manager
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
/**
* Clone a state, a state is a collection of information about the variables required to build
* the conversation user interface.
*
* @param {Object} state State to clone
* @return {Object} newstate A copy of the state to clone.
*/
var cloneState = function(state) {
// Do a deep extend to make sure we recursively copy objects and
// arrays so that the new state doesn't contain any references to
// the old state, e.g. adding a value to an array in the new state
// shouldn't also add it to the old state.
return $.extend(true, {}, state);
};
/**
* Format messages to be used in a state.
*
* @param {Array} messages The messages to format.
* @param {Number} loggedInUserId The logged in user id.
* @param {Array} members The converstation members.
* @return {Array} Formatted messages.
*/
var formatMessages = function(messages, loggedInUserId, members) {
return messages.map(function(message) {
var fromLoggedInUser = message.useridfrom == loggedInUserId;
return {
// Stringify the id.
id: "" + message.id,
fromLoggedInUser: fromLoggedInUser,
userFrom: members[message.useridfrom],
text: message.text,
timeCreated: message.timecreated ? parseInt(message.timecreated, 10) : null
};
});
};
/**
* Format members to be used in a state.
*
* @param {Array} members The messages to format.
* @return {Array} Formatted members.
*/
var formatMembers = function(members) {
return members.map(function(member) {
return {
id: member.id,
fullname: member.fullname,
profileurl: member.profileurl,
profileimageurl: member.profileimageurl,
profileimageurlsmall: member.profileimageurlsmall,
isonline: member.isonline,
showonlinestatus: member.showonlinestatus,
isblocked: member.isblocked,
iscontact: member.iscontact,
isdeleted: member.isdeleted,
canmessage: member.canmessage,
canmessageevenifblocked: member.canmessageevenifblocked,
requirescontact: member.requirescontact,
contactrequests: member.contactrequests || []
};
});
};
/**
* Create an initial (blank) state.
*
* @param {Number} midnight Midnight time.
* @param {Number} loggedInUserId The logged in user id.
* @param {Number} id The conversation id.
* @param {Number} messagePollMin The message poll start timeout in seconds.
* @param {Number} messagePollMax The message poll max timeout limit in seconds.
* @param {Number} messagePollAfterMax The message poll frequency in seconds to reset to after max limit is reached.
* @return {Object} Initial state.
*/
var buildInitialState = function(
midnight,
loggedInUserId,
id,
messagePollMin,
messagePollMax,
messagePollAfterMax
) {
return {
midnight: midnight,
loggedInUserId: loggedInUserId,
id: id,
messagePollMin: messagePollMin,
messagePollMax: messagePollMax,
messagePollAfterMax: messagePollAfterMax,
name: null,
subname: null,
type: null,
totalMemberCount: null,
imageUrl: null,
isFavourite: null,
isMuted: null,
canDeleteMessagesForAllUsers: false,
deleteMessagesForAllUsers: false,
members: {},
messages: [],
hasTriedToLoadMessages: false,
loadingMessages: true,
loadingMembers: true,
loadingConfirmAction: false,
pendingBlockUserIds: [],
pendingUnblockUserIds: [],
pendingRemoveContactIds: [],
pendingAddContactIds: [],
pendingDeleteMessageIds: [],
pendingSendMessageIds: [],
pendingDeleteConversation: false,
selectedMessageIds: [],
showEmojiAutoComplete: false,
showEmojiPicker: false
};
};
/**
* Add messages to a state and sort them by timecreated.
*
* @param {Object} state Current state.
* @param {Array} messages Messages to add to state.
* @return {Object} state New state with added messages.
*/
var addMessages = function(state, messages) {
var newState = cloneState(state);
var formattedMessages = formatMessages(messages, state.loggedInUserId, state.members);
formattedMessages = formattedMessages.map(function(message) {
message.sendState = null;
message.timeAdded = Date.now();
message.errorMessage = null;
return message;
});
var allMessages = state.messages.concat(formattedMessages);
// Sort the messages. Oldest to newest.
allMessages.sort(function(a, b) {
if (a.timeCreated === null && b.timeCreated === null) {
if (a.timeAdded < b.timeAdded) {
return -1;
} else if (a.timeAdded > b.timeAdded) {
return 1;
}
}
if (a.timeCreated === null && b.timeCreated !== null) {
// A comes after b.
return 1;
} else if (a.timeCreated !== null && b.timeCreated === null) {
// A comes before b.
return -1;
} else if (a.timeCreated < b.timeCreated) {
// A comes before b.
return -1;
} else if (a.timeCreated > b.timeCreated) {
// A comes after b.
return 1;
} else if (a.id < b.id) {
return -1;
} else if (a.id > b.id) {
return 1;
} else {
return 0;
}
});
// Filter out any duplicate messages.
newState.messages = allMessages.filter(function(message, index, sortedMessages) {
return !index || message.id != sortedMessages[index - 1].id;
});
return newState;
};
/**
* Update existing messages.
*
* @param {Object} state Current state.
* @param {Array} data 2D array of old and new messages
* @return {Object} state.
*/
var updateMessages = function(state, data) {
var newState = cloneState(state);
var updatesById = data.reduce(function(carry, messageData) {
var oldMessage = messageData[0];
var newMessage = messageData[1];
var formattedMessages = formatMessages([newMessage], state.loggedInUserId, state.members);
var formattedMessage = formattedMessages[0];
carry[oldMessage.id] = formattedMessage;
return carry;
}, {});
newState.messages = newState.messages.map(function(message) {
if (message.id in updatesById) {
return $.extend(message, updatesById[message.id]);
} else {
return message;
}
});
return newState;
};
/**
* Remove messages from state.
*
* @param {Object} state Current state.
* @param {Array} messages Messages to remove from state.
* @return {Object} state New state with removed messages.
*/
var removeMessages = function(state, messages) {
var newState = cloneState(state);
var removeMessageIds = messages.map(function(message) {
return "" + message.id;
});
newState.messages = newState.messages.filter(function(message) {
return removeMessageIds.indexOf(message.id) < 0;
});
return newState;
};
/**
* Remove messages from state by message id.
*
* @param {Object} state Current state.
* @param {Array} messageIds Message ids to remove from state.
* @return {Object} state New state with removed messages.
*/
var removeMessagesById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.messages = newState.messages.filter(function(message) {
return messageIds.indexOf(message.id) < 0;
});
return newState;
};
/**
* Add conversation member to state.
*
* @param {Object} state Current state.
* @param {Array} members Conversation members to be added to state.
* @return {Object} New state with added members.
*/
var addMembers = function(state, members) {
var newState = cloneState(state);
var formattedMembers = formatMembers(members);
formattedMembers.forEach(function(member) {
newState.members[member.id] = member;
});
return newState;
};
/**
* Remove members from state.
*
* @param {Object} state Current state.
* @param {Array} members Members to be removed from state.
* @return {Object} New state with removed members.
*/
var removeMembers = function(state, members) {
var newState = cloneState(state);
members.forEach(function(member) {
delete newState.members[member.id];
});
return newState;
};
/**
* Set the state loading messages attribute.
*
* @param {Object} state Current state.
* @param {Bool} value New loading messages value.
* @return {Object} New state with loading messages attribute.
*/
var setLoadingMessages = function(state, value) {
var newState = cloneState(state);
newState.loadingMessages = value;
if (state.loadingMessages && !value) {
// If we're going from loading to not loading then
// it means we've tried to load.
newState.hasTriedToLoadMessages = true;
}
return newState;
};
/**
* Set the state loading members attribute.
*
* @param {Object} state Current state.
* @param {Bool} value New loading members value.
* @return {Object} New state with loading members attribute.
*/
var setLoadingMembers = function(state, value) {
var newState = cloneState(state);
newState.loadingMembers = value;
return newState;
};
/**
* Set the conversation id.
*
* @param {Object} state Current state.
* @param {String} value The ID.
* @return {Object} New state.
*/
var setId = function(state, value) {
var newState = cloneState(state);
newState.id = value;
return newState;
};
/**
* Set the state name attribute.
*
* @param {Object} state Current state.
* @param {String} value New name value.
* @return {Object} New state with name attribute.
*/
var setName = function(state, value) {
var newState = cloneState(state);
newState.name = value;
return newState;
};
/**
* Set the state subname attribute.
*
* @param {Object} state Current state.
* @param {String} value New subname value.
* @return {Object} New state.
*/
var setSubname = function(state, value) {
var newState = cloneState(state);
newState.subname = value;
return newState;
};
/**
* Set the conversation type.
*
* @param {Object} state Current state.
* @param {Int} type Conversation type.
* @return {Object} New state.
*/
var setType = function(state, type) {
var newState = cloneState(state);
newState.type = type;
return newState;
};
/**
* Set whether the conversation is a favourite conversation.
*
* @param {Object} state Current state.
* @param {Bool} isFavourite If it's a favourite.
* @return {Object} New state.
*/
var setIsFavourite = function(state, isFavourite) {
var newState = cloneState(state);
newState.isFavourite = isFavourite;
return newState;
};
/**
* Set whether the conversation is a muted conversation.
*
* @param {Object} state Current state.
* @param {bool} isMuted If it's muted.
* @return {Object} New state.
*/
var setIsMuted = function(state, isMuted) {
var newState = cloneState(state);
newState.isMuted = isMuted;
return newState;
};
/**
* Set the total member count.
*
* @param {Object} state Current state.
* @param {String} count The count.
* @return {Object} New state.
*/
var setTotalMemberCount = function(state, count) {
var newState = cloneState(state);
newState.totalMemberCount = count;
return newState;
};
/**
* Set the conversation image url.
*
* @param {Object} state Current state.
* @param {String} url The url to the image.
* @return {Object} New state.
*/
var setImageUrl = function(state, url) {
var newState = cloneState(state);
newState.imageUrl = url;
return newState;
};
/**
* Set the state loading confirm action attribute.
*
* @param {Object} state Current state.
* @param {Bool} value New loading confirm action value.
* @return {Object} New state with loading confirm action attribute.
*/
var setLoadingConfirmAction = function(state, value) {
var newState = cloneState(state);
newState.loadingConfirmAction = value;
return newState;
};
/**
* Set the state pending delete conversation attribute.
*
* @param {Object} state Current state.
* @param {Bool} value New pending delete conversation value.
* @return {Object} New state with pending delete conversation attribute.
*/
var setPendingDeleteConversation = function(state, value) {
var newState = cloneState(state);
newState.pendingDeleteConversation = value;
return newState;
};
/**
* Set the state of message to pending.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages to delete.
* @return {Object} New state with array of pending delete message ids.
*/
var setMessagesSendPendingById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.messages.forEach(function(message) {
if (messageIds.indexOf(message.id) >= 0) {
message.sendState = 'pending';
message.errorMessage = null;
}
});
return newState;
};
/**
* Set the state of message to sent.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages to delete.
* @return {Object} New state with array of pending delete message ids.
*/
var setMessagesSendSuccessById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.messages.forEach(function(message) {
if (messageIds.indexOf(message.id) >= 0) {
message.sendState = 'sent';
message.errorMessage = null;
}
});
return newState;
};
/**
* Set the state of messages to error.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages to delete.
* @param {string} errorMessage
* @return {Object} New state with array of pending delete message ids.
*/
var setMessagesSendFailById = function(state, messageIds, errorMessage) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.messages.forEach(function(message) {
if (messageIds.indexOf(message.id) >= 0) {
message.sendState = 'error';
message.errorMessage = errorMessage;
}
});
return newState;
};
/**
* Set the visibility of the emoji picker.
*
* @param {Object} state Current state.
* @param {Bool} show Should the emoji picker be shown.
* @return {Object} New state with array of pending delete message ids.
*/
var setShowEmojiPicker = function(state, show) {
var newState = cloneState(state);
newState.showEmojiPicker = show;
return newState;
};
/**
* Set whether emojis auto complete suggestions should be shown.
*
* @param {Object} state Current state.
* @param {Bool} show Show the autocomplete
* @return {Object} New state with array of pending delete message ids.
*/
var setShowEmojiAutoComplete = function(state, show) {
var newState = cloneState(state);
newState.showEmojiAutoComplete = show;
return newState;
};
/**
* Set the state pending block userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to block.
* @return {Object} New state with array of pending block userids.
*/
var addPendingBlockUsersById = function(state, userIds) {
var newState = cloneState(state);
userIds.forEach(function(id) {
newState.pendingBlockUserIds.push(id);
});
return newState;
};
/**
* Set the state pending remove userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to remove.
* @return {Object} New state with array of pending remove userids.
*/
var addPendingRemoveContactsById = function(state, userIds) {
var newState = cloneState(state);
userIds.forEach(function(id) {
newState.pendingRemoveContactIds.push(id);
});
return newState;
};
/**
* Set the state pending unblock userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to unblock.
* @return {Object} New state with array of pending unblock userids.
*/
var addPendingUnblockUsersById = function(state, userIds) {
var newState = cloneState(state);
userIds.forEach(function(id) {
newState.pendingUnblockUserIds.push(id);
});
return newState;
};
/**
* Set the state pending add users to contacts userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to add users to contacts.
* @return {Object} New state with array of pending add users to contacts userids.
*/
var addPendingAddContactsById = function(state, userIds) {
var newState = cloneState(state);
userIds.forEach(function(id) {
newState.pendingAddContactIds.push(id);
});
return newState;
};
/**
* Set the state pending delete messages.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages to delete.
* @return {Object} New state with array of pending delete message ids.
*/
var addPendingDeleteMessagesById = function(state, messageIds) {
var newState = cloneState(state);
messageIds.forEach(function(id) {
newState.pendingDeleteMessageIds.push(id);
});
return newState;
};
/**
* Update the state pending block userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to remove from the list of user ids to block.
* @return {Object} New state with array of pending block userids.
*/
var removePendingBlockUsersById = function(state, userIds) {
var newState = cloneState(state);
newState.pendingBlockUserIds = newState.pendingBlockUserIds.filter(function(id) {
return userIds.indexOf(id) < 0;
});
return newState;
};
/**
* Update the state pending remove userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to remove from the list of user ids to remove.
* @return {Object} New state with array of pending remove userids.
*/
var removePendingRemoveContactsById = function(state, userIds) {
var newState = cloneState(state);
newState.pendingRemoveContactIds = newState.pendingRemoveContactIds.filter(function(id) {
return userIds.indexOf(id) < 0;
});
return newState;
};
/**
* Update the state pending unblock userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to remove from the list of user ids to unblock.
* @return {Object} New state with array of pending unblock userids.
*/
var removePendingUnblockUsersById = function(state, userIds) {
var newState = cloneState(state);
newState.pendingUnblockUserIds = newState.pendingUnblockUserIds.filter(function(id) {
return userIds.indexOf(id) < 0;
});
return newState;
};
/**
* Update the state pending add to contacts userids.
*
* @param {Object} state Current state.
* @param {Array} userIds User ids to remove from the list of user ids to add to contacts.
* @return {Object} New state with array of pending add to contacts userids.
*/
var removePendingAddContactsById = function(state, userIds) {
var newState = cloneState(state);
newState.pendingAddContactIds = newState.pendingAddContactIds.filter(function(id) {
return userIds.indexOf(id) < 0;
});
return newState;
};
/**
* Update the state pending delete messages userids.
*
* @param {Object} state Current state.
* @param {Array} messageIds Message ids to remove from the list of messages to delete.
* @return {Object} New state with array of messages to delete.
*/
var removePendingDeleteMessagesById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.pendingDeleteMessageIds = newState.pendingDeleteMessageIds.filter(function(id) {
return messageIds.indexOf(id) < 0;
});
return newState;
};
/**
* Add messages to state selected messages.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages that are selected.
* @return {Object} New state with array of not blocked members.
*/
var addSelectedMessagesById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.selectedMessageIds = newState.selectedMessageIds.concat(messageIds);
return newState;
};
/**
* Remove messages from the state selected messages.
*
* @param {Object} state Current state.
* @param {Array} messageIds Messages to remove from selected messages.
* @return {Object} New state with array of selected messages.
*/
var removeSelectedMessagesById = function(state, messageIds) {
var newState = cloneState(state);
messageIds = messageIds.map(function(id) {
return "" + id;
});
newState.selectedMessageIds = newState.selectedMessageIds.filter(function(id) {
return messageIds.indexOf(id) < 0;
});
return newState;
};
/**
* Mark messages as read.
*
* @param {Object} state Current state.
* @param {Array} readMessages Messages that are read.
* @return {Object} New state with array of messages that have the isread attribute set.
*/
var markMessagesAsRead = function(state, readMessages) {
var newState = cloneState(state);
var readMessageIds = readMessages.map(function(message) {
return message.id;
});
newState.messages = newState.messages.map(function(message) {
if (readMessageIds.indexOf(message.id) >= 0) {
message.isRead = true;
}
return message;
});
return newState;
};
/**
* Add a contact request to each of the members that the request is for.
*
* @param {Object} state Current state.
* @param {Array} requests The contact requests
* @return {Object} New state
*/
var addContactRequests = function(state, requests) {
var newState = cloneState(state);
requests.forEach(function(request) {
var fromUserId = request.userid;
var toUserId = request.requesteduserid;
newState.members[fromUserId].contactrequests.push(request);
newState.members[toUserId].contactrequests.push(request);
});
return newState;
};
/**
* Remove a contact request from the members of that request.
*
* @param {Object} state Current state.
* @param {Array} requests The contact requests
* @return {Object} New state
*/
var removeContactRequests = function(state, requests) {
var newState = cloneState(state);
requests.forEach(function(request) {
var fromUserId = request.userid;
var toUserId = request.requesteduserid;
newState.members[fromUserId].contactrequests = newState.members[fromUserId].contactrequests.filter(function(existing) {
return existing.userid != fromUserId;
});
newState.members[toUserId].contactrequests = newState.members[toUserId].contactrequests.filter(function(existing) {
return existing.requesteduserid != toUserId;
});
});
return newState;
};
/**
* Set wheter the message of the conversation can delete for all users.
*
* @param {Object} state Current state.
* @param {Bool} value If it can delete for all users.
* @return {Object} New state.
*/
var setCanDeleteMessagesForAllUsers = function(state, value) {
var newState = cloneState(state);
newState.canDeleteMessagesForAllUsers = value;
return newState;
};
/**
* Set wheter the messages of the conversation delete for all users.
*
* @param {Object} state Current state.
* @param {Bool} value Delete messages for all users.
* @return {Object} New state.
*/
var setDeleteMessagesForAllUsers = function(state, value) {
var newState = cloneState(state);
newState.deleteMessagesForAllUsers = value;
return newState;
};
return {
buildInitialState: buildInitialState,
addMessages: addMessages,
updateMessages: updateMessages,
removeMessages: removeMessages,
removeMessagesById: removeMessagesById,
addMembers: addMembers,
removeMembers: removeMembers,
setLoadingMessages: setLoadingMessages,
setLoadingMembers: setLoadingMembers,
setId: setId,
setName: setName,
setSubname: setSubname,
setType: setType,
setIsFavourite: setIsFavourite,
setIsMuted: setIsMuted,
setCanDeleteMessagesForAllUsers: setCanDeleteMessagesForAllUsers,
setDeleteMessagesForAllUsers: setDeleteMessagesForAllUsers,
setTotalMemberCount: setTotalMemberCount,
setImageUrl: setImageUrl,
setLoadingConfirmAction: setLoadingConfirmAction,
setPendingDeleteConversation: setPendingDeleteConversation,
setMessagesSendPendingById: setMessagesSendPendingById,
setMessagesSendSuccessById: setMessagesSendSuccessById,
setMessagesSendFailById: setMessagesSendFailById,
setShowEmojiAutoComplete: setShowEmojiAutoComplete,
setShowEmojiPicker: setShowEmojiPicker,
addPendingBlockUsersById: addPendingBlockUsersById,
addPendingRemoveContactsById: addPendingRemoveContactsById,
addPendingUnblockUsersById: addPendingUnblockUsersById,
addPendingAddContactsById: addPendingAddContactsById,
addPendingDeleteMessagesById: addPendingDeleteMessagesById,
removePendingBlockUsersById: removePendingBlockUsersById,
removePendingRemoveContactsById: removePendingRemoveContactsById,
removePendingUnblockUsersById: removePendingUnblockUsersById,
removePendingAddContactsById: removePendingAddContactsById,
removePendingDeleteMessagesById: removePendingDeleteMessagesById,
addSelectedMessagesById: addSelectedMessagesById,
removeSelectedMessagesById: removeSelectedMessagesById,
markMessagesAsRead: markMessagesAsRead,
addContactRequests: addContactRequests,
removeContactRequests: removeContactRequests
};
});
@@ -0,0 +1,176 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the group info page of the message drawer.
*
* @module core_message/message_drawer_view_group_info
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/str',
'core/templates',
'core_message/message_repository',
'core_message/message_drawer_lazy_load_list',
],
function(
$,
Str,
Templates,
Repository,
LazyLoadList
) {
var LOAD_MEMBERS_LIMIT = 50;
var SELECTORS = {
CONTENT_CONTAINER: '[data-region="group-info-content-container"]',
MEMBERS_LIST: '[data-region="members-list"]',
};
var TEMPLATES = {
CONTENT: 'core_message/message_drawer_view_group_info_body_content',
MEMBERS_LIST: 'core_message/message_drawer_view_group_info_participants_list'
};
/**
* Get the content container of the group info view container.
*
* @param {Object} root Contact container element.
* @return {Object} jQuery object
*/
var getContentContainer = function(root) {
return root.find(SELECTORS.CONTENT_CONTAINER);
};
/**
* Render the group info page.
*
* @param {Object} root Container element.
* @param {Object} conversation The group conversation.
* @param {Number} loggedInUserId The logged in user's id.
* @return {Object} jQuery promise
*/
var render = function(root, conversation, loggedInUserId) {
var placeholderCount = conversation.totalMemberCount > 50 ? 50 : conversation.totalMemberCount;
var placeholders = Array.apply(null, Array(placeholderCount)).map(function() {
return true;
});
var templateContext = {
name: conversation.name,
subname: conversation.subname,
imageurl: conversation.imageUrl,
placeholders: placeholders,
loggedinuser: {
id: loggedInUserId
}
};
return Templates.render(TEMPLATES.CONTENT, templateContext)
.then(function(html) {
getContentContainer(root).append(html);
return html;
});
};
/**
* Get the callback to load members of the conversation.
*
* @param {Object} conversation The conversation
* @param {Number} limit How many members to load
* @param {Number} offset How many memebers to skip
* @return {Function} the callback.
*/
var getLoadMembersCallback = function(conversation, limit, offset) {
return function(root, userId) {
return Repository.getConversationMembers(conversation.id, userId, limit + 1, offset)
.then(function(members) {
if (members.length > limit) {
members = members.slice(0, -1);
} else {
LazyLoadList.setLoadedAll(root, true);
}
offset = offset + limit;
// Filter out the logged in user so that they don't appear in the list.
return members.filter(function(member) {
return member.id != userId;
});
});
};
};
/**
* Function to render the members in the list.
*
* @param {Object} contentContainer The list content container.
* @param {Array} members The list of members to render
* @return {Object} jQuery promise
*/
var renderMembersCallback = function(contentContainer, members) {
return Templates.render(TEMPLATES.MEMBERS_LIST, {contacts: members})
.then(function(html) {
contentContainer.append(html);
return html;
});
};
/**
* Setup the contact page.
*
* @param {string} namespace The route namespace.
* @param {Object} header Contact header container element.
* @param {Object} body Contact body container element.
* @param {Object} footer Contact body container element.
* @param {Number} conversation The conversation
* @param {Number} loggedInUserId The logged in user id
* @return {Object} jQuery promise
*/
var show = function(namespace, header, body, footer, conversation, loggedInUserId) {
var root = $(body);
getContentContainer(root).empty();
return render(root, conversation, loggedInUserId)
.then(function() {
var listRoot = LazyLoadList.getRoot(root);
LazyLoadList.show(
listRoot,
getLoadMembersCallback(conversation, LOAD_MEMBERS_LIMIT, 0),
renderMembersCallback
);
return;
});
};
/**
* String describing this page used for aria-labels.
*
* @param {Object} root Contact container element.
* @param {Number} conversation The conversation
* @return {Object} jQuery promise
*/
var description = function(root, conversation) {
return Str.get_string('messagedrawerviewgroupinfo', 'core_message', conversation.name);
};
return {
show: show,
description: description
};
});
@@ -0,0 +1,298 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the overview page of the message drawer.
*
* @module core_message/message_drawer_view_overview
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/key_codes',
'core/pubsub',
'core/str',
'core_message/message_drawer_router',
'core_message/message_drawer_routes',
'core_message/message_drawer_events',
'core_message/message_drawer_view_overview_section',
'core_message/message_repository',
'core_message/message_drawer_view_conversation_constants'
],
function(
$,
KeyCodes,
PubSub,
Str,
Router,
Routes,
MessageDrawerEvents,
Section,
MessageRepository,
Constants
) {
var SELECTORS = {
CONTACT_REQUEST_COUNT: '[data-region="contact-request-count"]',
FAVOURITES: '[data-region="view-overview-favourites"]',
GROUP_MESSAGES: '[data-region="view-overview-group-messages"]',
MESSAGES: '[data-region="view-overview-messages"]',
SEARCH_INPUT: '[data-region="view-overview-search-input"]',
SECTION_TOGGLE_BUTTON: '[data-toggle]'
};
// Categories displayed in the message drawer. Some methods (such as filterCountsByType) are expecting their value
// will be the same as the defined in the CONVERSATION_TYPES, except for the favourite.
var OVERVIEW_SECTION_TYPES = {
PRIVATE: [Constants.CONVERSATION_TYPES.PRIVATE, Constants.CONVERSATION_TYPES.SELF],
PUBLIC: [Constants.CONVERSATION_TYPES.PUBLIC],
FAVOURITE: null
};
var loadAllCountsPromise = null;
/**
* Load the total and unread conversation counts from the server for this user. This function
* returns a jQuery promise that will be resolved with the counts.
*
* The request is only sent once per page load and will be cached for subsequent
* calls to this function.
*
* @param {Number} loggedInUserId The logged in user's id
* @return {Object} jQuery promise
*/
var loadAllCounts = function(loggedInUserId) {
if (loadAllCountsPromise === null) {
loadAllCountsPromise = MessageRepository.getAllConversationCounts(loggedInUserId);
}
return loadAllCountsPromise;
};
/**
* Filter a set of counts to return only the count for the given type.
*
* This is used on the result returned by the loadAllCounts function.
*
* @param {Object} counts Conversation counts indexed by conversation type.
* @param {Array|null} types The conversation types handlded by this section (null for all conversation types).
* @param {bool} includeFavourites If this section includes favourites
* @return {Number}
*/
var filterCountsByTypes = function(counts, types, includeFavourites) {
var total = 0;
if (types && types.length) {
total = types.reduce(function(carry, type) {
return carry + counts.types[type];
}, total);
}
if (includeFavourites) {
total += counts.favourites;
}
return total;
};
/**
* Opens one of the sections based on whether the section has unread conversations
* or any conversations
*
* Default section priority is favourites, groups, then messages. A section can increase
* in priority if it has conversations in it. It can increase even further if it has
* unread conversations.
*
* @param {Array} sections List of section roots, total counts, and unread counts.
*/
var openSection = function(sections) {
var isAlreadyOpen = sections.some(function(section) {
var sectionRoot = section[0];
return Section.isVisible(sectionRoot);
});
if (isAlreadyOpen) {
// The user has already opened a section so there is nothing to do.
return;
}
// Order the sections so that sections with unread conversations are prioritised
// over sections without and sections with total conversations are prioritised
// over sections without.
sections.sort(function(a, b) {
var aTotal = a[1];
var aUnread = a[2];
var bTotal = b[1];
var bUnread = b[2];
if (aUnread > 0 && bUnread == 0) {
return -1;
} else if (aUnread == 0 && bUnread > 0) {
return 1;
} else if (aTotal > 0 && bTotal == 0) {
return -1;
} else if (aTotal == 0 && bTotal > 0) {
return 1;
} else {
return 0;
}
});
// Get the root of the first section after sorting.
var sectionRoot = sections[0][0];
var button = sectionRoot.find(SELECTORS.SECTION_TOGGLE_BUTTON);
// Click it to expand it.
button.click();
};
/**
* Get the search input text element.
*
* @param {Object} header Overview header container element.
* @return {Object} The search input element.
*/
var getSearchInput = function(header) {
return header.find(SELECTORS.SEARCH_INPUT);
};
/**
* Get the logged in user id.
*
* @param {Object} body Overview body container element.
* @return {String} Logged in user id.
*/
var getLoggedInUserId = function(body) {
return body.attr('data-user-id');
};
/**
* Decrement the contact request count. If the count is zero or below then
* hide the count.
*
* @param {Object} header Conversation header container element.
* @return {Function} A function to handle decrementing the count.
*/
var decrementContactRequestCount = function(header) {
return function() {
var countContainer = header.find(SELECTORS.CONTACT_REQUEST_COUNT);
var count = parseInt(countContainer.text(), 10);
count = isNaN(count) ? 0 : count - 1;
if (count <= 0) {
countContainer.addClass('hidden');
} else {
countContainer.text(count);
}
};
};
/**
* Listen to, and handle event in the overview header.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} header Conversation header container element.
*/
var registerEventListeners = function(namespace, header) {
var searchInput = getSearchInput(header);
var ignoredKeys = [KeyCodes.tab, KeyCodes.shift, KeyCodes.ctrl, KeyCodes.alt];
searchInput.on('click', function() {
Router.go(namespace, Routes.VIEW_SEARCH);
});
searchInput.on('keydown', function(e) {
if (ignoredKeys.indexOf(e.keyCode) < 0 && e.key != 'Meta') {
Router.go(namespace, Routes.VIEW_SEARCH);
}
});
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, decrementContactRequestCount(header));
PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, decrementContactRequestCount(header));
};
/**
* Setup the overview page.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} header Overview header container element.
* @param {Object} body Overview body container element.
* @return {Object} jQuery promise
*/
var show = function(namespace, header, body) {
if (!header.attr('data-init')) {
registerEventListeners(namespace, header);
header.attr('data-init', true);
}
var fromPanel = header.attr('data-in-panel') ? 'frompanel' : null;
getSearchInput(header).val('');
var loggedInUserId = getLoggedInUserId(body);
var allCounts = loadAllCounts(loggedInUserId);
var sections = [
// Favourite conversations section.
[body.find(SELECTORS.FAVOURITES), OVERVIEW_SECTION_TYPES.FAVOURITE, true],
// Group conversations section.
[body.find(SELECTORS.GROUP_MESSAGES), OVERVIEW_SECTION_TYPES.PUBLIC, false],
// Private conversations section.
[body.find(SELECTORS.MESSAGES), OVERVIEW_SECTION_TYPES.PRIVATE, false]
];
sections.forEach(function(args) {
var sectionRoot = args[0];
var sectionTypes = args[1];
var includeFavourites = args[2];
var totalCountPromise = allCounts.then(function(result) {
return filterCountsByTypes(result.total, sectionTypes, includeFavourites);
});
var unreadCountPromise = allCounts.then(function(result) {
return filterCountsByTypes(result.unread, sectionTypes, includeFavourites);
});
Section.show(namespace, null, sectionRoot, null, sectionTypes, includeFavourites,
totalCountPromise, unreadCountPromise, fromPanel);
});
return allCounts.then(function(result) {
var sectionParams = sections.map(function(section) {
var sectionRoot = section[0];
var sectionTypes = section[1];
var includeFavourites = section[2];
var totalCount = filterCountsByTypes(result.total, sectionTypes, includeFavourites);
var unreadCount = filterCountsByTypes(result.unread, sectionTypes, includeFavourites);
return [sectionRoot, totalCount, unreadCount];
});
// Open up one of the sections for the user.
return openSection(sectionParams);
});
};
/**
* String describing this page used for aria-labels.
*
* @return {Object} jQuery promise
*/
var description = function() {
return Str.get_string('messagedrawerviewoverview', 'core_message');
};
return {
show: show,
description: description
};
});
@@ -0,0 +1,829 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls a section of the overview page in the message drawer.
*
* @module core_message/message_drawer_view_overview_section
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/custom_interaction_events',
'core/notification',
'core/pubsub',
'core/str',
'core/pending',
'core/templates',
'core/user_date',
'core_message/message_repository',
'core_message/message_drawer_events',
'core_message/message_drawer_router',
'core_message/message_drawer_routes',
'core_message/message_drawer_lazy_load_list',
'core_message/message_drawer_view_conversation_constants'
],
function(
$,
CustomEvents,
Notification,
PubSub,
Str,
Pending,
Templates,
UserDate,
MessageRepository,
MessageDrawerEvents,
MessageDrawerRouter,
MessageDrawerRoutes,
LazyLoadList,
MessageDrawerViewConversationContants
) {
var SELECTORS = {
TOGGLE: '[data-region="toggle"]',
CONVERSATION: '[data-conversation-id]',
BLOCKED_ICON_CONTAINER: '[data-region="contact-icon-blocked"]',
LAST_MESSAGE: '[data-region="last-message"]',
LAST_MESSAGE_DATE: '[data-region="last-message-date"]',
MUTED_ICON_CONTAINER: '[data-region="muted-icon-container"]',
UNREAD_COUNT: '[data-region="unread-count"]',
SECTION_TOTAL_COUNT: '[data-region="section-total-count"]',
SECTION_TOTAL_COUNT_CONTAINER: '[data-region="section-total-count-container"]',
SECTION_UNREAD_COUNT: '[data-region="section-unread-count"]',
SECTION_UNREAD_COUNT_CONTAINER: '[data-region="section-unread-count-container"]',
PLACEHOLDER_CONTAINER: '[data-region="placeholder-container"]'
};
var TEMPLATES = {
CONVERSATIONS_LIST: 'core_message/message_drawer_conversations_list',
CONVERSATIONS_LIST_ITEMS_PLACEHOLDER: 'core_message/message_drawer_conversations_list_items_placeholder'
};
var LOAD_LIMIT = 50;
var loadedConversationsById = {};
var deletedConversationsById = {};
var loadedTotalCounts = false;
var loadedUnreadCounts = false;
/**
* Get the section visibility status.
*
* @param {Object} root The section container element.
* @return {Bool} Is section visible.
*/
var isVisible = function(root) {
return LazyLoadList.getRoot(root).hasClass('show');
};
/**
* Set this section as expanded.
*
* @param {Object} root The section container element.
*/
var setExpanded = function(root) {
root.addClass('expanded');
};
/**
* Set this section as collapsed.
*
* @param {Object} root The section container element.
*/
var setCollapsed = function(root) {
root.removeClass('expanded');
};
/**
* Render the total count value and show it for the user. Also update the placeholder
* HTML for better visuals.
*
* @param {Object} root The section container element.
* @param {Number} count The total count
*/
var renderTotalCount = function(root, count) {
var container = root.find(SELECTORS.SECTION_TOTAL_COUNT_CONTAINER);
var countElement = container.find(SELECTORS.SECTION_TOTAL_COUNT);
countElement.text(count);
container.removeClass('hidden');
Str.get_string('totalconversations', 'core_message', count).done(function(string) {
$('#' + container.attr('aria-labelledby')).text(string);
});
var numPlaceholders = count > 20 ? 20 : count;
// Array of "true" up to the number of placeholders we want.
var placeholders = Array.apply(null, Array(numPlaceholders)).map(function() {
return true;
});
// Replace the current placeholder (loading spinner) with some nicer placeholders that
// better represent the content.
Templates.render(TEMPLATES.CONVERSATIONS_LIST_ITEMS_PLACEHOLDER, {placeholders: placeholders})
.then(function(html) {
var placeholderContainer = root.find(SELECTORS.PLACEHOLDER_CONTAINER);
placeholderContainer.html(html);
return;
})
.catch(function() {
// Silently ignore. Doesn't matter if we can't render the placeholders.
});
};
/**
* Render the unread count value and show it for the user if it's higher than zero.
*
* @param {Object} root The section container element.
* @param {Number} count The unread count
*/
var renderUnreadCount = function(root, count) {
var container = root.find(SELECTORS.SECTION_UNREAD_COUNT_CONTAINER);
var countElement = container.find(SELECTORS.SECTION_UNREAD_COUNT);
countElement.text(count);
Str.get_string('unreadconversations', 'core_message', count).done(function(string) {
$('#' + container.attr('aria-labelledby')).text(string);
});
if (count > 0) {
container.removeClass('hidden');
}
};
/**
* Create a formatted conversation object from the the one we get from events. The new object
* will be in a format that matches what we receive from the server.
*
* @param {Object} conversation
* @return {Object} formatted conversation.
*/
var formatConversationFromEvent = function(conversation) {
// Recursively lowercase all of the keys for an object.
var recursivelyLowercaseKeys = function(object) {
return Object.keys(object).reduce(function(carry, key) {
if ($.isArray(object[key])) {
carry[key.toLowerCase()] = object[key].map(recursivelyLowercaseKeys);
} else {
carry[key.toLowerCase()] = object[key];
}
return carry;
}, {});
};
// Recursively lowercase all of the keys for the conversation.
var formatted = recursivelyLowercaseKeys(conversation);
// Make sure all messages have the useridfrom property set.
formatted.messages = formatted.messages.map(function(message) {
message.useridfrom = message.userfrom.id;
return message;
});
return formatted;
};
/**
* Render the messages in the overview page.
*
* @param {Array} conversations List of conversations to render.
* @param {Number} userId Logged in user id.
* @return {Object} jQuery promise.
*/
var render = function(conversations, userId) {
// Helper to format the last message for rendering.
// Returns a promise which resolves to either a string, or null
// (such as in the event of an empty personal space).
var pending = new Pending();
var formatMessagePreview = async function(lastMessage) {
if (!lastMessage) {
return null;
}
// Check the message html for a src attribute, indicative of media.
// Replace <img with <noimg to stop browsers pre-fetching the image as part of tmp element creation.
var tmpElement = document.createElement("element");
tmpElement.innerHTML = lastMessage.text.replace(/<img /g, '<noimg ');
var isMedia = tmpElement.querySelector("[src]") || false;
if (!isMedia) {
// Try to get the text value of the content.
// If that's not possible, we'll report it under the catch-all 'other media'.
var messagePreview = $(lastMessage.text).text();
if (messagePreview) {
// The text value of the message must have no html/script tags.
if (messagePreview.indexOf('<') == -1) {
return messagePreview;
}
}
}
// As a fallback, report unknowns as 'other media' type content.
var pix = 'i/messagecontentmultimediageneral';
var label = 'messagecontentmultimediageneral';
if (lastMessage.text.includes('<img')) {
pix = 'i/messagecontentimage';
label = 'messagecontentimage';
} else if (lastMessage.text.includes('<video')) {
pix = 'i/messagecontentvideo';
label = 'messagecontentvideo';
} else if (lastMessage.text.includes('<audio')) {
pix = 'i/messagecontentaudio';
label = 'messagecontentaudio';
}
try {
var labelString = await Str.get_string(label, 'core_message');
var icon = await Templates.renderPix(pix, 'core', labelString);
return icon + ' ' + labelString;
} catch (error) {
Notification.exception(error);
return null;
}
};
var mapPromises = conversations.map(function(conversation) {
var lastMessage = conversation.messages.length ? conversation.messages[conversation.messages.length - 1] : null;
return formatMessagePreview(lastMessage)
.then(function(messagePreview) {
var formattedConversation = {
id: conversation.id,
imageurl: conversation.imageurl,
name: conversation.name,
subname: conversation.subname,
unreadcount: conversation.unreadcount,
ismuted: conversation.ismuted,
lastmessagedate: lastMessage ? lastMessage.timecreated : null,
sentfromcurrentuser: lastMessage ? lastMessage.useridfrom == userId : null,
lastmessage: messagePreview
};
var otherUser = null;
if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF) {
// Self-conversations have only one member.
otherUser = conversation.members[0];
} else if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE) {
// For private conversations, remove the current userId from the members to get the other user.
otherUser = conversation.members.reduce(function(carry, member) {
if (!carry && member.id != userId) {
carry = member;
}
return carry;
}, null);
}
if (otherUser !== null) {
formattedConversation.userid = otherUser.id;
formattedConversation.showonlinestatus = otherUser.showonlinestatus;
formattedConversation.isonline = otherUser.isonline;
formattedConversation.isblocked = otherUser.isblocked;
}
if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PUBLIC) {
formattedConversation.lastsendername = conversation.members.reduce(function(carry, member) {
if (!carry && lastMessage && member.id == lastMessage.useridfrom) {
carry = member.fullname;
}
return carry;
}, null);
}
return formattedConversation;
}).catch(Notification.exception);
});
return Promise.all(mapPromises)
.then(function(formattedConversations) {
formattedConversations.forEach(function(conversation) {
if (new Date().toDateString() == new Date(conversation.lastmessagedate * 1000).toDateString()) {
conversation.istoday = true;
}
});
return Templates.render(TEMPLATES.CONVERSATIONS_LIST, {conversations: formattedConversations});
}).then(function(html, js) {
pending.resolve();
return $.Deferred().resolve(html, js);
}).catch(function(error) {
pending.resolve();
Notification.exception(error);
});
};
/**
* Build the callback to load conversations.
*
* @param {Array|null} types The conversation types for this section.
* @param {bool} includeFavourites Include/exclude favourites.
* @param {Number} offset Result offset
* @return {Function}
*/
var getLoadCallback = function(types, includeFavourites, offset) {
// Note: This function is a bit messy because we've added the concept of loading
// multiple conversations types (e.g. private + self) at once but haven't properly
// updated the web service to accept an array of types. Instead we've added a new
// parameter for the self type which means we can only ever load self + other type.
// This should be improved to make it more extensible in the future. Adding new params
// for each type isn't very scalable.
var type = null;
// Include self conversations in the results by default.
var includeSelfConversations = true;
if (types && types.length) {
// Just get the conversation types that aren't "self" for now.
var nonSelfConversationTypes = types.filter(function(candidate) {
return candidate != MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF;
});
// If we're specifically asking for a list of types that doesn't include the self
// conversations then we don't need to include them.
includeSelfConversations = types.length != nonSelfConversationTypes.length;
// As mentioned above the webservice is currently limited to loading one type at a
// time (plus self conversations) so let's hope we never change this.
type = nonSelfConversationTypes[0];
}
return function(root, userId) {
return MessageRepository.getConversations(
userId,
type,
LOAD_LIMIT + 1,
offset,
includeFavourites,
includeSelfConversations
)
.then(function(response) {
var conversations = response.conversations;
if (conversations.length > LOAD_LIMIT) {
conversations = conversations.slice(0, -1);
} else {
LazyLoadList.setLoadedAll(root, true);
}
offset = offset + LOAD_LIMIT;
conversations.forEach(function(conversation) {
loadedConversationsById[conversation.id] = conversation;
});
return conversations;
})
.catch(Notification.exception);
};
};
/**
* Get the total count container element.
*
* @param {Object} root Overview messages container element.
* @return {Object} Total count container element.
*/
var getTotalConversationCountElement = function(root) {
return root.find(SELECTORS.SECTION_TOTAL_COUNT);
};
/**
* Get the unread conversations count container element.
*
* @param {Object} root Overview messages container element.
* @return {Object} Unread conversations count container element.
*/
var getTotalUnreadConversationCountElement = function(root) {
return root.find(SELECTORS.SECTION_UNREAD_COUNT);
};
/**
* Increment the total conversations count.
*
* @param {Object} root Overview messages container element.
*/
var incrementTotalConversationCount = function(root) {
if (loadedTotalCounts) {
var element = getTotalConversationCountElement(root);
var count = parseInt(element.text());
count = count + 1;
element.text(count);
}
};
/**
* Decrement the total conversations count.
*
* @param {Object} root Overview messages container element.
*/
var decrementTotalConversationCount = function(root) {
if (loadedTotalCounts) {
var element = getTotalConversationCountElement(root);
var count = parseInt(element.text());
count = count - 1;
element.text(count);
}
};
/**
* Decrement the total unread conversations count.
*
* @param {Object} root Overview messages container element.
*/
var decrementTotalUnreadConversationCount = function(root) {
if (loadedUnreadCounts) {
var element = getTotalUnreadConversationCountElement(root);
var count = parseInt(element.text());
count = count - 1;
element.text(count);
if (count < 1) {
element.addClass('hidden');
}
}
};
/**
* Get a contact / conversation element.
*
* @param {Object} root Overview messages container element.
* @param {Number} conversationId The conversation id.
* @return {Object} Conversation element.
*/
var getConversationElement = function(root, conversationId) {
return root.find('[data-conversation-id="' + conversationId + '"]');
};
/**
* Get a contact / conversation element from a user id.
*
* @param {Object} root Overview messages container element.
* @param {Number} userId The user id.
* @return {Object} Conversation element.
*/
var getConversationElementFromUserId = function(root, userId) {
return root.find('[data-user-id="' + userId + '"]');
};
/**
* Show the conversation is muted icon.
*
* @param {Object} conversationElement The conversation element.
*/
var muteConversation = function(conversationElement) {
conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).removeClass('hidden');
};
/**
* Hide the conversation is muted icon.
*
* @param {Object} conversationElement The conversation element.
*/
var unmuteConversation = function(conversationElement) {
conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).addClass('hidden');
};
/**
* Show the contact is blocked icon.
*
* @param {Object} conversationElement The conversation element.
*/
var blockContact = function(conversationElement) {
conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).removeClass('hidden');
};
/**
* Hide the contact is blocked icon.
*
* @param {Object} conversationElement The conversation element.
*/
var unblockContact = function(conversationElement) {
conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).addClass('hidden');
};
/**
* Create an render new conversation element in the list of conversations.
*
* @param {Object} root Overview messages container element.
* @param {Object} conversation The conversation.
* @param {Number} userId The logged in user id.
* @return {Object} jQuery promise
*/
var createNewConversationFromEvent = function(root, conversation, userId) {
var existingConversations = root.find(SELECTORS.CONVERSATION);
if (!existingConversations.length) {
// If we didn't have any conversations then we need to show
// the content of the list and hide the empty message.
var listRoot = LazyLoadList.getRoot(root);
LazyLoadList.showContent(listRoot);
LazyLoadList.hideEmptyMessage(listRoot);
}
// Cache the conversation.
loadedConversationsById[conversation.id] = conversation;
return render([conversation], userId)
.then(function(html) {
var contentContainer = LazyLoadList.getContentContainer(root);
return contentContainer.prepend(html);
})
.then(function() {
return incrementTotalConversationCount(root);
})
.catch(Notification.exception);
};
/**
* Delete a conversation from the list of conversations.
*
* @param {Object} root Overview messages container element.
* @param {Object} conversationElement The conversation element.
*/
var deleteConversation = function(root, conversationElement) {
conversationElement.remove();
decrementTotalConversationCount(root);
var conversations = root.find(SELECTORS.CONVERSATION);
if (!conversations.length) {
// If we don't have any conversations then we need to hide
// the content of the list and show the empty message.
var listRoot = LazyLoadList.getRoot(root);
LazyLoadList.hideContent(listRoot);
LazyLoadList.showEmptyMessage(listRoot);
}
};
/**
* Mark a conversation as read.
*
* @param {Object} root Overview messages container element.
* @param {Object} conversationElement The conversation element.
*/
var markConversationAsRead = function(root, conversationElement) {
var unreadCount = conversationElement.find(SELECTORS.UNREAD_COUNT);
unreadCount.text('0');
unreadCount.addClass('hidden');
decrementTotalUnreadConversationCount(root);
};
/**
* Listen to, and handle events in this section.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} root The section container element.
* @param {Function} loadCallback The callback to load items.
* @param {Array|null} types The conversation types for this section
* @param {bool} includeFavourites If this section includes favourites
* @param {String} fromPanel Routing argument to send if the section is loaded in message index left panel.
*/
var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites, fromPanel) {
var listRoot = LazyLoadList.getRoot(root);
var conversationBelongsToThisSection = function(conversation) {
// Make sure the type is an int so that the index of check matches correctly.
var conversationType = parseInt(conversation.type, 10);
if (
// If the conversation type isn't one this section cares about then we can ignore it.
(types && types.indexOf(conversationType) < 0) ||
// If this is the favourites section and the conversation isn't a favourite then ignore it.
(includeFavourites && !conversation.isFavourite) ||
// If this section doesn't include favourites and the conversation is a favourite then ignore it.
(!includeFavourites && conversation.isFavourite)
) {
return false;
}
return true;
};
// Set the minimum height of the section to the height of the toggle. This
// smooths out the collapse animation.
var toggle = root.find(SELECTORS.TOGGLE);
root.css('min-height', toggle.outerHeight());
root.on('show.bs.collapse', function() {
setExpanded(root);
LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {
return render(conversations, userId)
.then(function(html) {
contentContainer.append(html);
return html;
})
.catch(Notification.exception);
});
});
root.on('hidden.bs.collapse', function() {
setCollapsed(root);
});
PubSub.subscribe(MessageDrawerEvents.CONTACT_BLOCKED, function(userId) {
var conversationElement = getConversationElementFromUserId(root, userId);
if (conversationElement.length) {
blockContact(conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONTACT_UNBLOCKED, function(userId) {
var conversationElement = getConversationElementFromUserId(root, userId);
if (conversationElement.length) {
unblockContact(conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_MUTED, function(conversation) {
var conversationId = conversation.id;
var conversationElement = getConversationElement(root, conversationId);
if (conversationElement.length) {
muteConversation(conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_MUTED, function(conversation) {
var conversationId = conversation.id;
var conversationElement = getConversationElement(root, conversationId);
if (conversationElement.length) {
unmuteConversation(conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {
if (!conversationBelongsToThisSection(conversation)) {
return;
}
var pendingPromise = new Pending('core_message/message_drawer_view_overview_section:new');
var loggedInUserId = conversation.loggedInUserId;
var conversationId = conversation.id;
var element = getConversationElement(root, conversationId);
conversation = formatConversationFromEvent(conversation);
if (element.length) {
var contentContainer = LazyLoadList.getContentContainer(root);
render([conversation], loggedInUserId)
.then(function(html) {
if (deletedConversationsById[conversationId]) {
// This conversation was deleted at some point since the messaging drawer was created.
if (conversation.messages[0].timeadded < deletedConversationsById[conversationId]) {
// The 'new' message was added before the conversation was deleted.
// This is probably stale data.
return;
}
}
contentContainer.prepend(html);
element.remove();
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
} else if (conversation.messages.length) {
createNewConversationFromEvent(root, conversation, loggedInUserId)
.then(pendingPromise.resolve)
.catch();
} else {
pendingPromise.resolve();
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_DELETED, function(conversationId) {
var conversationElement = getConversationElement(root, conversationId);
delete loadedConversationsById[conversationId];
deletedConversationsById[conversationId] = new Date();
if (conversationElement.length) {
deleteConversation(root, conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, function(conversationId) {
var conversationElement = getConversationElement(root, conversationId);
if (conversationElement.length) {
markConversationAsRead(root, conversationElement);
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {
var conversationElement = null;
if (conversationBelongsToThisSection(conversation)) {
conversationElement = getConversationElement(root, conversation.id);
if (!conversationElement.length) {
createNewConversationFromEvent(
root,
formatConversationFromEvent(conversation),
conversation.loggedInUserId
);
}
} else {
conversationElement = getConversationElement(root, conversation.id);
if (conversationElement.length) {
deleteConversation(root, conversationElement);
}
}
});
PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {
var conversationElement = null;
if (conversationBelongsToThisSection(conversation)) {
conversationElement = getConversationElement(root, conversation.id);
if (!conversationElement.length) {
createNewConversationFromEvent(
root,
formatConversationFromEvent(conversation),
conversation.loggedInUserId
);
}
} else {
conversationElement = getConversationElement(root, conversation.id);
if (conversationElement.length) {
deleteConversation(root, conversationElement);
}
}
});
CustomEvents.define(root, [CustomEvents.events.activate]);
root.on(CustomEvents.events.activate, SELECTORS.CONVERSATION, function(e, data) {
var conversationElement = $(e.target).closest(SELECTORS.CONVERSATION);
var conversationId = conversationElement.attr('data-conversation-id');
var conversation = loadedConversationsById[conversationId];
MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONVERSATION, conversation, fromPanel);
data.originalEvent.preventDefault();
});
};
/**
* Setup the section.
*
* @param {String} namespace Unique identifier for the Routes
* @param {Object} header The header container element.
* @param {Object} body The section container element.
* @param {Object} footer The footer container element.
* @param {Array} types The conversation types that show in this section
* @param {bool} includeFavourites If this section includes favourites
* @param {Object} totalCountPromise Resolves wth the total conversations count
* @param {Object} unreadCountPromise Resolves wth the unread conversations count
* @param {bool} fromPanel shown in message app panel.
*/
var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise,
fromPanel) {
var root = $(body);
if (!root.attr('data-init')) {
var loadCallback = getLoadCallback(types, includeFavourites, 0);
registerEventListeners(namespace, root, loadCallback, types, includeFavourites, fromPanel);
if (isVisible(root)) {
setExpanded(root);
var listRoot = LazyLoadList.getRoot(root);
LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {
return render(conversations, userId)
.then(function(html) {
contentContainer.append(html);
return html;
})
.catch(Notification.exception);
});
}
// This is given to us by the calling code because the total counts for all sections
// are loaded in a single ajax request rather than one request per section.
totalCountPromise.then(function(count) {
renderTotalCount(root, count);
loadedTotalCounts = true;
return;
})
.catch(function() {
// Silently ignore if we can't updated the counts. No need to bother the user.
});
// This is given to us by the calling code because the unread counts for all sections
// are loaded in a single ajax request rather than one request per section.
unreadCountPromise.then(function(count) {
renderUnreadCount(root, count);
loadedUnreadCounts = true;
return;
})
.catch(function() {
// Silently ignore if we can't updated the counts. No need to bother the user.
});
root.attr('data-init', true);
}
};
return {
show: show,
isVisible: isVisible
};
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,292 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the settings page in the message drawer.
*
* @module core_message/message_drawer_view_settings
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/notification',
'core/str',
'core/pubsub',
'core/templates',
'core_message/message_repository',
'core/custom_interaction_events',
'core_message/message_drawer_events'
],
function(
$,
Notification,
Str,
PubSub,
Templates,
Repository,
CustomEvents,
MessageDrawerEvents
) {
var SELECTORS = {
CHECKBOX: 'input[type="checkbox"]',
SETTINGS: '[data-region="settings"]',
PRIVACY_PREFERENCE: '[data-preference="blocknoncontacts"] input[type="radio"]',
NOTIFICATIONS_PREFERENCE: '[data-preference="notifications"] input[type="checkbox"]',
ENTER_TO_SEND_PREFERENCE: '[data-preference="entertosend"] input[type="checkbox"]',
NOTIFICATION_PREFERENCES_CONTAINER: '[data-region="notification-preference-container"]',
CONTENT_CONTAINER: '[data-region="content-container"]',
PLACEHOLDER_CONTAINER: '[data-region="placeholder-container"]'
};
var TEMPLATES = {
NOTIFICATION_PREFERENCES: 'core_message/message_drawer_view_settings_body_content_notification_preferences'
};
var NOTIFICATION_PREFERENCES_KEY = 'message_provider_moodle_instantmessage';
/**
* Select the correct radio button in the DOM for the privacy preference.
*
* @param {Object} body The settings body element.
* @param {Number} value Which radio button should be set
*/
var setPrivacyPreference = function(body, value) {
var inputs = body.find(SELECTORS.PRIVACY_PREFERENCE);
inputs.each(function(index, input) {
input = $(input);
if (input.val() == value) {
input.prop('checked', true);
} else {
input.prop('checked', false);
}
});
};
/**
* Set the "enter to send" checkbox to the correct value in the DOM.
*
* @param {Object} body The settings body element.
* @param {Bool} value Whether enter to send is enabled or disabled.
*/
var setEnterToSend = function(body, value) {
var checkbox = body.find(SELECTORS.ENTER_TO_SEND_PREFERENCE);
if (value) {
checkbox.prop('checked', true);
} else {
checkbox.prop('checked', false);
}
};
/**
* Send a request to the server to save the given preferences. Also publish
* a preferences updated event for the rest of the message drawer to
* subscribe to.
*
* @param {Number} loggedInUserId The logged in user id.
* @param {Array} preferences The preferences to set.
* @return {Object} jQuery promise
*/
var savePreferences = function(loggedInUserId, preferences) {
return Repository.savePreferences(loggedInUserId, preferences)
.then(function() {
PubSub.publish(MessageDrawerEvents.PREFERENCES_UPDATED, preferences);
return;
})
.catch(Notification.exception);
};
/**
* Create all of the event listeners for the message preferences page.
*
* @method registerEventListeners
* @param {Object} body The settings body element.
* @param {Number} loggedInUserId The logged in user id.
*/
var registerEventListeners = function(body, loggedInUserId) {
var settingsContainer = body.find(SELECTORS.SETTINGS);
CustomEvents.define(settingsContainer, [
CustomEvents.events.activate
]);
settingsContainer.on(CustomEvents.events.activate, SELECTORS.NOTIFICATIONS_PREFERENCE, function(e) {
var container = $(e.target).closest(SELECTORS.NOTIFICATION_PREFERENCES_CONTAINER);
var checkboxes = container.find(SELECTORS.CHECKBOX);
if (!checkboxes.length) {
return;
}
// The preference value is all of the enabled processors, comma separated, so let's
// see which ones are enabled.
var values = checkboxes.toArray().reduce(function(carry, checkbox) {
checkbox = $(checkbox);
if (checkbox.prop('checked')) {
carry.push(checkbox.attr('data-name'));
}
return carry;
}, []);
var newValue = values.length ? values.join(',') : 'none';
var preferences = [
{
type: 'message_provider_moodle_instantmessage_enabled',
value: newValue
}
];
savePreferences(loggedInUserId, preferences);
});
settingsContainer.on('change', SELECTORS.PRIVACY_PREFERENCE, function(e) {
var newValue = $(e.target).val();
var preferences = [
{
type: 'message_blocknoncontacts',
value: newValue
}
];
savePreferences(loggedInUserId, preferences);
});
settingsContainer.on(CustomEvents.events.activate, SELECTORS.ENTER_TO_SEND_PREFERENCE, function(e) {
var newValue = $(e.target).prop('checked');
var preferences = [
{
type: 'message_entertosend',
value: newValue
}
];
savePreferences(loggedInUserId, preferences);
});
};
/**
* Initialise the module by loading the user's messaging preferences from the server and
* rendering them in the settings page.
*
* Moodle may have many (or no) message processors enabled to notify the user when they
* receive messages. We need to dynamically build the settings page based on which processors
* are configured for the user.
*
* @param {Object} body The settings body element.
* @param {Number} loggedInUserId The logged in user id.
*/
var init = function(body, loggedInUserId) {
// Load the message preferences from the server.
Repository.getUserMessagePreferences(loggedInUserId)
.then(function(response) {
// Set the values of the stright forward preferences.
setPrivacyPreference(body, response.blocknoncontacts);
setEnterToSend(body, response.entertosend);
// Parse the list of other preferences into a more usable format.
var notificationProcessors = [];
if (response.preferences.components.length) {
response.preferences.components.forEach(function(component) {
if (component.notifications.length) {
// Filter down to just the notification processors that work on instant
// messaging. We don't care about another other ones.
var notificationPreferences = component.notifications.filter(function(notification) {
return notification.preferencekey == NOTIFICATION_PREFERENCES_KEY;
});
if (notificationPreferences.length) {
// Messaging only has one config at the moment which is for notifications
// on personal messages.
var configuration = component.notifications[0];
notificationProcessors = configuration.processors.map(function(processor) {
// Consider the the processor enabled if either preference is set. This is
// for backwards compatibility. Going forward they will be treated as one
// setting.
var checked = processor.enabled;
return {
displayname: processor.displayname,
name: processor.name,
checked: checked,
// The admin can force processors to be enabled at a site level so
// we need to check if this processor has been locked by the admin.
locked: processor.locked,
lockedmessage: processor.lockedmessage || null,
};
});
}
}
});
}
var container = body.find(SELECTORS.NOTIFICATION_PREFERENCES_CONTAINER);
if (notificationProcessors.length) {
// We have processors (i.e. email, mobile, jabber) to show.
container.removeClass('hidden');
// Render the processor options.
return Templates.render(TEMPLATES.NOTIFICATION_PREFERENCES, {processors: notificationProcessors})
.then(function(html) {
container.append(html);
return html;
});
} else {
return true;
}
})
.then(function() {
// We're done loading so hide the loading placeholder and show the settings.
body.find(SELECTORS.CONTENT_CONTAINER).removeClass('hidden');
body.find(SELECTORS.PLACEHOLDER_CONTAINER).addClass('hidden');
// Register the event listers for if the user wants to change the preferences.
registerEventListeners(body, loggedInUserId);
return;
})
.catch(Notification.exception);
};
/**
* Initialise the settings page by adding event listeners to
* the checkboxes.
*
* @param {string} namespace The route namespace.
* @param {Object} header The settings header element.
* @param {Object} body The settings body element.
* @param {Object} footer The footer body element.
* @param {Number} loggedInUserId The logged in user id.
* @return {Object} jQuery promise
*/
var show = function(namespace, header, body, footer, loggedInUserId) {
if (!body.attr('data-init')) {
init(body, loggedInUserId);
body.attr('data-init', true);
}
return $.Deferred().resolve().promise();
};
/**
* String describing this page used for aria-labels.
*
* @return {Object} jQuery promise
*/
var description = function() {
return Str.get_string('messagedrawerviewsettings', 'core_message');
};
return {
show: show,
description: description,
};
});
@@ -0,0 +1,63 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the preference for an individual notification type on the
* message preference page.
*
* @module core_message/message_notification_preference
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core_message/notification_preference'],
function($, NotificationPreference) {
var SELECTORS = {
PREFERENCE_KEY: '[data-preference-key]',
};
/**
* Constructor for the Preference.
*
* @class
* @param {object} element jQuery object root element of the preference
* @param {int} userId The current user id
*/
var MessageNotificationPreference = function(element, userId) {
NotificationPreference.call(this, element, userId);
};
/**
* Clone the parent prototype.
*/
MessageNotificationPreference.prototype = Object.create(NotificationPreference.prototype);
/**
* Set constructor.
*/
MessageNotificationPreference.prototype.constructor = MessageNotificationPreference;
/**
* Get the unique prefix key that identifies this user preference.
*
* @method getPreferenceKey
* @return {string}
*/
MessageNotificationPreference.prototype.getPreferenceKey = function() {
return this.root.find(SELECTORS.PREFERENCE_KEY).attr('data-preference-key');
};
return MessageNotificationPreference;
});
+105
View File
@@ -0,0 +1,105 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the message popover in the nav bar.
*
* @module core_message/message_popover
* @copyright 2018 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(
[
'jquery',
'core/custom_interaction_events',
'core/pubsub',
'core_message/message_drawer_events'
],
function(
$,
CustomEvents,
PubSub,
MessageDrawerEvents
) {
var SELECTORS = {
COUNT_CONTAINER: '[data-region="count-container"]'
};
/**
* Toggle the message drawer visibility.
*
* @param {String} buttonid The button id for the popover.
*/
var toggleMessageDrawerVisibility = function(buttonid) {
PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY, buttonid);
};
/**
* Decrement the unread conversation count in the nav bar if a conversation
* is read. When there are no unread conversations then hide the counter.
*
* @param {Object} button The button element for the popover.
* @return {Function}
*/
var handleDecrementConversationCount = function(button) {
return function() {
var countContainer = button.find(SELECTORS.COUNT_CONTAINER);
var count = parseInt(countContainer.text(), 10);
if (isNaN(count)) {
countContainer.addClass('hidden');
} else if (!count || count < 2) {
countContainer.addClass('hidden');
} else {
count = count - 1;
countContainer.text(count);
}
};
};
/**
* Add events listeners for when the popover icon is clicked and when conversations
* are read.
*
* @param {Object} button The button element for the popover.
*/
var registerEventListeners = function(button) {
CustomEvents.define(button, [CustomEvents.events.activate]);
button.on(CustomEvents.events.activate, function(e, data) {
toggleMessageDrawerVisibility(button.attr('id'));
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));
};
/**
* Initialise the message popover.
*
* @param {Object} button The button element for the popover.
*/
var init = function(button) {
button = $(button);
registerEventListeners(button);
};
return {
init: init,
};
});
+124
View File
@@ -0,0 +1,124 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the message preference page.
*
* @module core_message/message_preferences
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification',
'core_message/message_notification_preference', 'core/custom_interaction_events'],
function($, Ajax, Notification, MessageNotificationPreference, CustomEvents) {
var SELECTORS = {
PREFERENCE: '[data-state]',
PREFERENCES_CONTAINER: '[data-region="preferences-container"]',
CONTACTABLE_PRIVACY_CONTAINER: '[data-region="privacy-setting-container"]',
};
/**
* Constructor for the MessagePreferences.
*
* @class
* @param {object} element The root element for the message preferences
*/
var MessagePreferences = function(element) {
this.root = $(element);
this.userId = this.root.find(SELECTORS.PREFERENCES_CONTAINER).attr('data-user-id');
this.registerEventListeners();
};
/**
* Check if the preferences have been disabled on this page.
*
* @method preferencesDisabled
* @return {bool}
*/
MessagePreferences.prototype.preferencesDisabled = function() {
return this.root.find(SELECTORS.PREFERENCES_CONTAINER).hasClass('disabled');
};
/**
* Update the contactable privacy user preference in the DOM and
* send a request to update on the server.
*
* @return {Promise}
* @method saveContactablePrivacySetting
*/
MessagePreferences.prototype.saveContactablePrivacySetting = function() {
var container = this.root.find(SELECTORS.CONTACTABLE_PRIVACY_CONTAINER);
var value = $("input[type='radio']:checked").val();
if (container.hasClass('loading')) {
return $.Deferred().resolve();
}
container.addClass('loading');
var request = {
methodname: 'core_user_update_user_preferences',
args: {
userid: this.userId,
preferences: [
{
type: container.attr('data-preference-key'),
value: value,
}
]
}
};
return Ajax.call([request])[0]
.fail(Notification.exception)
.always(function() {
container.removeClass('loading');
});
};
/**
* Create all of the event listeners for the message preferences page.
*
* @method registerEventListeners
*/
MessagePreferences.prototype.registerEventListeners = function() {
CustomEvents.define(this.root, [
CustomEvents.events.activate
]);
this.root.on('change', function(e) {
// Add listener for privacy setting radio buttons change.
if (e.target.name == 'message_blocknoncontacts') {
this.saveContactablePrivacySetting();
} else {
// Add listener for processor preferences.
if (!this.preferencesDisabled()) {
var preferencesContainer = $(e.target).closest(SELECTORS.PREFERENCES_CONTAINER);
var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE);
var messagePreference = new MessageNotificationPreference(preferencesContainer, this.userId);
preferenceElement.addClass('loading');
messagePreference.save().always(function() {
preferenceElement.removeClass('loading');
});
}
}
}.bind(this));
};
return MessagePreferences;
});
File diff suppressed because it is too large Load Diff
+113
View File
@@ -0,0 +1,113 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Send bulk message to the given user ids.
*
* @module core_message/message_send_bulk
* @copyright 2019 Shamim Rezaie <shamim@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {get_string} from 'core/str';
import ModalSaveCancel from 'core/modal_save_cancel';
import Templates from 'core/templates';
import ModalEvents from 'core/modal_events';
import Ajax from 'core/ajax';
import Notification from 'core/notification';
/**
* Show the send message popup.
*
* @method showModal
* @param {int[]} users
* @param {Function} callback A callback to apply after the form is closed.
* @returns {Promise}
*/
export const showModal = (users, callback = null) => {
if (!users.length) {
// Nothing to do.
return Promise.resolve();
}
let titlePromise = null;
if (users.length == 1) {
titlePromise = get_string('sendbulkmessagesingle', 'core_message');
} else {
titlePromise = get_string('sendbulkmessage', 'core_message', users.length);
}
return ModalSaveCancel.create({
body: Templates.render('core_message/send_bulk_message', {}),
title: titlePromise,
show: true,
buttons: {
save: titlePromise,
},
})
.then(function(modal) {
// When the dialog is closed, perform the callback (if provided).
modal.getRoot().on(ModalEvents.hidden, function() {
if (callback) {
callback();
}
modal.getRoot().remove();
});
modal.getRoot().on(ModalEvents.save, function() {
let messageText = modal.getRoot().find('form textarea').val();
sendMessage(messageText, users);
});
return modal;
});
};
/**
* Send a message to these users.
*
* @method sendMessage
* @param {String} messageText
* @param {Number[]} users
* @returns {Promise}
*/
export const sendMessage = (messageText, users) => {
let messages = [];
users.forEach(user => {
messages.push({
touserid: user,
text: messageText
});
});
return Ajax.call([{
methodname: 'core_message_send_instant_messages',
args: {messages: messages}
}])[0]
.then(function(messageIds) {
if (messageIds.length == 1) {
return get_string('sendbulkmessagesentsingle', 'core_message');
} else {
return get_string('sendbulkmessagesent', 'core_message', messageIds.length);
}
})
.then(function(msg) {
Notification.addNotification({
message: msg,
type: "success"
});
return true;
})
.catch(Notification.exception);
};
+107
View File
@@ -0,0 +1,107 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to message a user from their profile page.
*
* @module core_message/message_user_button
* @copyright 2019 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/custom_interaction_events', 'core_message/message_drawer_helper', 'core/templates'],
function($, CustomEvents, MessageDrawerHelper, Templates) {
var SELECTORS = {
MESSAGE_TEXTAREA: '[data-region="send-message-txt"]',
MESSAGE_USER_BUTTON: '#message-user-button',
MESSAGE_JUMP: '[data-region="jumpto"]'
};
var TEMPLATES = {
CONTENT: 'core_message/message_jumpto'
};
/**
* Get the id for the user being messaged.
*
* @param {object} element jQuery object for the button
* @return {int}
*/
var getUserId = function(element) {
return parseInt(element.attr('data-userid'));
};
/**
* Returns the conversation id, 0 if none.
*
* @param {object} element jQuery object for the button
* @return {int}
*/
var getConversationId = function(element) {
return parseInt(element.attr('data-conversationid'));
};
/**
* Handles opening the messaging drawer to send a
* message to a given user.
*
* @method enhance
* @param {object} element jQuery object for the button
*/
var send = function(element) {
element = $(element);
var args = {
conversationid: getConversationId(element),
buttonid: $(element).attr('id'),
userid: getUserId(element)
};
Templates.render(TEMPLATES.CONTENT, {})
.then(function(html) {
element.after(html);
})
.then(function() {
$(SELECTORS.MESSAGE_USER_BUTTON).next().focus(function() {
$(SELECTORS.MESSAGE_TEXTAREA).focus();
});
});
CustomEvents.define(element, [CustomEvents.events.activate]);
element.on(CustomEvents.events.activate, function(e, data) {
if ($(e.target).hasClass('active')) {
MessageDrawerHelper.hide();
$(SELECTORS.MESSAGE_USER_BUTTON).next().attr('tabindex', -1);
} else {
$(SELECTORS.MESSAGE_USER_BUTTON).next().attr('tabindex', 0);
if (args.conversationid) {
MessageDrawerHelper.showConversation(args);
} else {
MessageDrawerHelper.createConversationWithUser(args);
}
}
$(e.target).focus();
$(e.target).toggleClass('active');
e.preventDefault();
data.originalEvent.preventDefault();
});
};
return /** @alias module:core_message/message_user_button */ {
send: send
};
});
+158
View File
@@ -0,0 +1,158 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the preference for an individual notification type on the
* message preference page.
*
* @module core_message/notification_preference
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification', 'core_message/notification_processor'],
function($, Ajax, Notification, NotificationProcessor) {
const SELECTORS = {
PROCESSOR: '[data-processor-name]',
STATE_INPUTS: '[data-state] input',
};
/**
* Constructor for the Preference.
*
* @class
* @param {object} element jQuery object root element of the preference
* @param {int} userId The current user id
*/
const NotificationPreference = function(element, userId) {
this.root = $(element);
this.userId = userId;
};
/**
* Get the unique prefix key that identifies this user preference.
*
* @method getPreferenceKey
* @return {string}
*/
NotificationPreference.prototype.getPreferenceKey = function() {
return this.root.attr('data-preference-key');
};
/**
* Get the unique key for the enabled preference.
*
* @method getEnabledPreferenceKey
* @return {string}
*/
NotificationPreference.prototype.getEnabledPreferenceKey = function() {
return this.getPreferenceKey() + '_enabled';
};
/**
* Get the list of Processors available for this preference.
*
* @method getProcessors
* @return {array}
*/
NotificationPreference.prototype.getProcessors = function() {
return this.root.find(SELECTORS.PROCESSOR).map(function(index, element) {
return new NotificationProcessor($(element));
});
};
/**
* Flag the preference as loading.
*
* @method startLoading
*/
NotificationPreference.prototype.startLoading = function() {
this.root.addClass('loading');
this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', true);
};
/**
* Remove the loading flag for this preference.
*
* @method stopLoading
*/
NotificationPreference.prototype.stopLoading = function() {
this.root.removeClass('loading');
this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', false);
};
/**
* Check if the preference is loading.
*
* @method isLoading
* @return {Boolean}
*/
NotificationPreference.prototype.isLoading = function() {
return this.root.hasClass('loading');
};
/**
* Persist the current state of the processors for this preference.
*
* @method save
* @return {object} jQuery promise
*/
NotificationPreference.prototype.save = function() {
if (this.isLoading()) {
return $.Deferred().resolve();
}
this.startLoading();
let enabledValue = '';
this.getProcessors().each(function(index, processor) {
if (processor.isEnabled()) {
if (enabledValue === '') {
enabledValue = processor.getName();
} else {
enabledValue += ',' + processor.getName();
}
}
});
if (enabledValue === '') {
enabledValue = 'none';
}
const args = {
userid: this.userId,
preferences: [
{
type: this.getEnabledPreferenceKey(),
value: enabledValue,
}
],
};
const request = {
methodname: 'core_user_update_user_preferences',
args: args,
};
return Ajax.call([request])[0]
.fail(Notification.exception)
.always(function() {
this.stopLoading();
}.bind(this));
};
return NotificationPreference;
});
+61
View File
@@ -0,0 +1,61 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Represents the notification processor (e.g. email, popup, jabber)
*
* @module core_message/notification_processor
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery'], function($) {
const SELECTORS = {
STATE_INPUTS: '.preference-state input.notification_enabled'
};
/**
* Constructor for the notification processor.
*
* @class
* @param {object} element jQuery object root element of the processor
*/
const NotificationProcessor = function(element) {
this.root = $(element);
};
/**
* Get the processor name.
*
* @method getName
* @return {string}
*/
NotificationProcessor.prototype.getName = function() {
return this.root.attr('data-processor-name');
};
/**
* Check if the processor is enabled when the user is logged in.
*
* @method isLoggedInEnabled
* @return {bool}
*/
NotificationProcessor.prototype.isEnabled = function() {
const enabled = this.root.find(SELECTORS.STATE_INPUTS);
return enabled.prop('checked');
};
return NotificationProcessor;
});
@@ -0,0 +1,310 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Load the settings for a message processor.
*
* @module core_message/notification_processor_settings
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as Ajax from 'core/ajax';
import * as Str from 'core/str';
import * as Notification from 'core/notification';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import * as Fragment from 'core/fragment';
const SELECTORS = {
SAVE_BUTTON: '[data-action="save"]',
CANCEL_BUTTON: '[data-action="cancel"]',
PROCESSOR: '[data-processor-name]',
PREFERENCE_ROW: '[data-region="preference-row"]',
};
export default class NotificationProcessorSettings extends Modal {
static TYPE = 'core_message-notification_processor_settings';
static TEMPLATE = 'core/modal_save_cancel';
/**
* Constructor for the Modal.
*
* @class
* @param {object} root The root jQuery element for the modal.
*/
constructor(root) {
super(root);
this.name = null;
this.userId = null;
this.contextId = null;
this.element = null;
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
this.cancelButton = this.getFooter().find(SELECTORS.CANCEL_BUTTON);
}
/**
* Set the userid to the given value.
*
* @method setUserId
* @param {int} id The notification userid
*/
setUserId(id) {
this.userId = id;
}
/**
* Retrieve the current userid, if any.
*
* @method getUserId
* @return {int|null} The notification userid
*/
getUserId() {
return this.userId;
}
/**
* Set the object to the given value.
*
* @method setElement
* @param {object} element The notification node element.
*/
setElement(element) {
this.element = element;
}
/**
* Retrieve the current element, if any.
*
* @method getElement
* @return {object|null} The notification node element.
*/
getElement() {
return this.element;
}
/**
* Set the name to the given value.
*
* @method setName
* @param {string} name The notification name.
*/
setName(name) {
this.name = name;
}
/**
* Retrieve the current name, if any.
*
* @method getName
* @return {string|null} The notification name.
*/
getName() {
return this.name;
}
/**
* Set the context id to the given value.
*
* @method setContextId
* @param {Number} id The notification context id
*/
setContextId(id) {
this.contextId = id;
}
/**
* Retrieve the current context id, if any.
*
* @method getContextId
* @return {Number|null} The notification context id
*/
getContextId() {
return this.contextId;
}
/**
* Get the form element from the modal.
*
* @method getForm
* @return {object}
*/
getForm() {
return this.getBody().find('form');
}
/**
* Disable the buttons in the footer.
*
* @method disableButtons
*/
disableButtons() {
this.saveButton.prop('disabled', true);
this.cancelButton.prop('disabled', true);
}
/**
* Enable the buttons in the footer.
*
* @method enableButtons
*/
enableButtons() {
this.saveButton.prop('disabled', false);
this.cancelButton.prop('disabled', false);
}
/**
* Load the title for the modal to the appropriate value
* depending on message outputs.
*
* @method loadTitleContent
* @return {object} A promise resolved with the new title text.
*/
loadTitleContent() {
this.titlePromise = Str.get_string('processorsettings', 'message');
this.setTitle(this.titlePromise);
return this.titlePromise;
}
/**
* Load the body for the modal to the appropriate value
* depending on message outputs.
*
* @method loadBodyContent
* @return {object} A promise resolved with the fragment html and js from
*/
loadBodyContent() {
this.disableButtons();
const args = {
userid: this.getUserId(),
type: this.getName(),
};
this.bodyPromise = Fragment.loadFragment('message', 'processor_settings', this.getContextId(), args);
this.setBody(this.bodyPromise);
this.bodyPromise.then(() => {
this.enableButtons();
return;
})
.catch(Notification.exception);
return this.bodyPromise;
}
/**
* Load both the title and body content.
*
* @method loadAllContent
* @return {object} promise
*/
loadAllContent() {
return $.when(this.loadTitleContent(), this.loadBodyContent());
}
/**
* Load the modal content before showing it. This
* is to allow us to re-use the same modal for creating and
* editing different message outputs within the page.
*
* @method show
*/
show() {
this.loadAllContent();
super.show(this);
}
/**
* Clear the notification from the modal when it's closed so
* that it is loaded fresh next time it's displayed.
*
* @method hide
*/
hide() {
super.hide(this);
this.setContextId(null);
this.setName(null);
this.setUserId(null);
}
/**
* Checks if the processor has been configured. If so then remove the unconfigured
* status from the interface.
*
* @method updateConfiguredStatus
* @return {Promise|boolean}
*/
updateConfiguredStatus() {
const processorHeader = $(this.getElement()).closest(SELECTORS.PROCESSOR);
if (!processorHeader.hasClass('unconfigured')) {
return false;
}
const processorName = processorHeader.attr('data-processor-name');
const request = {
methodname: 'core_message_get_message_processor',
args: {
name: processorName,
userid: this.userId,
},
};
return Ajax.call([request])[0]
.then((result) => {
// Check if the user has figured configuring the processor.
if (result.userconfigured) {
// If they have then we can enable the settings.
const notifications = $(SELECTORS.PREFERENCE_ROW + ' [data-processor-name="' + processorName + '"]');
processorHeader.removeClass('unconfigured');
notifications.removeClass('disabled');
}
return result;
});
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
// When the user clicks the save button we trigger the form submission.
this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, (e, data) => {
this.getForm().submit();
data.originalEvent.preventDefault();
});
this.getModal().on('mpp:formsubmitted', (e) => {
this.hide();
this.updateConfiguredStatus();
e.stopPropagation();
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {
this.hide();
data.originalEvent.preventDefault();
e.stopPropagation();
});
}
}
NotificationProcessorSettings.registerModalType();
@@ -0,0 +1,187 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Controls the preferences for the list of notification types on the
* message preference page
*
* @module core_message/preferences_notifications_list_controller
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery',
'core/ajax',
'core/notification',
'core/custom_interaction_events',
'core_message/notification_preference',
'core_message/notification_processor_settings',
],
function(
$,
Ajax,
Notification,
CustomEvents,
NotificationPreference,
NotificationProcessorSettings,
) {
var SELECTORS = {
DISABLE_NOTIFICATIONS: '[data-region="disable-notification-container"] [data-disable-notifications]',
DISABLE_NOTIFICATIONS_CONTAINER: '[data-region="disable-notification-container"]',
PREFERENCE: '.preference-state',
PREFERENCE_ROW: '[data-region="preference-row"]',
PREFERENCE_INPUT: '.preference-state input',
PROCESSOR_SETTING: '[data-processor-setting]',
};
/**
* Constructor for the PreferencesController.
*
* @class
* @param {object} element jQuery object root element of the preference
*/
var PreferencesController = function(element) {
this.root = $(element);
this.userId = this.root.attr('data-user-id');
this.registerEventListeners();
};
/**
* Check if the preferences are all disabled.
*
* @method isDisabled
* @return {bool}
*/
PreferencesController.prototype.isDisabled = function() {
return this.root.hasClass('disabled');
};
/**
* Disable all of the preferences.
*
* @method setDisabled
*/
PreferencesController.prototype.setDisabled = function() {
this.root.addClass('disabled');
this.root.find(SELECTORS.PREFERENCE_INPUT).prop('disabled', true);
};
/**
* Enable all of the preferences.
*
* @method setEnabled
*/
PreferencesController.prototype.setEnabled = function() {
this.root.removeClass('disabled');
this.root.find(SELECTORS.PREFERENCE_INPUT).prop('disabled', false);
};
/**
* Update the disable all notifications user property in the DOM and
* send a request to update on the server.
*
* @method toggleDisableAllStatus
* @return {Promise}
*/
PreferencesController.prototype.toggleDisableAllStatus = function() {
var checkbox = $(SELECTORS.DISABLE_NOTIFICATIONS);
var container = $(SELECTORS.DISABLE_NOTIFICATIONS_CONTAINER);
var ischecked = checkbox.prop('checked');
if (container.hasClass('loading')) {
return $.Deferred().resolve();
}
container.addClass('loading');
var request = {
methodname: 'core_user_update_user_preferences',
args: {
userid: this.userId,
emailstop: ischecked ? 1 : 0,
}
};
return Ajax.call([request])[0]
.done(function() {
if (ischecked) {
this.setDisabled();
} else {
this.setEnabled();
}
}.bind(this))
.always(function() {
container.removeClass('loading');
})
.fail(Notification.exception);
};
/**
* Set up all of the event listeners for the PreferencesController.
*
* @method registerEventListeners
*/
PreferencesController.prototype.registerEventListeners = function() {
var disabledNotificationsElement = $(SELECTORS.DISABLE_NOTIFICATIONS);
CustomEvents.define(this.root, [
CustomEvents.events.activate,
]);
this.root.on('change', function(e) {
if (!this.isDisabled()) {
var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE);
var preferenceRow = $(e.target).closest(SELECTORS.PREFERENCE_ROW);
var preference = new NotificationPreference(preferenceRow, this.userId);
preferenceElement.addClass('loading');
preference.save().always(function() {
preferenceElement.removeClass('loading');
});
}
}.bind(this));
var eventFormPromise = NotificationProcessorSettings.create({});
this.root.on(CustomEvents.events.activate, SELECTORS.PROCESSOR_SETTING, function(e, data) {
var element = $(e.target).closest(SELECTORS.PROCESSOR_SETTING);
data.originalEvent.preventDefault();
eventFormPromise.then(function(modal) {
// Configure modal with element settings.
modal.setUserId($(element).attr('data-user-id'));
modal.setName($(element).attr('data-name'));
modal.setContextId($(element).attr('data-context-id'));
modal.setElement(element);
modal.show();
e.stopImmediatePropagation();
return;
}).catch(Notification.exception);
});
CustomEvents.define(disabledNotificationsElement, [
CustomEvents.events.activate
]);
disabledNotificationsElement.on(CustomEvents.events.activate, function() {
this.toggleDisableAllStatus();
}.bind(this));
};
return PreferencesController;
});
@@ -0,0 +1,103 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Manages the processor form on the message preferences page.
*
* @module core_message/preferences_processor_form
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/notification'],
function($, Ajax, Notification) {
/**
* Constructor for the ProcessorForm.
*
* @class
* @param {object} element jQuery object root element of the preference
*/
var ProcessorForm = function(element) {
this.root = $(element);
this.userId = this.root.attr('data-user-id');
this.name = this.root.attr('data-processor-name');
this.root.find('form').on('submit', function(e) {
e.preventDefault();
this.save().done(function() {
$(element).trigger('mpp:formsubmitted');
});
}.bind(this));
};
/**
* Flag the processor as loading.
*
* @method startLoading
*/
ProcessorForm.prototype.startLoading = function() {
this.root.addClass('loading');
};
/**
* Remove the loading flag for this processor.
*
* @method stopLoading
*/
ProcessorForm.prototype.stopLoading = function() {
this.root.removeClass('loading');
};
/**
* Check if this processor is loading.
*
* @method isLoading
* @return {bool}
*/
ProcessorForm.prototype.isLoading = function() {
return this.root.hasClass('loading');
};
/**
* Persist the processor configuration.
*
* @method save
* @return {object} jQuery promise
*/
ProcessorForm.prototype.save = function() {
if (this.isLoading()) {
return $.Deferred();
}
this.startLoading();
var data = this.root.find('form').serializeArray();
var request = {
methodname: 'core_message_message_processor_config_form',
args: {
userid: this.userId,
name: this.name,
formvalues: data,
}
};
return Ajax.call([request])[0]
.fail(Notification.exception)
.always(function() {
this.stopLoading();
}.bind(this));
};
return ProcessorForm;
});
+232
View File
@@ -0,0 +1,232 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Module to add/remove contact using ajax.
*
* @module core_message/toggle_contact_button
* @copyright 2016 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/custom_interaction_events'],
function($, Ajax, Templates, Notification, CustomEvents) {
/**
* Check the state of the element, if the current user is a contact or not.
*
* @method isContact
* @param {object} element jQuery object for the button
* @return {bool}
*/
var isContact = function(element) {
return element.attr('data-is-contact') == '1';
};
/**
* Check the state of the element, if the current user has sent a contact request or not.
*
* @method isRequested
* @param {object} element jQuery object for the button
* @return {bool}
*/
let isRequested = (element) => element.attr('data-is-requested') == '1';
/**
* Record that the user has sent a contact request.
*
* @method setContactRequested
* @param {object} element jQuery object for the button
*/
var setContactRequested = function(element) {
element.attr('data-is-requested', '1');
};
/**
* Record that the user is not a contact.
*
* @method setNotContact
* @param {object} element jQuery object for the button
*/
var setNotContact = function(element) {
element.attr('data-is-contact', '0');
};
/**
* Get the id for the user being viewed.
*
* @method getUserId
* @param {object} element jQuery object for the button
* @return {int}
*/
var getUserId = function(element) {
return element.attr('data-userid');
};
/**
* Get the id for the logged in user.
*
* @method getUserId
* @param {object} element jQuery object for the button
* @return {int}
*/
var getCurrentUserId = function(element) {
return element.attr('data-currentuserid');
};
/**
* Check whether a text label should be displayed or not.
*
* @method getUserId
* @param {object} element jQuery object for the button
* @return {int}
*/
var displayTextLabel = function(element) {
return element.attr('data-display-text-label') == '1';
};
/**
* Check if this element is currently loading.
*
* @method isLoading
* @param {object} element jQuery object for the button
* @return {bool}
*/
var isLoading = function(element) {
return element.hasClass('loading') || element.attr('disabled');
};
/**
* Sends an ajax request to the server and handles the element state
* while the request is being performed.
*
* @method sendRequest
* @param {object} element jQuery object for the button
* @param {object} request Request hash to send
* @return {object} jQuery promise
*/
var sendRequest = function(element, request) {
if (isLoading(element)) {
return $.Deferred();
}
element.addClass('loading');
element.attr('disabled', 'disabled');
return Ajax.call([request])[0]
.fail(Notification.exception)
.always(function() {
element.removeClass('loading');
element.removeAttr('disabled');
});
};
/**
* Send a request to the server to add the current user as
* a contact. The contents of the button are changed to the
* remove contact button upon success.
*
* @method addContact
* @param {object} element jQuery object for the button
*/
var addContact = function(element) {
if (isLoading(element)) {
return;
}
var request = {
methodname: 'core_message_create_contact_request',
args: {
userid: getCurrentUserId(element),
requesteduserid: getUserId(element),
}
};
sendRequest(element, request).done(function() {
setContactRequested(element);
element.addClass('disabled');
const templateContext = {
'displaytextlabel': displayTextLabel(element)
};
Templates.render('message/contact_request_sent', templateContext).done(function(html, js) {
Templates.replaceNodeContents(element, html, js);
});
});
};
/**
* Send a request to the server to remove the current user as
* a contact. The contents of the button are changed to the
* add contact button upon success.
*
* @method removeContact
* @param {object} element jQuery object for the button
*/
var removeContact = function(element) {
if (isLoading(element)) {
return;
}
var request = {
methodname: 'core_message_delete_contacts',
args: {
userids: [getUserId(element)],
}
};
sendRequest(element, request).done(function() {
setNotContact(element);
const templateContext = {
'displaytextlabel': displayTextLabel(element)
};
Templates.render('message/add_contact_button', templateContext).done(function(html, js) {
Templates.replaceNodeContents(element, html, js);
});
});
};
/**
* Enhances the given element with a loading gif and event handles to make
* ajax requests to add or remove a contact where appropriate.
*
* @public
* @method enhance
* @param {object} element jQuery object for the button
*/
var enhance = function(element) {
element = $(element);
if (!element.children('.loading-icon').length && !isRequested(element)) {
// Add the loading gif if it isn't already there.
Templates.render('core/loading', {}).done(function(html, js) {
element.append(html, js);
});
}
CustomEvents.define(element, [CustomEvents.events.activate]);
element.on(CustomEvents.events.activate, function(e, data) {
if (isContact(element)) {
removeContact(element);
} else if (!isRequested(element)) {
addContact(element);
}
e.preventDefault();
data.originalEvent.preventDefault();
});
};
return {
enhance: enhance
};
});
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More