ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç PK>\'src/providerchooser.jsnu[// 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 . /** * AI provider selection handler. * * @module core_ai/providerchooser * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ const Selectors = { fields: { selector: '[data-aiproviderchooser-field="selector"]', updateButton: '[data-aiproviderchooser-field="updateButton"]', }, }; /** * Initialise the AI provider chooser. */ export const init = () => { document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => { const form = e.target.closest('form'); const updateButton = form.querySelector(Selectors.fields.updateButton); const url = new URL(form.action); form.action = url.toString(); updateButton.click(); }); }; PK>\o`src/policymodal.jsnu[// 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 . import Modal from 'core/modal'; import Policy from './policy'; import CustomEvents from 'core/custom_interaction_events'; /** * The Javascript module to handle the policy modal. * * @module core_ai/policymodal * @copyright Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class PolicyModal extends Modal { static TYPE = 'core_ai/policymodal'; static TEMPLATE = 'core_ai/policymodal'; /** * Configure the modal. * * @param {object} modalConfig The modal configuration. */ configure(modalConfig) { super.configure({ ...modalConfig, show: true, removeOnClose: true, }); this.context = modalConfig.context; this.setXlarge(); } /** * Set the modal to be extra large. */ setXlarge() { this.getModal().addClass('modal-xl'); } /** * Handle click events within the policy modal. */ registerEventListeners() { super.registerEventListeners(); this.registerCloseOnSave(); this.registerCloseOnCancel(); this.getModal().on(CustomEvents.events.activate, this.getActionSelector('save'), (e) => { e.preventDefault(); Policy.acceptPolicy(); }); } } PK>\X*B  )src/aiprovider_action_management_table.jsnu[// 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 . import PluginManagementTable from 'core_admin/plugin_management_table'; import {call as fetchMany} from 'core/ajax'; let watching = false; /** * Handles setting plugin state for the AI provider management table. * * @module core_ai/aiprovider_action_management_table * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class extends PluginManagementTable { /** * Constructor for the class. * * @param {int} providerid The provider id. */ constructor(providerid) { super(); // Call the parent constructor, so inherited properties and methods initialize properly. this.providerid = providerid; // Store provider id as an instance field. } /** * Initialise an instance of the class. * * @param {int} providerid The provider id. */ static init(providerid) { if (watching) { return; } watching = true; new this(providerid); } /** * Set the plugin state (enabled or disabled). * * @param {string} methodname The web service to call. * @param {string} plugin The name of the plugin and action to set the state for. * @param {number} state The state to set. * @returns {Promise} */ setPluginState(methodname, plugin, state) { const providerid = this.providerid; return fetchMany([{ methodname, args: { plugin, state, providerid, }, }])[0]; } } PK>\F""+src/aiprovider_instance_management_table.jsnu[// 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 . import PluginManagementTable from 'core_admin/plugin_management_table'; import {call as fetchMany} from 'core/ajax'; import {refreshTableContent} from 'core_table/dynamic'; import Pending from 'core/pending'; import {fetchNotifications} from 'core/notification'; import {prefetchStrings} from 'core/prefetch'; import {getString} from 'core/str'; import DeleteCancelModal from 'core/modal_delete_cancel'; import ModalEvents from 'core/modal_events'; let watching = false; /** * Handles setting plugin state for the AI provider management table. * * @module core_ai/aiprovider_instance_management_table * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class extends PluginManagementTable { constructor() { super(); this.addClickHandler(this.handleDelete); } /** * Initialise an instance of the class. * * This is just a way of making it easier to initialise an instance of the class from PHP. */ static init() { if (watching) { return; } prefetchStrings('core_ai', [ 'providerinstancedelete', 'providerinstancedeleteconfirm', ]); watching = true; new this(); } /** * Call the delete service. * * @param {string} methodname The web service to call * @param {number} providerid The provider id. * @return {Promise} The promise. */ deleteProvider(methodname, providerid) { return fetchMany([{ methodname, args: { providerid, }, }])[0]; } /** * Handle delete. * * @param {HTMLElement} tableRoot * @param {Event} e */ async handleDelete(tableRoot, e) { const deleteElement = e.target.closest('[data-delete-method]'); if (deleteElement) { e.preventDefault(); const providerId = e.target.dataset.id; const deleteMethod = e.target.dataset.deleteMethod; const bodyParams = { provider: e.target.dataset.provider, name: e.target.dataset.name, }; const modal = await DeleteCancelModal.create({ title: getString('providerinstancedelete', 'core_ai'), body: getString('providerinstancedeleteconfirm', 'core_ai', bodyParams), show: true, removeOnClose: true, }); // Handle delete event. modal.getRoot().on(ModalEvents.delete, async(e) => { e.preventDefault(); const pendingPromise = new Pending('core_table/dynamic:deleteProvider'); await this.deleteProvider(deleteMethod, providerId); // Reload the table, so we get the updated list of providers, and any messages. await Promise.all([ refreshTableContent(tableRoot), fetchNotifications(), ]); modal.destroy(); pendingPromise.resolve(); }); } } } PK>\>;src/repository.jsnu[// 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 . /** * Repository helper for the Moodle Tiny Autosave plugin. * * @module tiny_autosave/repository * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call} from 'core/ajax'; import * as config from 'core/config'; import * as Options from './options'; import Pending from 'core/pending'; import {ensureEditorIsValid} from 'editor_tiny/utils'; const fetchOne = (methodname, args) => call([{ methodname, args, }])[0]; /** * Resume an Autosave session. * * @param {TinyMCE} editor The TinyMCE editor instance * @returns {Promise} The Autosave session */ export const resumeAutosaveSession = (editor) => { if (!ensureEditorIsValid(editor)) { return Promise.reject('Invalid editor'); } const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession'); return fetchOne('tiny_autosave_resume_session', { contextid: Options.getContextId(editor), pagehash: Options.getPageHash(editor), pageinstance: Options.getPageInstance(editor), elementid: editor.targetElm.id, draftid: Options.getDraftItemId(editor), }) .then((result) => { pendingPromise.resolve(); return result; }); }; /** * Update the content of the Autosave session. * * @param {TinyMCE} editor The TinyMCE editor instance * @returns {Promise} The Autosave session */ export const updateAutosaveSession = (editor) => { if (!ensureEditorIsValid(editor)) { return Promise.reject('Invalid editor'); } if (Options.hasAutosaveHasReset(editor)) { return Promise.reject('Skipping store of autosave content - content has been reset'); } const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession'); return fetchOne('tiny_autosave_update_session', { contextid: Options.getContextId(editor), pagehash: Options.getPageHash(editor), pageinstance: Options.getPageInstance(editor), elementid: editor.targetElm.id, drafttext: editor.getContent(), }) .then((result) => { pendingPromise.resolve(); return result; }); }; /** * Remove the Autosave session. * * @param {TinyMCE} editor The TinyMCE editor instance */ export const removeAutosaveSession = (editor) => { if (!ensureEditorIsValid(editor)) { throw new Error('Invalid editor'); } Options.setAutosaveHasReset(editor); // Please note that we must use a Beacon send here. // The XHR is not guaranteed because it will be aborted on page transition. // https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API // Note: Moodle does not currently have a sendBeacon API endpoint. const requestUrl = new URL(`${config.wwwroot}/lib/ajax/service.php`); requestUrl.searchParams.set('sesskey', config.sesskey); const args = { contextid: Options.getContextId(editor), pagehash: Options.getPageHash(editor), pageinstance: Options.getPageInstance(editor), elementid: editor.targetElm.id, }; navigator.sendBeacon(requestUrl, JSON.stringify([{ index: 0, methodname: 'tiny_autosave_reset_session', args, }])); }; PK>\Qk k src/helper.jsnu[// 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 . /** * The helper module or AI Subsystem. * * @module core_ai/helper * @copyright 2024 Huong Nguyen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class AIHelper { /** * Replace double line breaks with
and with

for paragraphs. * This is to handle the difference in response from the AI to what is expected by the editor. * * @param {String} text The text to replace. * @returns {String} */ static replaceLineBreaks(text) { // Replace double line breaks with

for paragraphs const textWithParagraphs = text.replace(/\n{2,}|\r\n/g, '

'); // Replace remaining single line breaks with
tags const textWithBreaks = textWithParagraphs.replace(/\n/g, '
'); // Add opening and closing

tags to wrap the entire content return `

${textWithBreaks}

`; } /** * Replace markdown formatting. * Even when asked not to, AI models will sometimes return markdown. * * @param {String} text The text to replace. * @returns {String} */ static replaceMarkdown(text) { // Replace markdown bold formatting HTML equivalent. const textWithMarkdown = text.replace(/\*\*(.*?)\*\*/g, '$1'); return textWithMarkdown; } /** * Format the response provided by the AI model. * * @param {String} text The text to format. * @returns {String} */ static formatResponse(text) { let formattedText = this.replaceLineBreaks(text) ; formattedText = this.replaceMarkdown(formattedText); return formattedText; } } PK>\j## src/policy.jsnu[// 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 . import {getPolicyStatus, setPolicyStatus} from "./repository"; /** * The Javascript module to handle the policy acceptance. * * @module core_ai/policy * @copyright 2024 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default class { static #policyAcceptedFor = {}; static preconfigurePolicyState(userid, state) { if (!this.#policyAcceptedFor.hasOwnProperty(userid)) { this.#policyAcceptedFor[userid] = state; } } /** * Get the policy status for a user. * * @param {Number} userid The user ID. * @return {Promise} The policy status. */ static async getPolicyStatus(userid) { if (this.#policyAcceptedFor[userid]) { return this.#policyAcceptedFor[userid]; } const accepted = await getPolicyStatus(userid); this.#policyAcceptedFor[userid] = accepted.status; return accepted.status; } static acceptPolicy() { this.#policyAcceptedFor[M.cfg.userId] = true; return setPolicyStatus(M.cfg.contextid); } } PK>\Q build/repository.min.jsnu[define("tiny_autosave/repository",["exports","core/ajax","core/config","./options","core/pending","editor_tiny/utils"],(function(_exports,_ajax,config,Options,_pending,_utils){var obj;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} /** * Repository helper for the Moodle Tiny Autosave plugin. * * @module tiny_autosave/repository * @copyright 2022 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateAutosaveSession=_exports.resumeAutosaveSession=_exports.removeAutosaveSession=void 0,config=_interopRequireWildcard(config),Options=_interopRequireWildcard(Options),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};const fetchOne=(methodname,args)=>(0,_ajax.call)([{methodname:methodname,args:args}])[0];_exports.resumeAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");const pendingPromise=new _pending.default("tiny_autosave/repository:resumeAutosaveSession");return fetchOne("tiny_autosave_resume_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,draftid:Options.getDraftItemId(editor)}).then((result=>(pendingPromise.resolve(),result)))};_exports.updateAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))return Promise.reject("Invalid editor");if(Options.hasAutosaveHasReset(editor))return Promise.reject("Skipping store of autosave content - content has been reset");const pendingPromise=new _pending.default("tiny_autosave/repository:updateAutosaveSession");return fetchOne("tiny_autosave_update_session",{contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id,drafttext:editor.getContent()}).then((result=>(pendingPromise.resolve(),result)))};_exports.removeAutosaveSession=editor=>{if(!(0,_utils.ensureEditorIsValid)(editor))throw new Error("Invalid editor");Options.setAutosaveHasReset(editor);const requestUrl=new URL("".concat(config.wwwroot,"/lib/ajax/service.php"));requestUrl.searchParams.set("sesskey",config.sesskey);const args={contextid:Options.getContextId(editor),pagehash:Options.getPageHash(editor),pageinstance:Options.getPageInstance(editor),elementid:editor.targetElm.id};navigator.sendBeacon(requestUrl,JSON.stringify([{index:0,methodname:"tiny_autosave_reset_session",args:args}]))}})); //# sourceMappingURL=repository.min.js.mapPK>\Fee/build/aiprovider_action_management_table.min.jsnu[define("core_ai/aiprovider_action_management_table",["exports","core_admin/plugin_management_table","core/ajax"],(function(_exports,_plugin_management_table,_ajax){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=(obj=_plugin_management_table)&&obj.__esModule?obj:{default:obj};let watching=!1; /** * Handles setting plugin state for the AI provider management table. * * @module core_ai/aiprovider_action_management_table * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */class _default extends _plugin_management_table.default{constructor(providerid){super(),this.providerid=providerid}static init(providerid){watching||(watching=!0,new this(providerid))}setPluginState(methodname,plugin,state){const providerid=this.providerid;return(0,_ajax.call)([{methodname:methodname,args:{plugin:plugin,state:state,providerid:providerid}}])[0]}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=aiprovider_action_management_table.min.js.mapPK>\SFd d 1build/aiprovider_instance_management_table.min.jsnu[define("core_ai/aiprovider_instance_management_table",["exports","core_admin/plugin_management_table","core/ajax","core_table/dynamic","core/pending","core/notification","core/prefetch","core/str","core/modal_delete_cancel","core/modal_events"],(function(_exports,_plugin_management_table,_ajax,_dynamic,_pending,_notification,_prefetch,_str,_modal_delete_cancel,_modal_events){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_plugin_management_table=_interopRequireDefault(_plugin_management_table),_pending=_interopRequireDefault(_pending),_modal_delete_cancel=_interopRequireDefault(_modal_delete_cancel),_modal_events=_interopRequireDefault(_modal_events);let watching=!1; /** * Handles setting plugin state for the AI provider management table. * * @module core_ai/aiprovider_instance_management_table * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */class _default extends _plugin_management_table.default{constructor(){super(),this.addClickHandler(this.handleDelete)}static init(){watching||((0,_prefetch.prefetchStrings)("core_ai",["providerinstancedelete","providerinstancedeleteconfirm"]),watching=!0,new this)}deleteProvider(methodname,providerid){return(0,_ajax.call)([{methodname:methodname,args:{providerid:providerid}}])[0]}async handleDelete(tableRoot,e){if(e.target.closest("[data-delete-method]")){e.preventDefault();const providerId=e.target.dataset.id,deleteMethod=e.target.dataset.deleteMethod,bodyParams={provider:e.target.dataset.provider,name:e.target.dataset.name},modal=await _modal_delete_cancel.default.create({title:(0,_str.getString)("providerinstancedelete","core_ai"),body:(0,_str.getString)("providerinstancedeleteconfirm","core_ai",bodyParams),show:!0,removeOnClose:!0});modal.getRoot().on(_modal_events.default.delete,(async e=>{e.preventDefault();const pendingPromise=new _pending.default("core_table/dynamic:deleteProvider");await this.deleteProvider(deleteMethod,providerId),await Promise.all([(0,_dynamic.refreshTableContent)(tableRoot),(0,_notification.fetchNotifications)()]),modal.destroy(),pendingPromise.resolve()}))}}}return _exports.default=_default,_exports.default})); //# sourceMappingURL=aiprovider_instance_management_table.min.js.mapPK>\-8build/repository.min.js.mapnu[{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Repository helper for the Moodle Tiny Autosave plugin.\n *\n * @module tiny_autosave/repository\n * @copyright 2022 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call} from 'core/ajax';\nimport * as config from 'core/config';\nimport * as Options from './options';\nimport Pending from 'core/pending';\nimport {ensureEditorIsValid} from 'editor_tiny/utils';\n\nconst fetchOne = (methodname, args) => call([{\n methodname,\n args,\n}])[0];\n\n/**\n * Resume an Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise} The Autosave session\n */\nexport const resumeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n const pendingPromise = new Pending('tiny_autosave/repository:resumeAutosaveSession');\n return fetchOne('tiny_autosave_resume_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n draftid: Options.getDraftItemId(editor),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Update the content of the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n * @returns {Promise} The Autosave session\n */\nexport const updateAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n return Promise.reject('Invalid editor');\n }\n if (Options.hasAutosaveHasReset(editor)) {\n return Promise.reject('Skipping store of autosave content - content has been reset');\n }\n\n const pendingPromise = new Pending('tiny_autosave/repository:updateAutosaveSession');\n\n return fetchOne('tiny_autosave_update_session', {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n drafttext: editor.getContent(),\n })\n .then((result) => {\n pendingPromise.resolve();\n return result;\n });\n};\n\n/**\n * Remove the Autosave session.\n *\n * @param {TinyMCE} editor The TinyMCE editor instance\n */\nexport const removeAutosaveSession = (editor) => {\n if (!ensureEditorIsValid(editor)) {\n throw new Error('Invalid editor');\n }\n Options.setAutosaveHasReset(editor);\n\n // Please note that we must use a Beacon send here.\n // The XHR is not guaranteed because it will be aborted on page transition.\n // https://developer.mozilla.org/en-US/docs/Web/API/Beacon_API\n // Note: Moodle does not currently have a sendBeacon API endpoint.\n const requestUrl = new URL(`${config.wwwroot}/lib/ajax/service.php`);\n requestUrl.searchParams.set('sesskey', config.sesskey);\n\n const args = {\n contextid: Options.getContextId(editor),\n pagehash: Options.getPageHash(editor),\n pageinstance: Options.getPageInstance(editor),\n elementid: editor.targetElm.id,\n };\n navigator.sendBeacon(requestUrl, JSON.stringify([{\n index: 0,\n methodname: 'tiny_autosave_reset_session',\n args,\n }]));\n};\n"],"names":["fetchOne","methodname","args","editor","Promise","reject","pendingPromise","Pending","contextid","Options","getContextId","pagehash","getPageHash","pageinstance","getPageInstance","elementid","targetElm","id","draftid","getDraftItemId","then","result","resolve","hasAutosaveHasReset","drafttext","getContent","Error","setAutosaveHasReset","requestUrl","URL","config","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index"],"mappings":";;;;;;;iTA6BMA,SAAW,CAACC,WAAYC,QAAS,cAAK,CAAC,CACzCD,WAAAA,WACAC,KAAAA,QACA,kCAQkCC,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,wBAEpBC,eAAiB,IAAIC,iBAAQ,yDAC5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BC,QAAST,QAAQU,eAAehB,UAEnCiB,MAAMC,SACHf,eAAegB,UACRD,0CAUuBlB,cAC7B,8BAAoBA,eACdC,QAAQC,OAAO,qBAEtBI,QAAQc,oBAAoBpB,eACrBC,QAAQC,OAAO,qEAGpBC,eAAiB,IAAIC,iBAAQ,yDAE5BP,SAAS,+BAAgC,CAC5CQ,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,GAC5BO,UAAWrB,OAAOsB,eAErBL,MAAMC,SACHf,eAAegB,UACRD,0CASuBlB,cAC7B,8BAAoBA,cACf,IAAIuB,MAAM,kBAEpBjB,QAAQkB,oBAAoBxB,cAMtByB,WAAa,IAAIC,cAAOC,OAAOC,kCACrCH,WAAWI,aAAaC,IAAI,UAAWH,OAAOI,eAExChC,KAAO,CACTM,UAAWC,QAAQC,aAAaP,QAChCQ,SAAUF,QAAQG,YAAYT,QAC9BU,aAAcJ,QAAQK,gBAAgBX,QACtCY,UAAWZ,OAAOa,UAAUC,IAEhCkB,UAAUC,WAAWR,WAAYS,KAAKC,UAAU,CAAC,CAC7CC,MAAO,EACPtC,WAAY,8BACZC,KAAAA"}PK>\Ec build/policymodal.min.js.mapnu[{"version":3,"file":"policymodal.min.js","sources":["../src/policymodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport Modal from 'core/modal';\nimport Policy from './policy';\nimport CustomEvents from 'core/custom_interaction_events';\n\n/**\n * The Javascript module to handle the policy modal.\n *\n * @module core_ai/policymodal\n * @copyright Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class PolicyModal extends Modal {\n static TYPE = 'core_ai/policymodal';\n static TEMPLATE = 'core_ai/policymodal';\n\n /**\n * Configure the modal.\n *\n * @param {object} modalConfig The modal configuration.\n */\n configure(modalConfig) {\n super.configure({\n ...modalConfig,\n show: true,\n removeOnClose: true,\n });\n\n this.context = modalConfig.context;\n this.setXlarge();\n }\n\n /**\n * Set the modal to be extra large.\n */\n setXlarge() {\n this.getModal().addClass('modal-xl');\n }\n\n /**\n * Handle click events within the policy modal.\n */\n registerEventListeners() {\n super.registerEventListeners();\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n\n this.getModal().on(CustomEvents.events.activate, this.getActionSelector('save'), (e) => {\n e.preventDefault();\n Policy.acceptPolicy();\n });\n }\n}\n"],"names":["PolicyModal","Modal","configure","modalConfig","show","removeOnClose","context","setXlarge","getModal","addClass","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","on","CustomEvents","events","activate","this","getActionSelector","e","preventDefault","acceptPolicy"],"mappings":";;;;;;;uPA0BqBA,oBAAoBC,eASrCC,UAAUC,mBACAD,UAAU,IACTC,YACHC,MAAM,EACNC,eAAe,SAGdC,QAAUH,YAAYG,aACtBC,YAMTA,iBACSC,WAAWC,SAAS,YAM7BC,+BACUA,8BACDC,2BACAC,6BAEAJ,WAAWK,GAAGC,mCAAaC,OAAOC,SAAUC,KAAKC,kBAAkB,SAAUC,IAC9EA,EAAEC,iCACKC,uEArCErB,mBACH,uCADGA,uBAEC"}PK>\S6_F F 3build/aiprovider_action_management_table.min.js.mapnu[{"version":3,"file":"aiprovider_action_management_table.min.js","sources":["../src/aiprovider_action_management_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport PluginManagementTable from 'core_admin/plugin_management_table';\nimport {call as fetchMany} from 'core/ajax';\n\nlet watching = false;\n\n/**\n * Handles setting plugin state for the AI provider management table.\n *\n * @module core_ai/aiprovider_action_management_table\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends PluginManagementTable {\n\n /**\n * Constructor for the class.\n *\n * @param {int} providerid The provider id.\n */\n constructor(providerid) {\n super(); // Call the parent constructor, so inherited properties and methods initialize properly.\n this.providerid = providerid; // Store provider id as an instance field.\n }\n\n /**\n * Initialise an instance of the class.\n *\n * @param {int} providerid The provider id.\n */\n static init(providerid) {\n if (watching) {\n return;\n }\n watching = true;\n new this(providerid);\n }\n\n /**\n * Set the plugin state (enabled or disabled).\n *\n * @param {string} methodname The web service to call.\n * @param {string} plugin The name of the plugin and action to set the state for.\n * @param {number} state The state to set.\n * @returns {Promise}\n */\n setPluginState(methodname, plugin, state) {\n const providerid = this.providerid;\n return fetchMany([{\n methodname,\n args: {\n plugin,\n state,\n providerid,\n },\n }])[0];\n }\n\n}\n"],"names":["watching","PluginManagementTable","constructor","providerid","this","setPluginState","methodname","plugin","state","args"],"mappings":"0VAiBIA,UAAW;;;;;;;4BAScC,iCAOzBC,YAAYC,yBAEHA,WAAaA,uBAQVA,YACJH,WAGJA,UAAW,MACPI,KAAKD,aAWbE,eAAeC,WAAYC,OAAQC,aACzBL,WAAaC,KAAKD,kBACjB,cAAU,CAAC,CACdG,WAAAA,WACAG,KAAM,CACFF,OAAAA,OACAC,MAAAA,MACAL,WAAAA,eAEJ"}PK>\c:66build/policymodal.min.jsnu[define("core_ai/policymodal",["exports","core/modal","./policy","core/custom_interaction_events"],(function(_exports,_modal,_policy,_custom_interaction_events){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} /** * The Javascript module to handle the policy modal. * * @module core_ai/policymodal * @copyright Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=_interopRequireDefault(_modal),_policy=_interopRequireDefault(_policy),_custom_interaction_events=_interopRequireDefault(_custom_interaction_events);class PolicyModal extends _modal.default{configure(modalConfig){super.configure({...modalConfig,show:!0,removeOnClose:!0}),this.context=modalConfig.context,this.setXlarge()}setXlarge(){this.getModal().addClass("modal-xl")}registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel(),this.getModal().on(_custom_interaction_events.default.events.activate,this.getActionSelector("save"),(e=>{e.preventDefault(),_policy.default.acceptPolicy()}))}}return _exports.default=PolicyModal,_defineProperty(PolicyModal,"TYPE","core_ai/policymodal"),_defineProperty(PolicyModal,"TEMPLATE","core_ai/policymodal"),_exports.default})); //# sourceMappingURL=policymodal.min.js.mapPK>\<build/providerchooser.min.jsnu[define("core_ai/providerchooser",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0; /** * AI provider selection handler. * * @module core_ai/providerchooser * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ const Selectors_fields={selector:'[data-aiproviderchooser-field="selector"]',updateButton:'[data-aiproviderchooser-field="updateButton"]'};_exports.init=()=>{var _document$querySelect;null===(_document$querySelect=document.querySelector(Selectors_fields.selector))||void 0===_document$querySelect||_document$querySelect.addEventListener("change",(e=>{const form=e.target.closest("form"),updateButton=form.querySelector(Selectors_fields.updateButton),url=new URL(form.action);form.action=url.toString(),updateButton.click()}))}})); //# sourceMappingURL=providerchooser.min.js.mapPK>\>Ƃ2 2 build/helper.min.js.mapnu[{"version":3,"file":"helper.min.js","sources":["../src/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 .\n\n/**\n * The helper module or AI Subsystem.\n *\n * @module core_ai/helper\n * @copyright 2024 Huong Nguyen \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class AIHelper {\n /**\n * Replace double line breaks with
and with

for paragraphs.\n * This is to handle the difference in response from the AI to what is expected by the editor.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceLineBreaks(text) {\n // Replace double line breaks with

for paragraphs\n const textWithParagraphs = text.replace(/\\n{2,}|\\r\\n/g, '

');\n\n // Replace remaining single line breaks with
tags\n const textWithBreaks = textWithParagraphs.replace(/\\n/g, '
');\n\n // Add opening and closing

tags to wrap the entire content\n return `

${textWithBreaks}

`;\n }\n\n /**\n * Replace markdown formatting.\n * Even when asked not to, AI models will sometimes return markdown.\n *\n * @param {String} text The text to replace.\n * @returns {String}\n */\n static replaceMarkdown(text) {\n // Replace markdown bold formatting HTML equivalent.\n const textWithMarkdown = text.replace(/\\*\\*(.*?)\\*\\*/g, '$1');\n\n return textWithMarkdown;\n }\n\n /**\n * Format the response provided by the AI model.\n *\n * @param {String} text The text to format.\n * @returns {String}\n */\n static formatResponse(text) {\n let formattedText = this.replaceLineBreaks(text) ;\n formattedText = this.replaceMarkdown(formattedText);\n\n return formattedText;\n }\n}\n"],"names":["text","textWithBreaks","replace","formattedText","this","replaceLineBreaks","replaceMarkdown"],"mappings":";;;;;;;;+BA8B6BA,YAKfC,eAHqBD,KAAKE,QAAQ,eAAgB,cAGdA,QAAQ,MAAO,4BAG5CD,8CAUMD,aAEMA,KAAKE,QAAQ,iBAAkB,6CAWtCF,UACdG,cAAgBC,KAAKC,kBAAkBL,aAC3CG,cAAgBC,KAAKE,gBAAgBH,eAE9BA"}PK>\7]IIbuild/helper.min.jsnu[define("core_ai/helper",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default= /** * The helper module or AI Subsystem. * * @module core_ai/helper * @copyright 2024 Huong Nguyen * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class{static replaceLineBreaks(text){const textWithBreaks=text.replace(/\n{2,}|\r\n/g,"

").replace(/\n/g,"
");return"

".concat(textWithBreaks,"

")}static replaceMarkdown(text){return text.replace(/\*\*(.*?)\*\*/g,"$1")}static formatResponse(text){let formattedText=this.replaceLineBreaks(text);return formattedText=this.replaceMarkdown(formattedText),formattedText}},_exports.default})); //# sourceMappingURL=helper.min.js.mapPK>\T5build/aiprovider_instance_management_table.min.js.mapnu[{"version":3,"file":"aiprovider_instance_management_table.min.js","sources":["../src/aiprovider_instance_management_table.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport PluginManagementTable from 'core_admin/plugin_management_table';\nimport {call as fetchMany} from 'core/ajax';\nimport {refreshTableContent} from 'core_table/dynamic';\nimport Pending from 'core/pending';\nimport {fetchNotifications} from 'core/notification';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport DeleteCancelModal from 'core/modal_delete_cancel';\nimport ModalEvents from 'core/modal_events';\n\nlet watching = false;\n\n/**\n * Handles setting plugin state for the AI provider management table.\n *\n * @module core_ai/aiprovider_instance_management_table\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class extends PluginManagementTable {\n constructor() {\n super();\n this.addClickHandler(this.handleDelete);\n }\n\n /**\n * Initialise an instance of the class.\n *\n * This is just a way of making it easier to initialise an instance of the class from PHP.\n */\n static init() {\n if (watching) {\n return;\n }\n\n prefetchStrings('core_ai', [\n 'providerinstancedelete',\n 'providerinstancedeleteconfirm',\n ]);\n\n watching = true;\n new this();\n }\n\n /**\n * Call the delete service.\n *\n * @param {string} methodname The web service to call\n * @param {number} providerid The provider id.\n * @return {Promise} The promise.\n */\n deleteProvider(methodname, providerid) {\n return fetchMany([{\n methodname,\n args: {\n providerid,\n },\n }])[0];\n }\n\n /**\n * Handle delete.\n *\n * @param {HTMLElement} tableRoot\n * @param {Event} e\n */\n async handleDelete(tableRoot, e) {\n const deleteElement = e.target.closest('[data-delete-method]');\n if (deleteElement) {\n e.preventDefault();\n const providerId = e.target.dataset.id;\n const deleteMethod = e.target.dataset.deleteMethod;\n const bodyParams = {\n provider: e.target.dataset.provider,\n name: e.target.dataset.name,\n };\n const modal = await DeleteCancelModal.create({\n title: getString('providerinstancedelete', 'core_ai'),\n body: getString('providerinstancedeleteconfirm', 'core_ai', bodyParams),\n show: true,\n removeOnClose: true,\n });\n\n // Handle delete event.\n modal.getRoot().on(ModalEvents.delete, async(e) => {\n e.preventDefault();\n const pendingPromise = new Pending('core_table/dynamic:deleteProvider');\n await this.deleteProvider(deleteMethod, providerId);\n // Reload the table, so we get the updated list of providers, and any messages.\n await Promise.all([\n refreshTableContent(tableRoot),\n fetchNotifications(),\n ]);\n modal.destroy();\n pendingPromise.resolve();\n });\n }\n }\n}\n"],"names":["watching","PluginManagementTable","constructor","addClickHandler","this","handleDelete","deleteProvider","methodname","providerid","args","tableRoot","e","target","closest","preventDefault","providerId","dataset","id","deleteMethod","bodyParams","provider","name","modal","DeleteCancelModal","create","title","body","show","removeOnClose","getRoot","on","ModalEvents","delete","async","pendingPromise","Pending","Promise","all","destroy","resolve"],"mappings":"0wBAyBIA,UAAW;;;;;;;4BAScC,iCACzBC,2BAESC,gBAAgBC,KAAKC,4BAStBL,yCAIY,UAAW,CACvB,yBACA,kCAGJA,UAAW,MACPI,MAURE,eAAeC,WAAYC,mBAChB,cAAU,CAAC,CACdD,WAAAA,WACAE,KAAM,CACFD,WAAAA,eAEJ,sBASWE,UAAWC,MACJA,EAAEC,OAAOC,QAAQ,wBACpB,CACfF,EAAEG,uBACIC,WAAaJ,EAAEC,OAAOI,QAAQC,GAC9BC,aAAeP,EAAEC,OAAOI,QAAQE,aAChCC,WAAa,CACfC,SAAUT,EAAEC,OAAOI,QAAQI,SAC3BC,KAAMV,EAAEC,OAAOI,QAAQK,MAErBC,YAAcC,6BAAkBC,OAAO,CACzCC,OAAO,kBAAU,yBAA0B,WAC3CC,MAAM,kBAAU,gCAAiC,UAAWP,YAC5DQ,MAAM,EACNC,eAAe,IAInBN,MAAMO,UAAUC,GAAGC,sBAAYC,QAAQC,MAAAA,IACnCtB,EAAEG,uBACIoB,eAAiB,IAAIC,iBAAQ,2CAC7B/B,KAAKE,eAAeY,aAAcH,kBAElCqB,QAAQC,IAAI,EACd,gCAAoB3B,YACpB,wCAEJY,MAAMgB,UACNJ,eAAeK"}PK>\7  build/policy.min.jsnu[define("core_ai/policy",["exports","./repository"],(function(_exports,_repository){function _classStaticPrivateFieldSpecGet(receiver,classConstructor,descriptor){return function(receiver,classConstructor){if(receiver!==classConstructor)throw new TypeError("Private static access of wrong provenance")}(receiver,classConstructor),function(descriptor,action){if(void 0===descriptor)throw new TypeError("attempted to "+action+" private static field before its declaration")}(descriptor,"get"),function(receiver,descriptor){if(descriptor.get)return descriptor.get.call(receiver);return descriptor.value} /** * The Javascript module to handle the policy acceptance. * * @module core_ai/policy * @copyright 2024 Andrew Lyons * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */(receiver,descriptor)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;class _class{static preconfigurePolicyState(userid,state){_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor).hasOwnProperty(userid)||(_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid]=state)}static async getPolicyStatus(userid){if(_classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid])return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid];const accepted=await(0,_repository.getPolicyStatus)(userid);return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[userid]=accepted.status,accepted.status}static acceptPolicy(){return _classStaticPrivateFieldSpecGet(this,_class,_policyAcceptedFor)[M.cfg.userId]=!0,(0,_repository.setPolicyStatus)(M.cfg.contextid)}}_exports.default=_class;var _policyAcceptedFor={writable:!0,value:{}};return _exports.default})); //# sourceMappingURL=policy.min.js.mapPK>\o \ build/providerchooser.min.js.mapnu[{"version":3,"file":"providerchooser.min.js","sources":["../src/providerchooser.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/ //\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * AI provider selection handler.\n *\n * @module core_ai/providerchooser\n * @copyright 2024 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nconst Selectors = {\n fields: {\n selector: '[data-aiproviderchooser-field=\"selector\"]',\n updateButton: '[data-aiproviderchooser-field=\"updateButton\"]',\n },\n};\n\n/**\n * Initialise the AI provider chooser.\n */\nexport const init = () => {\n document.querySelector(Selectors.fields.selector)?.addEventListener('change', e => {\n const form = e.target.closest('form');\n const updateButton = form.querySelector(Selectors.fields.updateButton);\n const url = new URL(form.action);\n\n form.action = url.toString();\n updateButton.click();\n });\n};\n"],"names":["Selectors","selector","updateButton","document","querySelector","addEventListener","e","form","target","closest","url","URL","action","toString","click"],"mappings":";;;;;;;;MAsBMA,iBACM,CACJC,SAAU,4CACVC,aAAc,+DAOF,6DAChBC,SAASC,cAAcJ,iBAAiBC,kEAAWI,iBAAiB,UAAUC,UACpEC,KAAOD,EAAEE,OAAOC,QAAQ,QACxBP,aAAeK,KAAKH,cAAcJ,iBAAiBE,cACnDQ,IAAM,IAAIC,IAAIJ,KAAKK,QAEzBL,KAAKK,OAASF,IAAIG,WAClBX,aAAaY"}PK>\`vk  build/policy.min.js.mapnu[{"version":3,"file":"policy.min.js","sources":["../src/policy.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\nimport {getPolicyStatus, setPolicyStatus} from \"./repository\";\n\n/**\n * The Javascript module to handle the policy acceptance.\n *\n * @module core_ai/policy\n * @copyright 2024 Andrew Lyons \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n static #policyAcceptedFor = {};\n\n static preconfigurePolicyState(userid, state) {\n if (!this.#policyAcceptedFor.hasOwnProperty(userid)) {\n this.#policyAcceptedFor[userid] = state;\n }\n }\n\n /**\n * Get the policy status for a user.\n *\n * @param {Number} userid The user ID.\n * @return {Promise} The policy status.\n */\n static async getPolicyStatus(userid) {\n if (this.#policyAcceptedFor[userid]) {\n return this.#policyAcceptedFor[userid];\n }\n\n const accepted = await getPolicyStatus(userid);\n\n this.#policyAcceptedFor[userid] = accepted.status;\n\n return accepted.status;\n }\n\n static acceptPolicy() {\n this.#policyAcceptedFor[M.cfg.userId] = true;\n\n return setPolicyStatus(M.cfg.contextid);\n }\n}\n"],"names":["userid","state","_classStaticPrivateFieldSpecGet","hasOwnProperty","accepted","status","M","cfg","userId","contextid"],"mappings":";;;;;;;uJA2BmCA,OAAQC,OAC9BC,gEAAwBC,eAAeH,0EAChBA,QAAUC,oCAUbD,WACrBE,gEAAwBF,eACjBE,gEAAwBF,cAG7BI,eAAiB,+BAAgBJ,+EAEfA,QAAUI,SAASC,OAEpCD,SAASC,oGAIQC,EAAEC,IAAIC,SAAU,GAEjC,+BAAgBF,EAAEC,IAAIE,8EA7BL"}PKB\ZJr . . src/filter.jsnu[// 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 . /** * Question bank filter management. * * @module core_question/filter * @copyright 2021 Tomo Tsuyuki * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import CoreFilter from 'core/datafilter'; import Notification from 'core/notification'; import Selectors from 'core/datafilter/selectors'; import Templates from 'core/templates'; import Fragment from 'core/fragment'; import {getString} from 'core/str'; import {addIconToContainerRemoveOnCompletion} from 'core/loadingicon'; /** * Initialise the question bank filter on the element with the given id. * * @param {String} filterRegionId ID of the HTML element containing the filters. * @param {String} defaultcourseid Course ID for the default course to pass back to the view. * @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view. * @param {Number} perpage The number of questions to display per page. * @param {Number} bankContextId Context ID of the question bank being filtered. * @param {Number} quizCmId Course module ID of the quiz as the viewing context. * @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question) * @param {string} callback Name of the callback for the fragment API (e.g question_data) * @param {string} view The class name of the question bank view class used for this page. * @param {Number} cmid If we are in an activitiy, the course module ID. * @param {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype. * @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view. */ export const init = async( filterRegionId, defaultcourseid, defaultcategoryid, perpage, bankContextId, quizCmId, component, callback, view, cmid, pagevars, extraparams, ) => { const SELECTORS = { QUESTION_CONTAINER_ID: '#questionscontainer', QUESTION_TABLE: '#questionscontainer table', SORT_LINK: '#questionscontainer div.sorters a', PAGINATION_LINK: '#questionscontainer a[href].page-link', LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]', BULK_ACTIONS: '#bulkactionsui-container input', MENU_ACTIONS: '.menu-action', EDIT_SWITCH: '.editmode-switch-form input[name=setmode]', EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]', SHOW_ALL_LINK: '[data-filteraction="showall"]', }; const filterSet = document.querySelector(`#${filterRegionId}`); const viewData = { extraparams: JSON.stringify(extraparams), cmid, view, cat: defaultcategoryid, courseid: defaultcourseid, filter: {}, jointype: 0, qpage: 0, qperpage: perpage, sortdata: {}, lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null, }; let sortData = {}; const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort; if (defaultSort) { sortData = JSON.parse(defaultSort); } const [ showAllText, showPerPageText, ] = await Promise.all([ getString('showall', 'core', ''), getString('showperpage', 'core', extraparams.defaultqperpage), ]); /** * Retrieve table data. * * @param {Object} filterdata data * @param {Promise} pendingPromise pending promise */ const applyFilter = (filterdata, pendingPromise) => { // Reload the questions based on the specified filters. If no filters are provided, // use the default category filter condition. if (filterdata) { // Main join types. viewData.jointype = parseInt(filterSet.dataset.filterverb, 10); delete filterdata.jointype; // Retrieve filter info. viewData.filter = filterdata; if (Object.keys(filterdata).length !== 0) { if (!isNaN(viewData.jointype)) { filterdata.jointype = viewData.jointype; } } } // Load questions for first page. viewData.filter = JSON.stringify(filterdata); viewData.sortdata = JSON.stringify(sortData); viewData.quizcmid = quizCmId; const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID); // Clear the contents of the element, then append the loading icon. questionscontainer.innerHTML = ''; addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise); Fragment.loadFragment(component, callback, bankContextId, viewData) // Render questions for first page and pagination. .then((questionhtml, jsfooter) => { updateUrlParams(filterdata); if (questionhtml === undefined) { questionhtml = ''; } if (jsfooter === undefined) { jsfooter = ''; } Templates.replaceNode(questionscontainer, questionhtml, jsfooter); // Resolve filter promise. if (pendingPromise) { pendingPromise.resolve(); } return {questionhtml, jsfooter}; }) .catch(Notification.exception); }; // Init core filter processor with apply callback. const coreFilter = new CoreFilter(filterSet, applyFilter); coreFilter.activeFilters = {}; // Unset useless courseid filter. coreFilter.init(); /** * Update URL Param based upon the current filter. * * @param {Object} filters Active filters. */ const updateUrlParams = (filters) => { const url = new URL(location.href); const filterQuery = JSON.stringify(filters); url.searchParams.set('filter', filterQuery); history.pushState(filters, '', url); const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH); if (editSwitch) { const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL); const editSwitchUrl = new URL(editSwitchUrlInput.value); editSwitchUrl.searchParams.set('filter', filterQuery); editSwitchUrlInput.value = editSwitchUrl; editSwitch.dataset.pageurl = editSwitchUrl; } }; /** * Cleans URL parameters. */ const cleanUrlParams = () => { const queryString = location.search; const urlParams = new URLSearchParams(queryString); if (urlParams.has('cmid')) { const cleanedUrl = new URL(location.href.replace(location.search, '')); cleanedUrl.searchParams.set('cmid', urlParams.get('cmid')); history.pushState({}, '', cleanedUrl); } if (urlParams.has('courseid')) { const cleanedUrl = new URL(location.href.replace(location.search, '')); cleanedUrl.searchParams.set('courseid', urlParams.get('courseid')); history.pushState({}, '', cleanedUrl); } }; // Add listeners for the sorting, paging and clear actions. document.querySelector('.questionbankwindow').addEventListener('click', e => { const sortableLink = e.target.closest(SELECTORS.SORT_LINK); const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK); const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters); const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK); if (sortableLink) { e.preventDefault(); const oldSort = sortData; sortData = {}; sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder; for (const sortname in oldSort) { if (sortname !== sortableLink.dataset.sortname) { sortData[sortname] = oldSort[sortname]; } } viewData.qpage = 0; coreFilter.updateTableFromFilter(false); } if (paginationLink) { e.preventDefault(); const paginationURL = new URL(paginationLink.getAttribute("href")); const qpage = paginationURL.searchParams.get('qpage'); if (paginationURL.search !== null) { viewData.qpage = qpage; coreFilter.updateTableFromFilter(false); } } if (clearLink) { cleanUrlParams(); } if (showallLink) { e.preventDefault(); // Toggle between showing all and going back to the original qperpage. if (Number(showallLink.dataset.status) === 0) { viewData.qperpage = extraparams.maxqperpage; showallLink.dataset.status = 1; showallLink.innerText = showPerPageText; } else { viewData.qperpage = extraparams.defaultqperpage; showallLink.dataset.status = 0; showallLink.innerText = showAllText; } viewData.qpage = 0; coreFilter.updateTableFromFilter(); } }); // Run apply filter at page load. let initialFilters; let jointype = null; if (pagevars.filter) { // Load initial filter based on page vars. initialFilters = pagevars.filter; if (pagevars.jointype) { jointype = pagevars.jointype; } } if (Object.entries(initialFilters).length !== 0) { // Remove the default empty filter row. const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow); if (emptyFilterRow) { emptyFilterRow.remove(); } // Add filters. let rowcount = 0; for (const urlFilter in initialFilters) { if (urlFilter === 'jointype') { jointype = initialFilters[urlFilter]; continue; } // Add each filter row. rowcount += 1; const filterdata = { filtertype: urlFilter, values: initialFilters[urlFilter].values, jointype: initialFilters[urlFilter].jointype, filteroptions: initialFilters[urlFilter].filteroptions, rownum: rowcount }; coreFilter.addFilterRow(filterdata); } coreFilter.filterSet.dataset.filterverb = jointype; // Since we must filter by category, it does not make sense to allow the top-level "match any" or "match none" conditions, // as this would exclude the category. Remove those options and disable the select. const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join); join.querySelectorAll(`option:not([value="${jointype}"])`).forEach((option) => option.remove()); join.disabled = true; } }; PKB\x2 src/question_engine.jsnu[// 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 . /** * JavaScript required by the question engine. * * @module core_question/question_engine * @copyright 2021 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as scrollManager from 'core/scroll_manager'; import * as formSubmit from 'core_form/submit'; /** * Initialise a question submit button. This saves the scroll position and * sets the fragment on the form submit URL so the page reloads in the right place. * * @param {string} button the id of the button in the HTML. */ export const initSubmitButton = button => { formSubmit.init(button); scrollManager.watchScrollButtonSaves(); }; /** * Initialise a form that contains questions printed using print_question. * This has the effect of: * 1. Turning off browser autocomlete. * 2. Stopping enter from submitting the form (or toggling the next flag) unless * keyboard focus is on the submit button or the flag. * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle * the flags using ajax. * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there. * 5. Prevent the user from repeatedly submitting the form. * * @param {string} formSelector Selector to identify the form. */ export const initForm = (formSelector) => { const form = document.querySelector(formSelector); form.setAttribute('autocomplete', 'off'); form.addEventListener('submit', preventRepeatSubmission); form.addEventListener('key', (event) => { if (event.keyCode !== 13) { return; } if (event.target.matches('a')) { return; } if (event.target.matches('input[type="submit"]')) { return; } if (event.target.matches('input[type=img]')) { return; } if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) { return; } event.preventDefault(); }); const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton'); [...questionFlagSaveButtons].forEach((node) => node.remove()); // Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing. scrollManager.scrollToSavedPosition(); }; /** * Event handler to stop a question form being submitted more than once. * * @param {object} event the form submit event. */ export const preventRepeatSubmission = (event) => { const form = event.target.closest('form'); if (form.dataset.formSubmitted === '1') { event.preventDefault(); return; } setTimeout(() => { [...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true)); }); form.dataset.formSubmitted = '1'; }; PKB\V?  src/refresh_ui.jsnu[// 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 . /** * Question bank UI refresh utility * * @module core_question/refresh_ui * @copyright 2023 Catalyst IT Europe Ltd. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import Fragment from 'core/fragment'; import Templates from 'core/templates'; export default { /** * Reload the question bank UI, retaining the current filters and sort data. * * @param {Element} uiRoot The root element of the UI to be refreshed. Must contain "component", "callback" and "contextid" in * its data attributes, to be passed to the Fragment API. * @param {URL} returnUrl The url of the current page, containing filter and sort parameters. * @return {Promise} Resolved when the refresh is complete. */ refresh: (uiRoot, returnUrl) => { return new Promise((resolve, reject) => { const fragmentData = uiRoot.dataset; const viewData = {}; const sortData = {}; if (returnUrl) { returnUrl.searchParams.forEach((value, key) => { // Match keys like 'sortdata[fieldname]' and convert them to an array, // because the fragment API doesn't like non-alphanum argument keys. const sortItem = key.match(/sortdata\[([^\]]+)\]/); if (sortItem) { // The item returned by sortItem.pop() is the contents of the matching group, the field name. sortData[sortItem.pop()] = value; } else { viewData[key] = value; } }); } viewData.sortdata = JSON.stringify(sortData); // We have to use then() there, as loadFragment doesn't appear to work with await. Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData) .then((html, js) => { Templates.replaceNode(uiRoot, html, js); resolve(); return html; }) .catch(reject); }); } }; PKB\aWu src/question_banks_datasource.jsnu[// 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 . /** * Autocomplete data source for shared question banks. * * @module core_question/question_banks_datasource * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net} * @author Mark Johnson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import {call as fetchMany} from 'core/ajax'; import Notification from 'core/notification'; export default { transport: function(selector, query, callback) { const element = document.querySelector(selector); const contextId = element.dataset.contextid; let requiredcapabilities = ['use']; if (element.dataset.requiredcapabilities) { requiredcapabilities = JSON.parse(element.dataset.requiredcapabilities); } if (!contextId) { throw new Error('The attribute data-contextid is required on ' + selector); } fetchMany([{ methodname: 'core_question_search_shared_banks', args: { contextid: contextId, search: query, requiredcapabilities: requiredcapabilities, }, }])[0] .then(callback) .catch(Notification.exception); }, processResults: (selector, results) => { return results.sharedbanks; }, }; PKB\m3J J build/question_engine.min.jsnu[define("core_question/question_engine",["exports","core/scroll_manager","core_form/submit"],(function(_exports,scrollManager,formSubmit){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} /** * JavaScript required by the question engine. * * @module core_question/question_engine * @copyright 2021 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.preventRepeatSubmission=_exports.initSubmitButton=_exports.initForm=void 0,scrollManager=_interopRequireWildcard(scrollManager),formSubmit=_interopRequireWildcard(formSubmit);_exports.initSubmitButton=button=>{formSubmit.init(button),scrollManager.watchScrollButtonSaves()};_exports.initForm=formSelector=>{const form=document.querySelector(formSelector);form.setAttribute("autocomplete","off"),form.addEventListener("submit",preventRepeatSubmission),form.addEventListener("key",(event=>{13===event.keyCode&&(event.target.matches("a")||event.target.matches('input[type="submit"]')||event.target.matches("input[type=img]")||event.target.matches("textarea")||event.target.matches("[contenteditable=true]")||event.preventDefault())}));[...form.querySelectorAll(".questionflagsavebutton")].forEach((node=>node.remove())),scrollManager.scrollToSavedPosition()};const preventRepeatSubmission=event=>{const form=event.target.closest("form");"1"!==form.dataset.formSubmitted?(setTimeout((()=>{[...form.querySelectorAll("input[type=submit]")].forEach((input=>input.setAttribute("disabled",!0)))})),form.dataset.formSubmitted="1"):event.preventDefault()};_exports.preventRepeatSubmission=preventRepeatSubmission})); //# sourceMappingURL=question_engine.min.js.mapPKB\J+ + *build/question_banks_datasource.min.js.mapnu[{"version":3,"file":"question_banks_datasource.min.js","sources":["../src/question_banks_datasource.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Autocomplete data source for shared question banks.\n *\n * @module core_question/question_banks_datasource\n * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net}\n * @author Mark Johnson \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\nimport Notification from 'core/notification';\n\nexport default {\n\n transport: function(selector, query, callback) {\n const element = document.querySelector(selector);\n const contextId = element.dataset.contextid;\n let requiredcapabilities = ['use'];\n if (element.dataset.requiredcapabilities) {\n requiredcapabilities = JSON.parse(element.dataset.requiredcapabilities);\n }\n\n if (!contextId) {\n throw new Error('The attribute data-contextid is required on ' + selector);\n }\n\n fetchMany([{\n methodname: 'core_question_search_shared_banks',\n args: {\n contextid: contextId,\n search: query,\n requiredcapabilities: requiredcapabilities,\n },\n }])[0]\n .then(callback)\n .catch(Notification.exception);\n },\n\n processResults: (selector, results) => {\n return results.sharedbanks;\n },\n};\n"],"names":["transport","selector","query","callback","element","document","querySelector","contextId","dataset","contextid","requiredcapabilities","JSON","parse","Error","methodname","args","search","then","catch","Notification","exception","processResults","results","sharedbanks"],"mappings":";;;;;;;;sKA2Be,CAEXA,UAAW,SAASC,SAAUC,MAAOC,gBAC3BC,QAAUC,SAASC,cAAcL,UACjCM,UAAYH,QAAQI,QAAQC,cAC9BC,qBAAuB,CAAC,UACxBN,QAAQI,QAAQE,uBAChBA,qBAAuBC,KAAKC,MAAMR,QAAQI,QAAQE,wBAGjDH,gBACK,IAAIM,MAAM,+CAAiDZ,yBAG3D,CAAC,CACPa,WAAY,oCACZC,KAAM,CACFN,UAAWF,UACXS,OAAQd,MACRQ,qBAAsBA,yBAE1B,GACHO,KAAKd,UACLe,MAAMC,sBAAaC,YAGxBC,eAAgB,CAACpB,SAAUqB,UAChBA,QAAQC"}PKB\build/refresh_ui.min.jsnu[define("core_question/refresh_ui",["exports","core/fragment","core/templates"],(function(_exports,_fragment,_templates){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Question bank UI refresh utility * * @module core_question/refresh_ui * @copyright 2023 Catalyst IT Europe Ltd. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_fragment=_interopRequireDefault(_fragment),_templates=_interopRequireDefault(_templates);var _default={refresh:(uiRoot,returnUrl)=>new Promise(((resolve,reject)=>{const fragmentData=uiRoot.dataset,viewData={},sortData={};returnUrl&&returnUrl.searchParams.forEach(((value,key)=>{const sortItem=key.match(/sortdata\[([^\]]+)\]/);sortItem?sortData[sortItem.pop()]=value:viewData[key]=value})),viewData.sortdata=JSON.stringify(sortData),_fragment.default.loadFragment(fragmentData.component,fragmentData.callback,fragmentData.contextid,viewData).then(((html,js)=>(_templates.default.replaceNode(uiRoot,html,js),resolve(),html))).catch(reject)}))};return _exports.default=_default,_exports.default})); //# sourceMappingURL=refresh_ui.min.js.mapPKB\~abuild/refresh_ui.min.js.mapnu[{"version":3,"file":"refresh_ui.min.js","sources":["../src/refresh_ui.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Question bank UI refresh utility\n *\n * @module core_question/refresh_ui\n * @copyright 2023 Catalyst IT Europe Ltd.\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Fragment from 'core/fragment';\nimport Templates from 'core/templates';\n\nexport default {\n /**\n * Reload the question bank UI, retaining the current filters and sort data.\n *\n * @param {Element} uiRoot The root element of the UI to be refreshed. Must contain \"component\", \"callback\" and \"contextid\" in\n * its data attributes, to be passed to the Fragment API.\n * @param {URL} returnUrl The url of the current page, containing filter and sort parameters.\n * @return {Promise} Resolved when the refresh is complete.\n */\n refresh: (uiRoot, returnUrl) => {\n return new Promise((resolve, reject) => {\n const fragmentData = uiRoot.dataset;\n const viewData = {};\n const sortData = {};\n if (returnUrl) {\n returnUrl.searchParams.forEach((value, key) => {\n // Match keys like 'sortdata[fieldname]' and convert them to an array,\n // because the fragment API doesn't like non-alphanum argument keys.\n const sortItem = key.match(/sortdata\\[([^\\]]+)\\]/);\n if (sortItem) {\n // The item returned by sortItem.pop() is the contents of the matching group, the field name.\n sortData[sortItem.pop()] = value;\n } else {\n viewData[key] = value;\n }\n });\n }\n viewData.sortdata = JSON.stringify(sortData);\n // We have to use then() there, as loadFragment doesn't appear to work with await.\n Fragment.loadFragment(fragmentData.component, fragmentData.callback, fragmentData.contextid, viewData)\n .then((html, js) => {\n Templates.replaceNode(uiRoot, html, js);\n resolve();\n return html;\n })\n .catch(reject);\n });\n }\n};\n"],"names":["refresh","uiRoot","returnUrl","Promise","resolve","reject","fragmentData","dataset","viewData","sortData","searchParams","forEach","value","key","sortItem","match","pop","sortdata","JSON","stringify","loadFragment","component","callback","contextid","then","html","js","replaceNode","catch"],"mappings":";;;;;;;4LA0Be,CASXA,QAAS,CAACC,OAAQC,YACP,IAAIC,SAAQ,CAACC,QAASC,gBACnBC,aAAeL,OAAOM,QACtBC,SAAW,GACXC,SAAW,GACbP,WACAA,UAAUQ,aAAaC,SAAQ,CAACC,MAAOC,aAG7BC,SAAWD,IAAIE,MAAM,wBACvBD,SAEAL,SAASK,SAASE,OAASJ,MAE3BJ,SAASK,KAAOD,SAI5BJ,SAASS,SAAWC,KAAKC,UAAUV,4BAE1BW,aAAad,aAAae,UAAWf,aAAagB,SAAUhB,aAAaiB,UAAWf,UACxFgB,MAAK,CAACC,KAAMC,yBACCC,YAAY1B,OAAQwB,KAAMC,IACpCtB,UACOqB,QAEVG,MAAMvB"}PKB\" build/filter.min.jsnu[define("core_question/filter",["exports","core/datafilter","core/notification","core/datafilter/selectors","core/templates","core/fragment","core/str","core/loadingicon"],(function(_exports,_datafilter,_notification,_selectors,_templates,_fragment,_str,_loadingicon){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} /** * Question bank filter management. * * @module core_question/filter * @copyright 2021 Tomo Tsuyuki * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_datafilter=_interopRequireDefault(_datafilter),_notification=_interopRequireDefault(_notification),_selectors=_interopRequireDefault(_selectors),_templates=_interopRequireDefault(_templates),_fragment=_interopRequireDefault(_fragment);_exports.init=async(filterRegionId,defaultcourseid,defaultcategoryid,perpage,bankContextId,quizCmId,component,callback,view,cmid,pagevars,extraparams)=>{var _document$querySelect,_document$querySelect2,_document$querySelect3,_document$querySelect4;const SELECTORS_QUESTION_CONTAINER_ID="#questionscontainer",SELECTORS_QUESTION_TABLE="#questionscontainer table",SELECTORS_SORT_LINK="#questionscontainer div.sorters a",SELECTORS_PAGINATION_LINK="#questionscontainer a[href].page-link",SELECTORS_LASTCHANGED_FIELD="#questionsubmit input[name=lastchanged]",SELECTORS_EDIT_SWITCH=".editmode-switch-form input[name=setmode]",SELECTORS_EDIT_SWITCH_URL=".editmode-switch-form input[name=pageurl]",SELECTORS_SHOW_ALL_LINK='[data-filteraction="showall"]',filterSet=document.querySelector("#".concat(filterRegionId)),viewData={extraparams:JSON.stringify(extraparams),cmid:cmid,view:view,cat:defaultcategoryid,courseid:defaultcourseid,filter:{},jointype:0,qpage:0,qperpage:perpage,sortdata:{},lastchanged:null!==(_document$querySelect=null===(_document$querySelect2=document.querySelector(SELECTORS_LASTCHANGED_FIELD))||void 0===_document$querySelect2?void 0:_document$querySelect2.value)&&void 0!==_document$querySelect?_document$querySelect:null};let sortData={};const defaultSort=null===(_document$querySelect3=document.querySelector(SELECTORS_QUESTION_TABLE))||void 0===_document$querySelect3||null===(_document$querySelect4=_document$querySelect3.dataset)||void 0===_document$querySelect4?void 0:_document$querySelect4.defaultsort;defaultSort&&(sortData=JSON.parse(defaultSort));const[showAllText,showPerPageText]=await Promise.all([(0,_str.getString)("showall","core",""),(0,_str.getString)("showperpage","core",extraparams.defaultqperpage)]),coreFilter=new _datafilter.default(filterSet,((filterdata,pendingPromise)=>{filterdata&&(viewData.jointype=parseInt(filterSet.dataset.filterverb,10),delete filterdata.jointype,viewData.filter=filterdata,0!==Object.keys(filterdata).length&&(isNaN(viewData.jointype)||(filterdata.jointype=viewData.jointype))),viewData.filter=JSON.stringify(filterdata),viewData.sortdata=JSON.stringify(sortData),viewData.quizcmid=quizCmId;const questionscontainer=document.querySelector(SELECTORS_QUESTION_CONTAINER_ID);questionscontainer.innerHTML="",(0,_loadingicon.addIconToContainerRemoveOnCompletion)(questionscontainer,pendingPromise),_fragment.default.loadFragment(component,callback,bankContextId,viewData).then(((questionhtml,jsfooter)=>(updateUrlParams(filterdata),void 0===questionhtml&&(questionhtml=""),void 0===jsfooter&&(jsfooter=""),_templates.default.replaceNode(questionscontainer,questionhtml,jsfooter),pendingPromise&&pendingPromise.resolve(),{questionhtml:questionhtml,jsfooter:jsfooter}))).catch(_notification.default.exception)}));coreFilter.activeFilters={},coreFilter.init();const updateUrlParams=filters=>{const url=new URL(location.href),filterQuery=JSON.stringify(filters);url.searchParams.set("filter",filterQuery),history.pushState(filters,"",url);const editSwitch=document.querySelector(SELECTORS_EDIT_SWITCH);if(editSwitch){const editSwitchUrlInput=document.querySelector(SELECTORS_EDIT_SWITCH_URL),editSwitchUrl=new URL(editSwitchUrlInput.value);editSwitchUrl.searchParams.set("filter",filterQuery),editSwitchUrlInput.value=editSwitchUrl,editSwitch.dataset.pageurl=editSwitchUrl}};let initialFilters;document.querySelector(".questionbankwindow").addEventListener("click",(e=>{const sortableLink=e.target.closest(SELECTORS_SORT_LINK),paginationLink=e.target.closest(SELECTORS_PAGINATION_LINK),clearLink=e.target.closest(_selectors.default.filterset.actions.resetFilters),showallLink=e.target.closest(SELECTORS_SHOW_ALL_LINK);if(sortableLink){e.preventDefault();const oldSort=sortData;sortData={},sortData[sortableLink.dataset.sortname]=sortableLink.dataset.sortorder;for(const sortname in oldSort)sortname!==sortableLink.dataset.sortname&&(sortData[sortname]=oldSort[sortname]);viewData.qpage=0,coreFilter.updateTableFromFilter(!1)}if(paginationLink){e.preventDefault();const paginationURL=new URL(paginationLink.getAttribute("href")),qpage=paginationURL.searchParams.get("qpage");null!==paginationURL.search&&(viewData.qpage=qpage,coreFilter.updateTableFromFilter(!1))}clearLink&&(()=>{const queryString=location.search,urlParams=new URLSearchParams(queryString);if(urlParams.has("cmid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("cmid",urlParams.get("cmid")),history.pushState({},"",cleanedUrl)}if(urlParams.has("courseid")){const cleanedUrl=new URL(location.href.replace(location.search,""));cleanedUrl.searchParams.set("courseid",urlParams.get("courseid")),history.pushState({},"",cleanedUrl)}})(),showallLink&&(e.preventDefault(),0===Number(showallLink.dataset.status)?(viewData.qperpage=extraparams.maxqperpage,showallLink.dataset.status=1,showallLink.innerText=showPerPageText):(viewData.qperpage=extraparams.defaultqperpage,showallLink.dataset.status=0,showallLink.innerText=showAllText),viewData.qpage=0,coreFilter.updateTableFromFilter())}));let jointype=null;if(pagevars.filter&&(initialFilters=pagevars.filter,pagevars.jointype&&(jointype=pagevars.jointype)),0!==Object.entries(initialFilters).length){const emptyFilterRow=filterSet.querySelector(_selectors.default.filterset.regions.emptyFilterRow);emptyFilterRow&&emptyFilterRow.remove();let rowcount=0;for(const urlFilter in initialFilters){if("jointype"===urlFilter){jointype=initialFilters[urlFilter];continue}rowcount+=1;const filterdata={filtertype:urlFilter,values:initialFilters[urlFilter].values,jointype:initialFilters[urlFilter].jointype,filteroptions:initialFilters[urlFilter].filteroptions,rownum:rowcount};coreFilter.addFilterRow(filterdata)}coreFilter.filterSet.dataset.filterverb=jointype;const join=coreFilter.filterSet.querySelector(_selectors.default.filterset.fields.join);join.querySelectorAll('option:not([value="'.concat(jointype,'"])')).forEach((option=>option.remove())),join.disabled=!0}}})); //# sourceMappingURL=filter.min.js.mapPKB\I BBbuild/filter.min.js.mapnu[{"version":3,"file":"filter.min.js","sources":["../src/filter.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Question bank filter management.\n *\n * @module core_question/filter\n * @copyright 2021 Tomo Tsuyuki \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport CoreFilter from 'core/datafilter';\nimport Notification from 'core/notification';\nimport Selectors from 'core/datafilter/selectors';\nimport Templates from 'core/templates';\nimport Fragment from 'core/fragment';\nimport {getString} from 'core/str';\nimport {addIconToContainerRemoveOnCompletion} from 'core/loadingicon';\n\n/**\n * Initialise the question bank filter on the element with the given id.\n *\n * @param {String} filterRegionId ID of the HTML element containing the filters.\n * @param {String} defaultcourseid Course ID for the default course to pass back to the view.\n * @param {String} defaultcategoryid Question bank category ID for the default course to pass back to the view.\n * @param {Number} perpage The number of questions to display per page.\n * @param {Number} bankContextId Context ID of the question bank being filtered.\n * @param {Number} quizCmId Course module ID of the quiz as the viewing context.\n * @param {string} component Frankenstyle name of the component for the fragment API callback (e.g. core_question)\n * @param {string} callback Name of the callback for the fragment API (e.g question_data)\n * @param {string} view The class name of the question bank view class used for this page.\n * @param {Number} cmid If we are in an activitiy, the course module ID.\n * @param {Object} pagevars JSON-encoded parameters from passed from the view, including filters and jointype.\n * @param {Object} extraparams JSON-encoded additional parameters specific to this view class, used for re-rendering the view.\n */\nexport const init = async(\n filterRegionId,\n defaultcourseid,\n defaultcategoryid,\n perpage,\n bankContextId,\n quizCmId,\n component,\n callback,\n view,\n cmid,\n pagevars,\n extraparams,\n) => {\n\n const SELECTORS = {\n QUESTION_CONTAINER_ID: '#questionscontainer',\n QUESTION_TABLE: '#questionscontainer table',\n SORT_LINK: '#questionscontainer div.sorters a',\n PAGINATION_LINK: '#questionscontainer a[href].page-link',\n LASTCHANGED_FIELD: '#questionsubmit input[name=lastchanged]',\n BULK_ACTIONS: '#bulkactionsui-container input',\n MENU_ACTIONS: '.menu-action',\n EDIT_SWITCH: '.editmode-switch-form input[name=setmode]',\n EDIT_SWITCH_URL: '.editmode-switch-form input[name=pageurl]',\n SHOW_ALL_LINK: '[data-filteraction=\"showall\"]',\n };\n\n const filterSet = document.querySelector(`#${filterRegionId}`);\n\n const viewData = {\n extraparams: JSON.stringify(extraparams),\n cmid,\n view,\n cat: defaultcategoryid,\n courseid: defaultcourseid,\n filter: {},\n jointype: 0,\n qpage: 0,\n qperpage: perpage,\n sortdata: {},\n lastchanged: document.querySelector(SELECTORS.LASTCHANGED_FIELD)?.value ?? null,\n };\n\n let sortData = {};\n const defaultSort = document.querySelector(SELECTORS.QUESTION_TABLE)?.dataset?.defaultsort;\n if (defaultSort) {\n sortData = JSON.parse(defaultSort);\n }\n\n const [\n showAllText,\n showPerPageText,\n ] = await Promise.all([\n getString('showall', 'core', ''),\n getString('showperpage', 'core', extraparams.defaultqperpage),\n ]);\n\n /**\n * Retrieve table data.\n *\n * @param {Object} filterdata data\n * @param {Promise} pendingPromise pending promise\n */\n const applyFilter = (filterdata, pendingPromise) => {\n // Reload the questions based on the specified filters. If no filters are provided,\n // use the default category filter condition.\n if (filterdata) {\n // Main join types.\n viewData.jointype = parseInt(filterSet.dataset.filterverb, 10);\n delete filterdata.jointype;\n // Retrieve filter info.\n viewData.filter = filterdata;\n if (Object.keys(filterdata).length !== 0) {\n if (!isNaN(viewData.jointype)) {\n filterdata.jointype = viewData.jointype;\n }\n }\n }\n // Load questions for first page.\n viewData.filter = JSON.stringify(filterdata);\n viewData.sortdata = JSON.stringify(sortData);\n viewData.quizcmid = quizCmId;\n\n const questionscontainer = document.querySelector(SELECTORS.QUESTION_CONTAINER_ID);\n // Clear the contents of the element, then append the loading icon.\n questionscontainer.innerHTML = '';\n addIconToContainerRemoveOnCompletion(questionscontainer, pendingPromise);\n\n Fragment.loadFragment(component, callback, bankContextId, viewData)\n // Render questions for first page and pagination.\n .then((questionhtml, jsfooter) => {\n updateUrlParams(filterdata);\n if (questionhtml === undefined) {\n questionhtml = '';\n }\n if (jsfooter === undefined) {\n jsfooter = '';\n }\n Templates.replaceNode(questionscontainer, questionhtml, jsfooter);\n // Resolve filter promise.\n if (pendingPromise) {\n pendingPromise.resolve();\n }\n return {questionhtml, jsfooter};\n })\n .catch(Notification.exception);\n };\n\n // Init core filter processor with apply callback.\n const coreFilter = new CoreFilter(filterSet, applyFilter);\n coreFilter.activeFilters = {}; // Unset useless courseid filter.\n coreFilter.init();\n\n /**\n * Update URL Param based upon the current filter.\n *\n * @param {Object} filters Active filters.\n */\n const updateUrlParams = (filters) => {\n const url = new URL(location.href);\n const filterQuery = JSON.stringify(filters);\n url.searchParams.set('filter', filterQuery);\n history.pushState(filters, '', url);\n const editSwitch = document.querySelector(SELECTORS.EDIT_SWITCH);\n if (editSwitch) {\n const editSwitchUrlInput = document.querySelector(SELECTORS.EDIT_SWITCH_URL);\n const editSwitchUrl = new URL(editSwitchUrlInput.value);\n editSwitchUrl.searchParams.set('filter', filterQuery);\n editSwitchUrlInput.value = editSwitchUrl;\n editSwitch.dataset.pageurl = editSwitchUrl;\n }\n };\n\n /**\n * Cleans URL parameters.\n */\n const cleanUrlParams = () => {\n const queryString = location.search;\n const urlParams = new URLSearchParams(queryString);\n if (urlParams.has('cmid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('cmid', urlParams.get('cmid'));\n history.pushState({}, '', cleanedUrl);\n }\n\n if (urlParams.has('courseid')) {\n const cleanedUrl = new URL(location.href.replace(location.search, ''));\n cleanedUrl.searchParams.set('courseid', urlParams.get('courseid'));\n history.pushState({}, '', cleanedUrl);\n }\n };\n\n // Add listeners for the sorting, paging and clear actions.\n document.querySelector('.questionbankwindow').addEventListener('click', e => {\n const sortableLink = e.target.closest(SELECTORS.SORT_LINK);\n const paginationLink = e.target.closest(SELECTORS.PAGINATION_LINK);\n const clearLink = e.target.closest(Selectors.filterset.actions.resetFilters);\n const showallLink = e.target.closest(SELECTORS.SHOW_ALL_LINK);\n if (sortableLink) {\n e.preventDefault();\n const oldSort = sortData;\n sortData = {};\n sortData[sortableLink.dataset.sortname] = sortableLink.dataset.sortorder;\n for (const sortname in oldSort) {\n if (sortname !== sortableLink.dataset.sortname) {\n sortData[sortname] = oldSort[sortname];\n }\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter(false);\n }\n if (paginationLink) {\n e.preventDefault();\n const paginationURL = new URL(paginationLink.getAttribute(\"href\"));\n const qpage = paginationURL.searchParams.get('qpage');\n if (paginationURL.search !== null) {\n viewData.qpage = qpage;\n coreFilter.updateTableFromFilter(false);\n }\n }\n if (clearLink) {\n cleanUrlParams();\n }\n if (showallLink) {\n\n e.preventDefault();\n\n // Toggle between showing all and going back to the original qperpage.\n if (Number(showallLink.dataset.status) === 0) {\n viewData.qperpage = extraparams.maxqperpage;\n showallLink.dataset.status = 1;\n showallLink.innerText = showPerPageText;\n } else {\n viewData.qperpage = extraparams.defaultqperpage;\n showallLink.dataset.status = 0;\n showallLink.innerText = showAllText;\n }\n viewData.qpage = 0;\n coreFilter.updateTableFromFilter();\n }\n });\n\n // Run apply filter at page load.\n let initialFilters;\n let jointype = null;\n if (pagevars.filter) {\n // Load initial filter based on page vars.\n initialFilters = pagevars.filter;\n if (pagevars.jointype) {\n jointype = pagevars.jointype;\n }\n }\n\n if (Object.entries(initialFilters).length !== 0) {\n // Remove the default empty filter row.\n const emptyFilterRow = filterSet.querySelector(Selectors.filterset.regions.emptyFilterRow);\n if (emptyFilterRow) {\n emptyFilterRow.remove();\n }\n\n // Add filters.\n let rowcount = 0;\n for (const urlFilter in initialFilters) {\n if (urlFilter === 'jointype') {\n jointype = initialFilters[urlFilter];\n continue;\n }\n // Add each filter row.\n rowcount += 1;\n const filterdata = {\n filtertype: urlFilter,\n values: initialFilters[urlFilter].values,\n jointype: initialFilters[urlFilter].jointype,\n filteroptions: initialFilters[urlFilter].filteroptions,\n rownum: rowcount\n };\n coreFilter.addFilterRow(filterdata);\n }\n coreFilter.filterSet.dataset.filterverb = jointype;\n\n // Since we must filter by category, it does not make sense to allow the top-level \"match any\" or \"match none\" conditions,\n // as this would exclude the category. Remove those options and disable the select.\n const join = coreFilter.filterSet.querySelector(Selectors.filterset.fields.join);\n join.querySelectorAll(`option:not([value=\"${jointype}\"])`).forEach((option) => option.remove());\n join.disabled = true;\n }\n};\n"],"names":["async","filterRegionId","defaultcourseid","defaultcategoryid","perpage","bankContextId","quizCmId","component","callback","view","cmid","pagevars","extraparams","SELECTORS","filterSet","document","querySelector","viewData","JSON","stringify","cat","courseid","filter","jointype","qpage","qperpage","sortdata","lastchanged","_document$querySelect2","value","sortData","defaultSort","_document$querySelect3","dataset","_document$querySelect4","defaultsort","parse","showAllText","showPerPageText","Promise","all","defaultqperpage","coreFilter","CoreFilter","filterdata","pendingPromise","parseInt","filterverb","Object","keys","length","isNaN","quizcmid","questionscontainer","innerHTML","loadFragment","then","questionhtml","jsfooter","updateUrlParams","undefined","replaceNode","resolve","catch","Notification","exception","activeFilters","init","filters","url","URL","location","href","filterQuery","searchParams","set","history","pushState","editSwitch","editSwitchUrlInput","editSwitchUrl","pageurl","initialFilters","addEventListener","e","sortableLink","target","closest","paginationLink","clearLink","Selectors","filterset","actions","resetFilters","showallLink","preventDefault","oldSort","sortname","sortorder","updateTableFromFilter","paginationURL","getAttribute","get","search","queryString","urlParams","URLSearchParams","has","cleanedUrl","replace","cleanUrlParams","Number","status","maxqperpage","innerText","entries","emptyFilterRow","regions","remove","rowcount","urlFilter","filtertype","values","filteroptions","rownum","addFilterRow","join","fields","querySelectorAll","forEach","option","disabled"],"mappings":"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"}PKB\梏?? build/question_engine.min.js.mapnu[{"version":3,"file":"question_engine.min.js","sources":["../src/question_engine.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * JavaScript required by the question engine.\n *\n * @module core_question/question_engine\n * @copyright 2021 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as scrollManager from 'core/scroll_manager';\nimport * as formSubmit from 'core_form/submit';\n\n/**\n * Initialise a question submit button. This saves the scroll position and\n * sets the fragment on the form submit URL so the page reloads in the right place.\n *\n * @param {string} button the id of the button in the HTML.\n */\nexport const initSubmitButton = button => {\n formSubmit.init(button);\n scrollManager.watchScrollButtonSaves();\n};\n\n/**\n * Initialise a form that contains questions printed using print_question.\n * This has the effect of:\n * 1. Turning off browser autocomlete.\n * 2. Stopping enter from submitting the form (or toggling the next flag) unless\n * keyboard focus is on the submit button or the flag.\n * 3. Removes any '.questionflagsavebutton's, since we have JavaScript to toggle\n * the flags using ajax.\n * 4. Scroll to the position indicated by scrollpos= in the URL, if it is there.\n * 5. Prevent the user from repeatedly submitting the form.\n *\n * @param {string} formSelector Selector to identify the form.\n */\nexport const initForm = (formSelector) => {\n const form = document.querySelector(formSelector);\n form.setAttribute('autocomplete', 'off');\n\n form.addEventListener('submit', preventRepeatSubmission);\n\n form.addEventListener('key', (event) => {\n if (event.keyCode !== 13) {\n return;\n }\n\n if (event.target.matches('a')) {\n return;\n }\n\n if (event.target.matches('input[type=\"submit\"]')) {\n return;\n }\n\n if (event.target.matches('input[type=img]')) {\n return;\n }\n\n if (event.target.matches('textarea') || event.target.matches('[contenteditable=true]')) {\n return;\n }\n\n event.preventDefault();\n });\n\n const questionFlagSaveButtons = form.querySelectorAll('.questionflagsavebutton');\n [...questionFlagSaveButtons].forEach((node) => node.remove());\n\n // Note: The scrollToSavedPosition function tries to wait until the content has loaded before firing.\n scrollManager.scrollToSavedPosition();\n};\n\n/**\n * Event handler to stop a question form being submitted more than once.\n *\n * @param {object} event the form submit event.\n */\nexport const preventRepeatSubmission = (event) => {\n const form = event.target.closest('form');\n if (form.dataset.formSubmitted === '1') {\n event.preventDefault();\n return;\n }\n\n setTimeout(() => {\n [...form.querySelectorAll('input[type=submit]')].forEach((input) => input.setAttribute('disabled', true));\n });\n form.dataset.formSubmitted = '1';\n};\n"],"names":["button","formSubmit","init","scrollManager","watchScrollButtonSaves","formSelector","form","document","querySelector","setAttribute","addEventListener","preventRepeatSubmission","event","keyCode","target","matches","preventDefault","querySelectorAll","forEach","node","remove","scrollToSavedPosition","closest","dataset","formSubmitted","setTimeout","input"],"mappings":";;;;;;;+QAgCgCA,SAC5BC,WAAWC,KAAKF,QAChBG,cAAcC,4CAgBOC,qBACfC,KAAOC,SAASC,cAAcH,cACpCC,KAAKG,aAAa,eAAgB,OAElCH,KAAKI,iBAAiB,SAAUC,yBAEhCL,KAAKI,iBAAiB,OAAQE,QACJ,KAAlBA,MAAMC,UAIND,MAAME,OAAOC,QAAQ,MAIrBH,MAAME,OAAOC,QAAQ,yBAIrBH,MAAME,OAAOC,QAAQ,oBAIrBH,MAAME,OAAOC,QAAQ,aAAeH,MAAME,OAAOC,QAAQ,2BAI7DH,MAAMI,yBAGsBV,KAAKW,iBAAiB,4BACzBC,SAASC,MAASA,KAAKC,WAGpDjB,cAAckB,+BAQLV,wBAA2BC,cAC9BN,KAAOM,MAAME,OAAOQ,QAAQ,QACC,MAA/BhB,KAAKiB,QAAQC,eAKjBC,YAAW,SACHnB,KAAKW,iBAAiB,uBAAuBC,SAASQ,OAAUA,MAAMjB,aAAa,YAAY,QAEvGH,KAAKiB,QAAQC,cAAgB,KAPzBZ,MAAMI"}PKB\΍q]]&build/question_banks_datasource.min.jsnu[define("core_question/question_banks_datasource",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){var obj; /** * Autocomplete data source for shared question banks. * * @module core_question/question_banks_datasource * @copyright 2025 onwards Catalyst IT EU {@link https://catalyst-eu.net} * @author Mark Johnson * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};var _default={transport:function(selector,query,callback){const element=document.querySelector(selector),contextId=element.dataset.contextid;let requiredcapabilities=["use"];if(element.dataset.requiredcapabilities&&(requiredcapabilities=JSON.parse(element.dataset.requiredcapabilities)),!contextId)throw new Error("The attribute data-contextid is required on "+selector);(0,_ajax.call)([{methodname:"core_question_search_shared_banks",args:{contextid:contextId,search:query,requiredcapabilities:requiredcapabilities}}])[0].then(callback).catch(_notification.default.exception)},processResults:(selector,results)=>results.sharedbanks};return _exports.default=_default,_exports.default})); //# sourceMappingURL=question_banks_datasource.min.js.mapPKB\Rnn0src/preferences_notifications_list_controller.jsnu[// 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 . /** * 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 * @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; }); PKB\~src/message_repository.jsnu[// 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 . /** * Retrieves messages from the server. * * @module core_message/message_repository * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/ajax', 'core/notification', 'core_message/message_drawer_view_conversation_constants' ], function( $, Ajax, Notification, Constants) { var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES; /** * Count the number of unread conversations (one or more messages from a user) * for a given user. * * @param {object} args The request arguments: * @return {object} jQuery promise */ var countUnreadConversations = function(args) { var request = { methodname: 'core_message_get_unread_conversations_count', args: args }; var promise = Ajax.call([request])[0]; promise.fail(Notification.exception); return promise; }; /** * Get contacts for given user. * * @param {int} userId The user id * @param {int} limit Limit for results * @param {int} offset Offset for results * @return {object} jQuery promise */ var getContacts = function(userId, limit, offset) { var args = { userid: userId }; if (typeof limit !== 'undefined') { args.limitnum = limit; } if (typeof offset !== 'undefined') { args.limitfrom = offset; } var request = { methodname: 'core_message_get_user_contacts', args: args }; return Ajax.call([request])[0]; }; /** * Block a user. * * @param {int} userId The requesting user * @param {int} blockedUserId Id of user to block * @return {object} jQuery promise */ var blockUser = function(userId, blockedUserId) { var requests = [ { methodname: 'core_message_block_user', args: { userid: userId, blockeduserid: blockedUserId } }, { methodname: 'core_message_get_member_info', args: { referenceuserid: userId, userids: [blockedUserId], includecontactrequests: true, includeprivacyinfo: true } } ]; // Wrap both requests in a single promise so that we can catch an error // from either request. return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) { // Only return the profile. return profiles.length ? profiles[0] : {}; }); }; /** * Unblock a user. * * @param {int} userId The requesting user * @param {int} unblockedUserId Id of user to unblock * @return {object} jQuery promise */ var unblockUser = function(userId, unblockedUserId) { var requests = [ { methodname: 'core_message_unblock_user', args: { userid: userId, unblockeduserid: unblockedUserId } }, { methodname: 'core_message_get_member_info', args: { referenceuserid: userId, userids: [unblockedUserId], includecontactrequests: true, includeprivacyinfo: true } } ]; // Wrap both requests in a single promise so that we can catch an error // from either request. return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) { // Only return the profile. return profiles.length ? profiles[0] : {}; }); }; /** * Create a request to add a user as a contact. * * @param {int} userId The requesting user * @param {int[]} requestUserIds List of user ids to add * @return {object} jQuery promise */ var createContactRequest = function(userId, requestUserIds) { var request = { methodname: 'core_message_create_contact_request', args: { userid: userId, requesteduserid: requestUserIds } }; return Ajax.call([request])[0]; }; /** * Remove a list of users as contacts. * * @param {int} userId The requesting user * @param {int[]} contactUserIds List of user ids to add * @return {object} jQuery promise */ var deleteContacts = function(userId, contactUserIds) { var requests = [ { methodname: 'core_message_delete_contacts', args: { userid: userId, userids: contactUserIds } }, { methodname: 'core_message_get_member_info', args: { referenceuserid: userId, userids: contactUserIds, includecontactrequests: true, includeprivacyinfo: true } } ]; return $.when.apply(null, Ajax.call(requests)).then(function(response1, profiles) { // Return all of the profiles as an array. return profiles; }); }; /** * Get messages between two users. * * @param {int} currentUserId The requesting user * @param {int} conversationId Other user in the conversation * @param {int} limit Limit for results * @param {int} offset Offset for results * @param {bool} newestFirst Order results by newest first * @param {int} timeFrom Only return messages after this timestamp * @return {object} jQuery promise */ var getMessages = function(currentUserId, conversationId, limit, offset, newestFirst, timeFrom) { var args = { currentuserid: currentUserId, convid: conversationId, newest: newestFirst ? true : false }; if (typeof limit !== 'undefined') { args.limitnum = limit; } if (typeof offset !== 'undefined') { args.limitfrom = offset; } if (typeof timeFrom !== 'undefined') { args.timefrom = timeFrom; } var request = { methodname: 'core_message_get_conversation_messages', args: args }; return Ajax.call([request])[0]; }; /** * Search for users. * * @param {int} userId The requesting user * @param {string} searchString Search string * @param {int} limit Limit for results * @param {int} offset Offset for results * @return {object} jQuery promise */ var searchUsers = function(userId, searchString, limit, offset) { var args = { userid: userId, search: searchString }; if (typeof limit !== 'undefined') { args.limitnum = limit; } if (typeof offset !== 'undefined') { args.limitfrom = offset; } var request = { methodname: 'core_message_message_search_users', args: args }; return Ajax.call([request])[0]; }; /** * Search for messages. * * @param {int} userId The requesting user * @param {string} searchString Search string * @param {int} limit Limit for results * @param {int} offset Offset for results * @return {object} jQuery promise */ var searchMessages = function(userId, searchString, limit, offset) { var args = { userid: userId, search: searchString }; if (typeof limit !== 'undefined') { args.limitnum = limit; } if (typeof offset !== 'undefined') { args.limitfrom = offset; } var request = { methodname: 'core_message_data_for_messagearea_search_messages', args: args }; return Ajax.call([request])[0]; }; /** * Send a list of messages to a user. * * @param {int} toUserId The recipient user id * @param {string[]} messages List of messages to send * @return {object} jQuery promise */ var sendMessagesToUser = function(toUserId, messages) { var formattedMessages = messages.map(function(message) { return { touserid: toUserId, text: message }; }); var request = { methodname: 'core_message_send_instant_messages', args: { messages: formattedMessages } }; return Ajax.call([request])[0] .then(function(results) { // Error handling for the weird way the old function works. var errors = results.reduce(function(carry, result) { if (result.errormessage) { carry.push(result.errormessage); } return carry; }, []); if (errors.length) { throw new Error(errors.join("\n")); } return results; }) .then(function(results) { // Format the results to match the other send message function. return results.map(function(result) { return { id: result.msgid, text: result.text, timecreated: result.timecreated, useridfrom: result.useridfrom, conversationid: result.conversationid, candeletemessagesforallusers: result.candeletemessagesforallusers }; }); }); }; /** * Send a single message to a user. * * @param {int} toUserId The recipient user id * @param {string} text The message text * @return {object} jQuery promise */ var sendMessageToUser = function(toUserId, text) { return sendMessagesToUser(toUserId, [text]) .then(function(results) { return results[0]; }); }; /** * Send messages to a conversation. * * @param {int} conversationId The conversation id * @param {string[]} messages List of messages to send * @return {object} jQuery promise */ var sendMessagesToConversation = function(conversationId, messages) { var formattedMessages = messages.map(function(message) { return { text: message }; }); var request = { methodname: 'core_message_send_messages_to_conversation', args: { conversationid: conversationId, messages: formattedMessages } }; return Ajax.call([request])[0]; }; /** * Send a message to a conversation. * * @param {int} conversationId The conversation id * @param {string} text The message text * @return {object} jQuery promise */ var sendMessageToConversation = function(conversationId, text) { return sendMessagesToConversation(conversationId, [text]) .then(function(result) { return result[0]; }); }; /** * Save message preferences. * * @param {int} userId The owner of the preferences * @param {object[]} preferences New preferences values * @return {object} jQuery promise */ var savePreferences = function(userId, preferences) { var request = { methodname: 'core_user_update_user_preferences', args: { userid: userId, preferences: preferences } }; return Ajax.call([request])[0]; }; /** * Get the user's preferences. * * @param {int} userId The target user * @return {object} jQuery promise */ var getPreferences = function(userId) { var request = { methodname: 'core_user_get_user_preferences', args: { userid: userId } }; return Ajax.call([request])[0]; }; /** * Delete a list of messages. * * @param {int} userId The user to delete messages for * @param {int[]} messageIds List of message ids to delete * @return {object} jQuery promise */ var deleteMessages = function(userId, messageIds) { return $.when.apply(null, Ajax.call(messageIds.map(function(messageId) { return { methodname: 'core_message_delete_message', args: { messageid: messageId, userid: userId } }; }))); }; /** * Delete a list of messages for all users. * * @param {int} userId The user to delete messages for * @param {int[]} messageIds List of message ids to delete * @return {object} jQuery promise */ var deleteMessagesForAllUsers = function(userId, messageIds) { return $.when.apply(null, Ajax.call(messageIds.map(function(messageId) { return { methodname: 'core_message_delete_message_for_all_users', args: { messageid: messageId, userid: userId } }; }))); }; /** * Delete a conversation between two users. * * @param {int} userId The user to delete messages for * @param {int} conversationId The id of the conversation * @return {object} jQuery promise */ var deleteConversation = function(userId, conversationId) { var request = { methodname: 'core_message_delete_conversations_by_id', args: { userid: userId, conversationids: [conversationId] } }; return Ajax.call([request])[0]; }; /** * Get the list of contact requests for a user. * * @param {int} userId The user id * @return {object} jQuery promise */ var getContactRequests = function(userId) { var request = { methodname: 'core_message_get_contact_requests', args: { userid: userId } }; return Ajax.call([request])[0]; }; /** * Accept a contact request. * * @param {int} sendingUserId The user that sent the request * @param {int} recipientUserId The user that received the request * @return {object} jQuery promise */ var acceptContactRequest = function(sendingUserId, recipientUserId) { var requests = [ { methodname: 'core_message_confirm_contact_request', args: { userid: sendingUserId, requesteduserid: recipientUserId } }, { methodname: 'core_message_get_member_info', args: { referenceuserid: recipientUserId, userids: [sendingUserId], includecontactrequests: true, includeprivacyinfo: true } } ]; // Wrap both requests in a single promise so that we can catch an error // from either request. return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) { // Only return the profile. return profiles.length ? profiles[0] : {}; }); }; /** * Decline a contact request. * * @param {int} sendingUserId The user that sent the request * @param {int} recipientUserId The user that received the request * @return {object} jQuery promise */ var declineContactRequest = function(sendingUserId, recipientUserId) { var requests = [ { methodname: 'core_message_decline_contact_request', args: { userid: sendingUserId, requesteduserid: recipientUserId } }, { methodname: 'core_message_get_member_info', args: { referenceuserid: recipientUserId, userids: [sendingUserId], includecontactrequests: true, includeprivacyinfo: true } } ]; // Wrap both requests in a single promise so that we can catch an error // from either request. return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) { // Only return the profile. return profiles.length ? profiles[0] : {}; }); }; /** * Get a conversation. * * @param {int} loggedInUserId The logged in user * @param {int} conversationId The conversation id * @param {bool} includeContactRequests Incldue contact requests between members * @param {bool} includePrivacyInfo Include privacy info for members * @param {int} memberLimit Limit for members * @param {int} memberOffset Offset for members * @param {int} messageLimit Limit for messages * @param {int} messageOffset Offset for messages * @param {bool} newestMessagesFirst Order the messages by newest first * @return {object} jQuery promise */ var getConversation = function( loggedInUserId, conversationId, includeContactRequests, includePrivacyInfo, memberLimit, memberOffset, messageLimit, messageOffset, newestMessagesFirst ) { var args = { userid: loggedInUserId, conversationid: conversationId }; if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) { args.includecontactrequests = includeContactRequests; } if (typeof includePrivacyInfo != 'undefined' && includePrivacyInfo !== null) { args.includeprivacyinfo = includePrivacyInfo; } if (typeof memberLimit != 'undefined' && memberLimit !== null) { args.memberlimit = memberLimit; } if (typeof memberOffset != 'undefined' && memberOffset !== null) { args.memberoffset = memberOffset; } if (typeof messageLimit != 'undefined' && messageLimit !== null) { args.messagelimit = messageLimit; } if (typeof messageOffset != 'undefined' && messageOffset !== null) { args.messageoffset = messageOffset; } if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) { args.newestmessagesfirst = newestMessagesFirst; } var request = { methodname: 'core_message_get_conversation', args: args }; return Ajax.call([request])[0]; }; /** * Get a conversation between users. * * @param {int} loggedInUserId The logged in user * @param {int} otherUserId The other user id * @param {bool} includeContactRequests Incldue contact requests between members * @param {bool} includePrivacyInfo Include privacy info for members * @param {int} memberLimit Limit for members * @param {int} memberOffset Offset for members * @param {int} messageLimit Limit for messages * @param {int} messageOffset Offset for messages * @param {bool} newestMessagesFirst Order the messages by newest first * @return {object} jQuery promise */ var getConversationBetweenUsers = function( loggedInUserId, otherUserId, includeContactRequests, includePrivacyInfo, memberLimit, memberOffset, messageLimit, messageOffset, newestMessagesFirst ) { var args = { userid: loggedInUserId, otheruserid: otherUserId }; if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) { args.includecontactrequests = includeContactRequests; } if (typeof includePrivacyInfo != 'undefined' && includePrivacyInfo !== null) { args.includeprivacyinfo = includePrivacyInfo; } if (typeof memberLimit != 'undefined' && memberLimit !== null) { args.memberlimit = memberLimit; } if (typeof memberOffset != 'undefined' && memberOffset !== null) { args.memberoffset = memberOffset; } if (typeof messageLimit != 'undefined' && messageLimit !== null) { args.messagelimit = messageLimit; } if (typeof messageOffset != 'undefined' && messageOffset !== null) { args.messageoffset = messageOffset; } if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) { args.newestmessagesfirst = newestMessagesFirst; } var request = { methodname: 'core_message_get_conversation_between_users', args: args }; return Ajax.call([request])[0]; }; /** * Get a self-conversation. * * @param {int} loggedInUserId The logged in user * @param {int} messageLimit Limit for messages * @param {int} messageOffset Offset for messages * @param {bool} newestMessagesFirst Order the messages by newest first * @return {object} jQuery promise */ var getSelfConversation = function( loggedInUserId, messageLimit, messageOffset, newestMessagesFirst ) { var args = { userid: loggedInUserId }; if (typeof messageLimit != 'undefined' && messageLimit !== null) { args.messagelimit = messageLimit; } if (typeof messageOffset != 'undefined' && messageOffset !== null) { args.messageoffset = messageOffset; } if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) { args.newestmessagesfirst = newestMessagesFirst; } var request = { methodname: 'core_message_get_self_conversation', args: args }; return Ajax.call([request])[0]; }; /** * Get the conversations for a user. * * @param {int} userId The logged in user * @param {int|null} type The type of conversation to get * @param {int} limit Limit for results * @param {int} offset Offset for results * @param {bool|null} favourites If favourites should be included or not * @param {bool} mergeself * @return {object} jQuery promise */ var getConversations = function( userId, type, limit, offset, favourites, mergeself ) { var args = { userid: userId, type: type }; if (typeof limit != 'undefined' && limit !== null) { args.limitnum = limit; } if (typeof offset != 'undefined' && offset !== null) { args.limitfrom = offset; } if (typeof favourites != 'undefined' && favourites !== null) { args.favourites = favourites; } if (typeof mergeself != 'undefined' && mergeself !== null) { args.mergeself = mergeself; } var request = { methodname: 'core_message_get_conversations', args: args }; return Ajax.call([request])[0] .then(function(result) { if (result.conversations.length) { result.conversations = result.conversations.map(function(conversation) { if (conversation.type == CONVERSATION_TYPES.PRIVATE || conversation.type == CONVERSATION_TYPES.SELF) { var otherUser = conversation.members.length ? conversation.members[0] : null; if (otherUser) { conversation.name = conversation.name ? conversation.name : otherUser.fullname; conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl; } } return conversation; }); } return result; }); }; /** * Get the conversations for a user. * * @param {int} conversationId The conversation id * @param {int} loggedInUserId The logged in user * @param {int} limit Limit for results * @param {int} offset Offset for results * @param {bool} includeContactRequests If contact requests should be included in result * @return {object} jQuery promise */ var getConversationMembers = function(conversationId, loggedInUserId, limit, offset, includeContactRequests) { var args = { userid: loggedInUserId, conversationid: conversationId }; if (typeof limit != 'undefined' && limit !== null) { args.limitnum = limit; } if (typeof offset != 'undefined' && offset !== null) { args.limitfrom = offset; } if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) { args.includecontactrequests = includeContactRequests; } var request = { methodname: 'core_message_get_conversation_members', args: args }; return Ajax.call([request])[0]; }; /** * Set a list of conversations to set as favourites for the given user. * * @param {int} userId The user id * @param {array} conversationIds List of conversation ids to set as favourite * @return {object} jQuery promise */ var setFavouriteConversations = function(userId, conversationIds) { var request = { methodname: 'core_message_set_favourite_conversations', args: { userid: userId, conversations: conversationIds } }; return Ajax.call([request])[0]; }; /** * Set a list of conversations to unset as favourites for the given user. * * @param {int} userId The user id * @param {array} conversationIds List of conversation ids to unset as favourite * @return {object} jQuery promise */ var unsetFavouriteConversations = function(userId, conversationIds) { var request = { methodname: 'core_message_unset_favourite_conversations', args: { userid: userId, conversations: conversationIds } }; return Ajax.call([request])[0]; }; /** * Set a list of conversations to set as muted for the given user. * * @param {int} userId The user id * @param {array} conversationIds List of conversation ids to set as favourite * @return {object} jQuery promise */ var setMutedConversations = function(userId, conversationIds) { var request = { methodname: 'core_message_mute_conversations', args: { userid: userId, conversationids: conversationIds } }; return Ajax.call([request])[0]; }; /** * Set a list of conversations to unset as muted for the given user. * * @param {int} userId The user id * @param {array} conversationIds List of conversation ids to unset as favourite * @return {object} jQuery promise */ var unsetMutedConversations = function(userId, conversationIds) { var request = { methodname: 'core_message_unmute_conversations', args: { userid: userId, conversationids: conversationIds } }; return Ajax.call([request])[0]; }; /** * Get a list of user's member info. * * @param {int} referenceUserId The user id * @param {array} userIds List of user ids to get * @param {bool} includeContactRequests Include contact requests between users in response * @param {bool} includePrivacyInfo Include privacy info for reference user in response * @return {object} jQuery promise */ var getMemberInfo = function(referenceUserId, userIds, includeContactRequests, includePrivacyInfo) { var args = { referenceuserid: referenceUserId, userids: userIds }; if (typeof includeContactRequests != 'undefined') { args.includecontactrequests = includeContactRequests; } if (typeof includePrivacyInfo != 'undefined') { args.includeprivacyinfo = includePrivacyInfo; } var request = { methodname: 'core_message_get_member_info', args: args }; return Ajax.call([request])[0]; }; /** * Get a list of user's member info. * * @param {int} userId The user id to mark as read for * @param {int} conversationId The conversation to mark as read * @return {object} jQuery promise */ var markAllConversationMessagesAsRead = function(userId, conversationId) { var request = { methodname: 'core_message_mark_all_conversation_messages_as_read', args: { userid: userId, conversationid: conversationId } }; return Ajax.call([request])[0]; }; /** * Get the user's message preferences. * * @param {int} userId The user id to load preferences for * @return {object} jQuery promise */ var getUserMessagePreferences = function(userId) { var request = { methodname: 'core_message_get_user_message_preferences', args: { userid: userId } }; return Ajax.call([request])[0]; }; /** * The the count of the user's conversations grouped by type. * * @param {Number} userId The user's id. * @return {Object} jQuery promise. */ var getTotalConversationCounts = function(userId) { var request = { methodname: 'core_message_get_conversation_counts', args: { userid: userId } }; return Ajax.call([request])[0]; }; /** * The the count of the user's unread conversations grouped by type. * * @param {Number} userId The user's id. * @return {Object} jQuery promise. */ var getUnreadConversationCounts = function(userId) { var request = { methodname: 'core_message_get_unread_conversation_counts', args: { userid: userId } }; return Ajax.call([request])[0]; }; /** * Get both the unread and total conversation counts in a single request. * * @param {Number} userId The user's id. * @return {Object} jQuery promise. */ var getAllConversationCounts = function(userId) { var requests = [ { methodname: 'core_message_get_conversation_counts', args: { userid: userId } }, { methodname: 'core_message_get_unread_conversation_counts', args: { userid: userId } }, ]; return $.when.apply(null, Ajax.call(requests)).then(function(total, unread) { return { total: total, unread: unread }; }); }; return { countUnreadConversations: countUnreadConversations, getContacts: getContacts, blockUser: blockUser, unblockUser: unblockUser, createContactRequest: createContactRequest, deleteContacts: deleteContacts, getMessages: getMessages, searchUsers: searchUsers, searchMessages: searchMessages, sendMessagesToUser: sendMessagesToUser, sendMessageToUser: sendMessageToUser, sendMessagesToConversation: sendMessagesToConversation, sendMessageToConversation: sendMessageToConversation, savePreferences: savePreferences, getPreferences: getPreferences, deleteMessages: deleteMessages, deleteMessagesForAllUsers: deleteMessagesForAllUsers, deleteConversation: deleteConversation, getContactRequests: getContactRequests, acceptContactRequest: acceptContactRequest, declineContactRequest: declineContactRequest, getConversation: getConversation, getConversationBetweenUsers: getConversationBetweenUsers, getSelfConversation: getSelfConversation, getConversations: getConversations, getConversationMembers: getConversationMembers, setFavouriteConversations: setFavouriteConversations, setMutedConversations: setMutedConversations, unsetFavouriteConversations: unsetFavouriteConversations, unsetMutedConversations: unsetMutedConversations, getMemberInfo: getMemberInfo, markAllConversationMessagesAsRead: markAllConversationMessagesAsRead, getUserMessagePreferences: getUserMessagePreferences, getTotalConversationCounts: getTotalConversationCounts, getUnreadConversationCounts: getUnreadConversationCounts, getAllConversationCounts: getAllConversationCounts }; }); PKB\PP%src/message_drawer_view_group_info.jsnu[// 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 . /** * Controls the group info page of the message drawer. * * @module core_message/message_drawer_view_group_info * @copyright 2018 Ryan Wyllie * @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 }; }); PKB\;!11+src/message_drawer_view_overview_section.jsnu[// 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 . /** * Controls a section of the overview page in the message drawer. * * @module core_message/message_drawer_view_overview_section * @copyright 2018 Ryan Wyllie * @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'); } else { container.addClass('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 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; // Re-render the unread count. renderUnreadCount(root, count); } }; /** * 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; }; root[0].addEventListener('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[0].addEventListener('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 }; }); PKB\yzC4src/message_drawer_view_contacts_section_contacts.jsnu[// 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 . /** * Controls the contacts section of the contacts page. * * @module core_message/message_drawer_view_contacts_section_contacts * @copyright 2018 Ryan Wyllie * @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, }; }); PKB\_YY4src/message_drawer_view_contacts_section_requests.jsnu[// 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 . /** * Controls the requests section of the contacts page. * * @module core_message/message_drawer_view_contacts_section_requests * @copyright 2018 Ryan Wyllie * @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, }; }); PKB\Z Z src/message_send_bulk.jsnu[// 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 . /** * Send bulk message to the given user ids. * * @module core_message/message_send_bulk * @copyright 2019 Shamim Rezaie * @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); }; PKB\yy5src/message_drawer_view_conversation_state_manager.jsnu[// 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 . /** * 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 * @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 || [], cancreatecontact: member.cancreatecontact, }; }); }; /** * 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 }; }); PKB\kĢ/'src/default_notification_preferences.jsnu[// 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 . /** * 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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ import * as Ajax from 'core/ajax'; import Notification from 'core/notification'; import {add as addToast} from 'core/toast'; 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.form-check'); 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); }); }; /** * AJAX call to update the default notification element status on the server. * * @param {Boolean} isEnabled * @param {string} preference */ const setDefaultNotification = (isEnabled, preference) => { // AJAX call to update the provider's enable status on the server. Ajax.call([{ methodname: 'core_message_set_default_notification', args: { preference: preference, state: isEnabled ? 1 : 0 } }])[0] .then((data) => addToast(data.successmessage)) .catch(Notification.exception); }; 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); setDefaultNotification( e.target.checked, providerEnabledElement.parentElement.parentElement.dataset.preference); }); }); container.querySelectorAll(selectors.lockSetting).forEach((lockedElement) => { // Set the initial statuses. if (lockedElement.checked) { toggleLockSetting(lockedElement); } lockedElement.addEventListener('change', (e) => { toggleLockSetting(e.target); setDefaultNotification( e.target.checked, lockedElement.parentElement.parentElement.dataset.preference); }); }); container.querySelectorAll(selectors.enabledSetting).forEach((enabledElement) => { enabledElement.addEventListener('change', (e) => { setDefaultNotification( e.target.checked, enabledElement.parentElement.parentElement.dataset.preference); }); }); }; /** * Initialize the page. */ const init = () => { registerEventListeners(); }; export default { init: init, }; PKB\侎``'src/message_drawer_view_conversation.jsnu[// 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 . /** * Controls the conversation page in the message drawer. * * This function handles all of the user actions that the user can take * when interacting with the conversation page. * * It maintains a view state which is a data representation of the view * and only operates on that data. * * The view state is immutable and should never be modified directly. Instead * all changes to the view state should be done using the StateManager which * will generate a new version of the view state with the requested changes. * * After any changes to the view state the module will call the render function * to ask the renderer to update the UI. * * General rules for this module: * 1.) Never modify viewState directly. All changes should be via the StateManager. * 2.) Call render() with the new state when you want to update the UI * 3.) Never modify the UI directly in this module. This module is only concerned * with the data in the view state. * * The general flow for a user interaction will be something like: * User interaction: User clicks "confirm block" button to block the other user * 1.) This module is hears the click * 2.) This module sends a request to the server to block the user * 3.) The server responds with the new user profile * 4.) This module generates a new state using the StateManager with the updated * user profile. * 5.) This module asks the Patcher to generate a patch from the current state and * the newly generated state. This patch tells the renderer what has changed * between the states. * 6.) This module gives the Renderer the generated patch. The renderer updates * the UI with changes according to the patch. * * @module core_message/message_drawer_view_conversation * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/auto_rows', 'core/backoff_timer', 'core/custom_interaction_events', 'core/notification', 'core/pending', 'core/pubsub', 'core/str', 'core_message/message_repository', 'core_message/message_drawer_events', 'core_message/message_drawer_view_conversation_constants', 'core_message/message_drawer_view_conversation_patcher', 'core_message/message_drawer_view_conversation_renderer', 'core_message/message_drawer_view_conversation_state_manager', 'core_message/message_drawer_router', 'core_message/message_drawer_routes', 'core/emoji/auto_complete', 'core/emoji/picker' ], function( $, AutoRows, BackOffTimer, CustomEvents, Notification, Pending, PubSub, Str, Repository, MessageDrawerEvents, Constants, Patcher, Renderer, StateManager, MessageDrawerRouter, MessageDrawerRoutes, initialiseEmojiAutoComplete, initialiseEmojiPicker ) { // Contains a cache of all view states that have been loaded so far // which saves us having to reload stuff with network requests when // switching between conversations. var stateCache = {}; // The current data representation of the view. var viewState = null; var loadedAllMessages = false; var messagesOffset = 0; var newMessagesPollTimer = null; var isRendering = false; var renderBuffer = []; // If the UI is currently resetting. var isResetting = true; // If the UI is currently sending a message. var isSendingMessage = false; // If the UI is currently deleting a conversation. var isDeletingConversationContent = false; // A buffer of messages to send. var sendMessageBuffer = []; // These functions which will be generated when this module is // first called. See generateRenderFunction for details. var render = null; // The list of renderers that have been registered to render // this conversation. See generateRenderFunction for details. var renderers = []; var NEWEST_FIRST = Constants.NEWEST_MESSAGES_FIRST; var LOAD_MESSAGE_LIMIT = Constants.LOAD_MESSAGE_LIMIT; var MILLISECONDS_IN_SEC = Constants.MILLISECONDS_IN_SEC; var SELECTORS = Constants.SELECTORS; var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES; /** * Get the other user userid. * * @return {Number} Userid. */ var getOtherUserId = function() { if (!viewState || viewState.type == CONVERSATION_TYPES.PUBLIC) { return null; } var loggedInUserId = viewState.loggedInUserId; if (viewState.type == CONVERSATION_TYPES.SELF) { // It's a self-conversation, so the other user is the one logged in. return loggedInUserId; } var otherUserIds = Object.keys(viewState.members).filter(function(userId) { return loggedInUserId != userId; }); return otherUserIds.length ? otherUserIds[0] : null; }; /** * Search the cache to see if we've already loaded a private conversation * with the given user id. * * @param {Number} userId The id of the other user. * @return {Number|null} Conversation id. */ var getCachedPrivateConversationIdFromUserId = function(userId) { return Object.keys(stateCache).reduce(function(carry, id) { if (!carry) { var state = stateCache[id].state; if (state.type != CONVERSATION_TYPES.PUBLIC) { if (userId in state.members) { // We've found a cached conversation for this user! carry = state.id; } } } return carry; }, null); }; /** * Get profile info for logged in user. * * @param {Object} body Conversation body container element. * @return {Object} */ var getLoggedInUserProfile = function(body) { return { id: parseInt(body.attr('data-user-id'), 10), fullname: null, profileimageurl: null, profileimageurlsmall: null, isonline: null, showonlinestatus: null, isblocked: null, iscontact: null, isdeleted: null, canmessage: null, canmessageevenifblocked: null, requirescontact: null, contactrequests: [], cancreatecontact: null, }; }; /** * Get the messages offset value to load more messages. * * @return {Number} */ var getMessagesOffset = function() { return messagesOffset; }; /** * Set the messages offset value for loading more messages. * * @param {Number} value The offset value */ var setMessagesOffset = function(value) { messagesOffset = value; stateCache[viewState.id].messagesOffset = value; }; /** * Check if all messages have been loaded. * * @return {Bool} */ var hasLoadedAllMessages = function() { return loadedAllMessages; }; /** * Set whether all messages have been loaded or not. * * @param {Bool} value If all messages have been loaded. */ var setLoadedAllMessages = function(value) { loadedAllMessages = value; stateCache[viewState.id].loadedAllMessages = value; }; /** * Get the messages container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var getMessagesContainer = function(body) { return body.find(SELECTORS.MESSAGES_CONTAINER); }; /** * Reformat the conversation for an event payload. * * @param {Object} state The view state. * @return {Object} New formatted conversation. */ var formatConversationForEvent = function(state) { return { id: state.id, name: state.name, subname: state.subname, imageUrl: state.imageUrl, isFavourite: state.isFavourite, isMuted: state.isMuted, type: state.type, totalMemberCount: state.totalMemberCount, loggedInUserId: state.loggedInUserId, messages: state.messages.map(function(message) { return $.extend({}, message); }), members: Object.keys(state.members).map(function(id) { var formattedMember = $.extend({}, state.members[id]); formattedMember.contactrequests = state.members[id].contactrequests.map(function(request) { return $.extend({}, request); }); return formattedMember; }) }; }; /** * Load up an empty private conversation between the logged in user and the * other user. Sets all of the conversation details based on the other user. * * A conversation isn't created until the user sends the first message. * * @param {Object} loggedInUserProfile The logged in user profile. * @param {Number} otherUserId The other user id. * @return {Object} Profile returned from repository. */ var loadEmptyPrivateConversation = function(loggedInUserProfile, otherUserId) { var loggedInUserId = loggedInUserProfile.id; // If the other user id is the same as the logged in user then this is a self // conversation. var conversationType = loggedInUserId == otherUserId ? CONVERSATION_TYPES.SELF : CONVERSATION_TYPES.PRIVATE; var newState = StateManager.setLoadingMembers(viewState, true); newState = StateManager.setLoadingMessages(newState, true); render(newState); return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true) .then(function(profiles) { if (profiles.length) { return profiles[0]; } else { throw new Error('Unable to load other user profile'); } }) .then(function(profile) { // If the conversation is a self conversation then the profile loaded is the // logged in user so only add that to the members array. var members = conversationType == CONVERSATION_TYPES.SELF ? [profile] : [profile, loggedInUserProfile]; var newState = StateManager.addMembers(viewState, members); newState = StateManager.setLoadingMembers(newState, false); newState = StateManager.setLoadingMessages(newState, false); newState = StateManager.setName(newState, profile.fullname); newState = StateManager.setType(newState, conversationType); newState = StateManager.setImageUrl(newState, profile.profileimageurl); newState = StateManager.setTotalMemberCount(newState, members.length); render(newState); return profile; }) .catch(function(error) { var newState = StateManager.setLoadingMembers(viewState, false); render(newState); Notification.exception(error); }); }; /** * Create a new state from a conversation object. * * @param {Object} conversation The conversation object. * @param {Number} loggedInUserId The logged in user id. * @return {Object} new state. */ var updateStateFromConversation = function(conversation, loggedInUserId) { var otherUser = null; if (conversation.type == CONVERSATION_TYPES.PRIVATE) { // For private conversations, remove current logged in user from the members list to get the other user. var otherUsers = conversation.members.filter(function(member) { return member.id != loggedInUserId; }); otherUser = otherUsers.length ? otherUsers[0] : null; } else if (conversation.type == CONVERSATION_TYPES.SELF) { // Self-conversations have only one member. otherUser = conversation.members[0]; } var name = conversation.name; var imageUrl = conversation.imageurl; if (conversation.type != CONVERSATION_TYPES.PUBLIC) { name = name || otherUser ? otherUser.fullname : ''; imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : ''; } var newState = StateManager.addMembers(viewState, conversation.members); newState = StateManager.setName(newState, name); newState = StateManager.setSubname(newState, conversation.subname); newState = StateManager.setType(newState, conversation.type); newState = StateManager.setImageUrl(newState, imageUrl); newState = StateManager.setTotalMemberCount(newState, conversation.membercount); newState = StateManager.setIsFavourite(newState, conversation.isfavourite); newState = StateManager.setIsMuted(newState, conversation.ismuted); newState = StateManager.addMessages(newState, conversation.messages); newState = StateManager.setCanDeleteMessagesForAllUsers(newState, conversation.candeletemessagesforallusers); return newState; }; /** * Get the details for a conversation from the conversation id. * * @param {Number} conversationId The conversation id. * @param {Object} loggedInUserProfile The logged in user profile. * @param {Number} messageLimit The number of messages to include. * @param {Number} messageOffset The number of messages to skip. * @param {Bool} newestFirst Order messages newest first. * @return {Object} Promise resolved when loaded. */ var loadNewConversation = function( conversationId, loggedInUserProfile, messageLimit, messageOffset, newestFirst ) { var loggedInUserId = loggedInUserProfile.id; var newState = StateManager.setLoadingMembers(viewState, true); newState = StateManager.setLoadingMessages(newState, true); render(newState); return Repository.getConversation( loggedInUserId, conversationId, true, true, 0, 0, messageLimit + 1, messageOffset, newestFirst ) .then(function(conversation) { if (conversation.messages.length > messageLimit) { conversation.messages = conversation.messages.slice(1); } else { setLoadedAllMessages(true); } setMessagesOffset(messageOffset + messageLimit); return conversation; }) .then(function(conversation) { var hasLoggedInUser = conversation.members.filter(function(member) { return member.id == loggedInUserProfile.id; }); if (hasLoggedInUser.length < 1) { conversation.members = conversation.members.concat([loggedInUserProfile]); } var newState = updateStateFromConversation(conversation, loggedInUserProfile.id); newState = StateManager.setLoadingMembers(newState, false); newState = StateManager.setLoadingMessages(newState, false); return render(newState) .then(function() { return conversation; }); }) .then(function() { return markConversationAsRead(conversationId); }) .catch(function(error) { var newState = StateManager.setLoadingMembers(viewState, false); newState = StateManager.setLoadingMessages(newState, false); render(newState); Notification.exception(error); }); }; /** * Get the details for a conversation from and existing conversation object. * * @param {Object} conversation The conversation object. * @param {Object} loggedInUserProfile The logged in user profile. * @param {Number} messageLimit The number of messages to include. * @param {Bool} newestFirst Order messages newest first. * @return {Object} Promise resolved when loaded. */ var loadExistingConversation = function( conversation, loggedInUserProfile, messageLimit, newestFirst ) { var hasLoggedInUser = conversation.members.filter(function(member) { return member.id == loggedInUserProfile.id; }); if (hasLoggedInUser.length < 1) { conversation.members = conversation.members.concat([loggedInUserProfile]); } var messageCount = conversation.messages.length; var hasLoadedEnoughMessages = messageCount >= messageLimit; var newState = updateStateFromConversation(conversation, loggedInUserProfile.id); newState = StateManager.setLoadingMembers(newState, false); newState = StateManager.setLoadingMessages(newState, !hasLoadedEnoughMessages); var renderPromise = render(newState); return renderPromise.then(function() { if (!hasLoadedEnoughMessages) { // We haven't got enough messages so let's load some more. return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []); } else { // We've got enough messages. No need to load any more for now. return {messages: conversation.messages}; } }) .then(function() { var messages = viewState.messages; // Update the offset to reflect the number of messages we've loaded. setMessagesOffset(messages.length); markConversationAsRead(viewState.id); return messages; }) .catch(Notification.exception); }; /** * Load messages for this conversation and pass them to the renderer. * * @param {Number} conversationId Conversation id. * @param {Number} limit Number of messages to load. * @param {Number} offset Get messages from offset. * @param {Bool} newestFirst Get newest messages first. * @param {Array} ignoreList Ignore any messages with ids in this list. * @param {Number|null} timeFrom Only get messages from this time onwards. * @return {Promise} renderer promise. */ var loadMessages = function(conversationId, limit, offset, newestFirst, ignoreList, timeFrom) { return Repository.getMessages( viewState.loggedInUserId, conversationId, limit ? limit + 1 : limit, offset, newestFirst, timeFrom ) .then(function(result) { // Prevent older requests from contaminating the current view. if (result.id != viewState.id) { result.messages = []; // Purge old conversation cache to prevent messages lose. if (result.id in stateCache) { delete stateCache[result.id]; } } return result; }) .then(function(result) { if (result.messages.length && ignoreList.length) { result.messages = result.messages.filter(function(message) { // Skip any messages in our ignore list. return ignoreList.indexOf(parseInt(message.id, 10)) < 0; }); } return result; }) .then(function(result) { if (!limit) { return result; } else if (result.messages.length > limit) { // Ignore the last result which was just to test if there are more // to load. result.messages = result.messages.slice(0, -1); } else { setLoadedAllMessages(true); } return result; }) .then(function(result) { var membersToAdd = result.members.filter(function(member) { return !(member.id in viewState.members); }); var newState = StateManager.addMembers(viewState, membersToAdd); newState = StateManager.addMessages(newState, result.messages); newState = StateManager.setLoadingMessages(newState, false); return render(newState) .then(function() { return result; }); }) .catch(function(error) { var newState = StateManager.setLoadingMessages(viewState, false); render(newState); // Re-throw the error for other error handlers. throw error; }); }; /** * Create a callback function for getting new messages for this conversation. * * @param {Number} conversationId Conversation id. * @param {Bool} newestFirst Show newest messages first * @return {Function} Callback function that returns a renderer promise. */ var getLoadNewMessagesCallback = function(conversationId, newestFirst) { return function() { var messages = viewState.messages; var mostRecentMessage = messages.length ? messages[messages.length - 1] : null; var lastTimeCreated = mostRecentMessage ? mostRecentMessage.timeCreated : null; if (lastTimeCreated && !isResetting && !isSendingMessage && !isDeletingConversationContent) { // There may be multiple messages with the same time created value since // the accuracy is only down to the second. The server will include these // messages in the result (since it does a >= comparison on time from) so // we need to filter them back out of the result so that we're left only // with the new messages. var ignoreMessageIds = []; for (var i = messages.length - 1; i >= 0; i--) { var message = messages[i]; if (message.timeCreated === lastTimeCreated) { ignoreMessageIds.push(message.id); } else { // Since the messages are ordered in ascending order of time created // we can break as soon as we hit a message with a different time created // because we know all other messages will have lower values. break; } } return loadMessages( conversationId, 0, 0, newestFirst, ignoreMessageIds, lastTimeCreated ) .then(function(result) { if (result.messages.length) { // If we found some results then restart the polling timer // because the other user might be sending messages. newMessagesPollTimer.restart(); // We've also got a new last message so publish that for other // components to update. var conversation = formatConversationForEvent(viewState); PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation); return markConversationAsRead(conversationId); } else { return result; } }); } return $.Deferred().resolve().promise(); }; }; /** * Mark a conversation as read. * * @param {Number} conversationId The conversation id. * @return {Promise} The renderer promise. */ var markConversationAsRead = function(conversationId) { var loggedInUserId = viewState.loggedInUserId; var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead'); return Repository.markAllConversationMessagesAsRead(loggedInUserId, conversationId) .then(function() { var newState = StateManager.markMessagesAsRead(viewState, viewState.messages); PubSub.publish(MessageDrawerEvents.CONVERSATION_READ, conversationId); return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Tell the statemanager there is request to block a user and run the renderer * to show the block user dialogue. * * @param {Number} userId User id. */ var requestBlockUser = function(userId) { cancelRequest(userId); var newState = StateManager.addPendingBlockUsersById(viewState, [userId]); render(newState); }; /** * Send the repository a request to block a user, update the statemanager and publish * a contact has been blocked. * * @param {Number} userId User id of user to block. * @return {Promise} Renderer promise. */ var blockUser = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); var pendingPromise = new Pending('core_message/message_drawer_view_conversation:blockUser'); render(newState); return Repository.blockUser(viewState.loggedInUserId, userId) .then(function(profile) { var newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.removePendingBlockUsersById(newState, [userId]); newState = StateManager.setLoadingConfirmAction(newState, false); PubSub.publish(MessageDrawerEvents.CONTACT_BLOCKED, userId); return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Tell the statemanager there is a request to unblock a user and run the renderer * to show the unblock user dialogue. * * @param {Number} userId User id of user to unblock. */ var requestUnblockUser = function(userId) { cancelRequest(userId); var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]); render(newState); }; /** * Send the repository a request to unblock a user, update the statemanager and publish * a contact has been unblocked. * * @param {Number} userId User id of user to unblock. * @return {Promise} Renderer promise. */ var unblockUser = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unblockUser'); render(newState); return Repository.unblockUser(viewState.loggedInUserId, userId) .then(function(profile) { var newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.removePendingUnblockUsersById(newState, [userId]); newState = StateManager.setLoadingConfirmAction(newState, false); PubSub.publish(MessageDrawerEvents.CONTACT_UNBLOCKED, userId); return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Tell the statemanager there is a request to remove a user from the contact list * and run the renderer to show the remove user from contacts dialogue. * * @param {Number} userId User id of user to remove from contacts. */ var requestRemoveContact = function(userId) { cancelRequest(userId); var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]); render(newState); }; /** * Send the repository a request to remove a user from the contacts list. update the statemanager * and publish a contact has been removed. * * @param {Number} userId User id of user to remove from contacts. * @return {Promise} Renderer promise. */ var removeContact = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); var pendingPromise = new Pending('core_message/message_drawer_view_conversation:removeContact'); render(newState); return Repository.deleteContacts(viewState.loggedInUserId, [userId]) .then(function(profiles) { var newState = StateManager.addMembers(viewState, profiles); newState = StateManager.removePendingRemoveContactsById(newState, [userId]); newState = StateManager.setLoadingConfirmAction(newState, false); PubSub.publish(MessageDrawerEvents.CONTACT_REMOVED, userId); return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Tell the statemanager there is a request to add a user to the contact list * and run the renderer to show the add user to contacts dialogue. * * @param {Number} userId User id of user to add to contacts. */ var requestAddContact = function(userId) { cancelRequest(userId); var newState = StateManager.addPendingAddContactsById(viewState, [userId]); render(newState); }; /** * Send the repository a request to add a user to the contacts list. update the statemanager * and publish a contact has been added. * * @param {Number} userId User id of user to add to contacts. * @return {Promise} Renderer promise. */ var addContact = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); var pendingPromise = new Pending('core_message/message_drawer_view_conversation:addContactRequests'); render(newState); return Repository.createContactRequest(viewState.loggedInUserId, userId) .then(function(response) { if (!response.request) { throw new Error(response.warnings[0].message); } return response.request; }) .then(function(request) { var newState = StateManager.removePendingAddContactsById(viewState, [userId]); newState = StateManager.addContactRequests(newState, [request]); newState = StateManager.setLoadingConfirmAction(newState, false); return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Set the current conversation as a favourite conversation. * * @return {Promise} Renderer promise. */ var setFavourite = function() { var userId = viewState.loggedInUserId; var conversationId = viewState.id; var pendingPromise = new Pending('core_message/message_drawer_view_conversation:setFavourite'); return Repository.setFavouriteConversations(userId, [conversationId]) .then(function() { var newState = StateManager.setIsFavourite(viewState, true); return render(newState); }) .then(function() { return PubSub.publish( MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, formatConversationForEvent(viewState) ); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Unset the current conversation as a favourite conversation. * * @return {Promise} Renderer promise. */ var unsetFavourite = function() { var userId = viewState.loggedInUserId; var conversationId = viewState.id; var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unsetFavourite'); return Repository.unsetFavouriteConversations(userId, [conversationId]) .then(function() { var newState = StateManager.setIsFavourite(viewState, false); return render(newState); }) .then(function() { return PubSub.publish( MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, formatConversationForEvent(viewState) ); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Set the current conversation as a muted conversation. * * @return {Promise} Renderer promise. */ var setMuted = function() { var userId = viewState.loggedInUserId; var conversationId = viewState.id; var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead'); return Repository.setMutedConversations(userId, [conversationId]) .then(function() { var newState = StateManager.setIsMuted(viewState, true); return render(newState); }) .then(function() { return PubSub.publish( MessageDrawerEvents.CONVERSATION_SET_MUTED, formatConversationForEvent(viewState) ); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Unset the current conversation as a muted conversation. * * @return {Promise} Renderer promise. */ var unsetMuted = function() { var userId = viewState.loggedInUserId; var conversationId = viewState.id; return Repository.unsetMutedConversations(userId, [conversationId]) .then(function() { var newState = StateManager.setIsMuted(viewState, false); return render(newState); }) .then(function() { return PubSub.publish( MessageDrawerEvents.CONVERSATION_UNSET_MUTED, formatConversationForEvent(viewState) ); }); }; /** * Tell the statemanager there is a request to delete the selected messages * and run the renderer to show confirm delete messages dialogue. * * @param {Number} userId User id. */ var requestDeleteSelectedMessages = function(userId) { var selectedMessageIds = viewState.selectedMessageIds; cancelRequest(userId); var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds); render(newState); }; /** * Send the repository a request to delete the messages pending deletion. Update the statemanager * and publish a message deletion event. * * @return {Promise} Renderer promise. */ var deleteSelectedMessages = function() { var pendingPromise = new Pending('core_message/message_drawer_view_conversation:deleteSelectedMessages'); var messageIds = viewState.pendingDeleteMessageIds; var sentMessages = viewState.messages.filter(function(message) { // If a message sendState is null then it means it was loaded from the server or if it's // set to sent then it means the user has successfully sent it in this page load. return messageIds.indexOf(message.id) >= 0 && (message.sendState == 'sent' || message.sendState === null); }); var newState = StateManager.setLoadingConfirmAction(viewState, true); render(newState); var deleteMessagesPromise = $.Deferred().resolve().promise(); if (sentMessages.length) { // We only need to send a request to the server if we're trying to delete messages that // have successfully been sent. var sentMessageIds = sentMessages.map(function(message) { return message.id; }); if (newState.deleteMessagesForAllUsers) { deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, sentMessageIds); } else { deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, sentMessageIds); } } // Mark that we are deleting content from the conversation to prevent updates of it. isDeletingConversationContent = true; // Stop polling for new messages to the open conversation. if (newMessagesPollTimer) { newMessagesPollTimer.stop(); } return deleteMessagesPromise.then(function() { var newState = StateManager.removeMessagesById(viewState, messageIds); newState = StateManager.removePendingDeleteMessagesById(newState, messageIds); newState = StateManager.removeSelectedMessagesById(newState, messageIds); newState = StateManager.setLoadingConfirmAction(newState, false); newState = StateManager.setDeleteMessagesForAllUsers(newState, false); var prevLastMessage = viewState.messages[viewState.messages.length - 1]; var newLastMessage = newState.messages.length ? newState.messages[newState.messages.length - 1] : null; if (newLastMessage && newLastMessage.id != prevLastMessage.id) { var conversation = formatConversationForEvent(newState); PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation); } else if (!newState.messages.length) { PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id); } isDeletingConversationContent = false; return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }) .catch(Notification.exception); }; /** * Tell the statemanager there is a request to delete a conversation * and run the renderer to show confirm delete conversation dialogue. * * @param {Number} userId User id of other user. */ var requestDeleteConversation = function(userId) { cancelRequest(userId); var newState = StateManager.setPendingDeleteConversation(viewState, true); render(newState); }; /** * Send the repository a request to delete a conversation. Update the statemanager * and publish a conversation deleted event. * * @return {Promise} Renderer promise. */ var deleteConversation = function() { var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead'); var newState = StateManager.setLoadingConfirmAction(viewState, true); render(newState); // Mark that we are deleting the conversation to prevent updates of it. isDeletingConversationContent = true; // Stop polling for new messages to the open conversation. if (newMessagesPollTimer) { newMessagesPollTimer.stop(); } return Repository.deleteConversation(viewState.loggedInUserId, viewState.id) .then(function() { var newState = StateManager.removeMessages(viewState, viewState.messages); newState = StateManager.removeSelectedMessagesById(newState, viewState.selectedMessageIds); newState = StateManager.setPendingDeleteConversation(newState, false); newState = StateManager.setLoadingConfirmAction(newState, false); PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id); isDeletingConversationContent = false; return render(newState); }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Tell the statemanager to cancel all pending actions. * * @param {Number} userId User id. */ var cancelRequest = function(userId) { var pendingDeleteMessageIds = viewState.pendingDeleteMessageIds; var newState = StateManager.removePendingAddContactsById(viewState, [userId]); newState = StateManager.removePendingRemoveContactsById(newState, [userId]); newState = StateManager.removePendingUnblockUsersById(newState, [userId]); newState = StateManager.removePendingBlockUsersById(newState, [userId]); newState = StateManager.removePendingDeleteMessagesById(newState, pendingDeleteMessageIds); newState = StateManager.setPendingDeleteConversation(newState, false); newState = StateManager.setDeleteMessagesForAllUsers(newState, false); render(newState); }; /** * Accept the contact request from the given user. * * @param {Number} userId User id of other user. * @return {Promise} Renderer promise. */ var acceptContactRequest = function(userId) { var pendingPromise = new Pending('core_message/message_drawer_view_conversation:acceptContactRequest'); // Search the list of the logged in user's contact requests to find the // one from this user. var loggedInUserId = viewState.loggedInUserId; var requests = viewState.members[userId].contactrequests.filter(function(request) { return request.requesteduserid == loggedInUserId; }); var request = requests[0]; var newState = StateManager.setLoadingConfirmAction(viewState, true); render(newState); return Repository.acceptContactRequest(userId, loggedInUserId) .then(function(profile) { var newState = StateManager.removeContactRequests(viewState, [request]); newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.setLoadingConfirmAction(newState, false); return render(newState); }) .then(function() { PubSub.publish(MessageDrawerEvents.CONTACT_ADDED, viewState.members[userId]); PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, request); return; }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Decline the contact request from the given user. * * @param {Number} userId User id of other user. * @return {Promise} Renderer promise. */ var declineContactRequest = function(userId) { var pendingPromise = new Pending('core_message/message_drawer_view_conversation:declineContactRequest'); // Search the list of the logged in user's contact requests to find the // one from this user. var loggedInUserId = viewState.loggedInUserId; var requests = viewState.members[userId].contactrequests.filter(function(request) { return request.requesteduserid == loggedInUserId; }); var request = requests[0]; var newState = StateManager.setLoadingConfirmAction(viewState, true); render(newState); return Repository.declineContactRequest(userId, loggedInUserId) .then(function(profile) { var newState = StateManager.removeContactRequests(viewState, [request]); newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.setLoadingConfirmAction(newState, false); return render(newState); }) .then(function() { PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, request); return; }) .then(function(result) { pendingPromise.resolve(); return result; }); }; /** * Send all of the messages in the buffer to the server to be created. Update the * UI with the newly created message information. * * This function will recursively call itself in order to make sure the buffer is * always being processed. */ var processSendMessageBuffer = function() { if (isSendingMessage) { // We're already sending messages so nothing to do. return; } if (!sendMessageBuffer.length) { // No messages waiting to send. Nothing to do. return; } var pendingPromise = new Pending('core_message/message_drawer_view_conversation:processSendMessageBuffer'); // Flag that we're processing the queue. isSendingMessage = true; // Grab all of the messages in the buffer. var messagesToSend = sendMessageBuffer.slice(); // Empty the buffer since we're processing it. sendMessageBuffer = []; var conversationId = viewState.id; var newConversationId = null; var messagesText = messagesToSend.map(function(message) { return message.text; }); var messageIds = messagesToSend.map(function(message) { return message.id; }); var sendMessagePromise = null; var newCanDeleteMessagesForAllUsers = null; if (!conversationId && (viewState.type != CONVERSATION_TYPES.PUBLIC)) { // If it's a new private conversation then we need to use the old // web service function to create the conversation. var otherUserId = getOtherUserId(); sendMessagePromise = Repository.sendMessagesToUser(otherUserId, messagesText) .then(function(messages) { if (messages.length) { newConversationId = parseInt(messages[0].conversationid, 10); newCanDeleteMessagesForAllUsers = messages[0].candeletemessagesforallusers; } return messages; }); } else { sendMessagePromise = Repository.sendMessagesToConversation(conversationId, messagesText); } sendMessagePromise .then(function(messages) { var newMessageIds = messages.map(function(message) { return message.id; }); var data = []; var selectedToRemove = []; var selectedToAdd = []; messagesToSend.forEach(function(oldMessage, index) { var newMessage = messages[index]; // Update messages expects and array of arrays where the first value // is the old message to update and the second value is the new values // to set. data.push([oldMessage, newMessage]); if (viewState.selectedMessageIds.indexOf(oldMessage.id) >= 0) { // If the message was added to the "selected messages" list while it was still // being sent then we should update it's id in that list now to make sure future // actions work. selectedToRemove.push(oldMessage.id); selectedToAdd.push(newMessage.id); } }); var newState = StateManager.updateMessages(viewState, data); newState = StateManager.setMessagesSendSuccessById(newState, newMessageIds); if (selectedToRemove.length) { newState = StateManager.removeSelectedMessagesById(newState, selectedToRemove); } if (selectedToAdd.length) { newState = StateManager.addSelectedMessagesById(newState, selectedToAdd); } var conversation = formatConversationForEvent(newState); if (!newState.id) { // If this message created the conversation then save the conversation // id. newState = StateManager.setId(newState, newConversationId); conversation.id = newConversationId; resetMessagePollTimer(newConversationId); PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED, conversation); newState = StateManager.setCanDeleteMessagesForAllUsers(newState, newCanDeleteMessagesForAllUsers); } // Update the UI with the new message values from the server. render(newState); // Recurse just in case there has been more messages added to the buffer. isSendingMessage = false; processSendMessageBuffer(); PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation); return; }) .then(function(result) { pendingPromise.resolve(); return result; }) .catch(function(e) { var errorMessage; if (e.message) { errorMessage = $.Deferred().resolve(e.message).promise(); } else { errorMessage = Str.get_string('unknownerror', 'core'); } var handleFailedMessages = function(errorMessage) { // We failed to create messages so remove the old messages from the pending queue // and update the UI to indicate that the message failed. var newState = StateManager.setMessagesSendFailById(viewState, messageIds, errorMessage); render(newState); isSendingMessage = false; processSendMessageBuffer(); }; errorMessage.then(handleFailedMessages) .then(function(result) { pendingPromise.resolve(); return result; }) .catch(function(e) { // Hrmm, we can't even load the error messages string! We'll have to // hard code something in English here if we still haven't got a message // to show. var finalError = e.message || 'Something went wrong!'; handleFailedMessages(finalError); }); }); }; /** * Create a plain version of an HTML text. * * This texts is used as a message preview while is sent to the server. This way * it is possible to prevent self-xss. * * @param {String} text Text to send. * @return {String} The plain text version of the text. */ const previewText = function(text) { // Remove all script and styles from text (we don't want it there). let plaintext = text.replace(//gi, ''); plaintext = plaintext.replace(//gi, ''); // Beautify a bit the output adding some line breaks. plaintext = plaintext.replace(/<\/div>/ig, '\n'); plaintext = plaintext.replace(/<\/li>/ig, '\n'); plaintext = plaintext.replace(/
  • /ig, ' * '); plaintext = plaintext.replace(/<\/ul>/ig, '\n'); plaintext = plaintext.replace(/<\/p>/ig, '\n'); plaintext = plaintext.replace(/]*>/gi, '\n'); // Remove all remaining tags and convert line breaks into html. plaintext = plaintext.replace(/<[^>]+>/ig, ''); plaintext = plaintext.replace(/\n+/ig, '\n'); return plaintext.replace(/\n/ig, '
    '); }; /** * Buffers messages to be sent to the server. We use a buffer here to allow the * user to freely input messages without blocking the interface for them. * * Instead we just queue all of their messages up and send them as fast as we can. * * @param {String} text Text to send. */ var sendMessage = function(text) { var id = 'temp' + Date.now(); // Render a preview version of the message while sending. let loadingmessage = { id: id, useridfrom: viewState.loggedInUserId, text: previewText(text), timecreated: null }; var newState = StateManager.addMessages(viewState, [loadingmessage]); render(newState); // Send the real message. var message = { id: id, useridfrom: viewState.loggedInUserId, text: text, timecreated: null }; sendMessageBuffer.push(message); processSendMessageBuffer(); // Remove the unsent message attribute so we can be notified of new unsent messages (see storeUnsentMessage). const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA); if (textArea) { textArea.removeAttribute('data-unsent-message-viewed'); } }; /** * Retry sending a message that failed. * * @param {Object} message The message to send. */ var retrySendMessage = function(message) { var newState = StateManager.setMessagesSendPendingById(viewState, [message.id]); render(newState); sendMessageBuffer.push(message); processSendMessageBuffer(); }; /** * Toggle the selected messages update the statemanager and render the result. * * @param {Number} messageId The id of the message to be toggled */ var toggleSelectMessage = function(messageId) { var newState = viewState; if (viewState.selectedMessageIds.indexOf(messageId) > -1) { newState = StateManager.removeSelectedMessagesById(viewState, [messageId]); } else { newState = StateManager.addSelectedMessagesById(viewState, [messageId]); } render(newState); }; /** * Cancel edit mode (selecting the messages). */ var cancelEditMode = function() { cancelRequest(getOtherUserId()); var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds); render(newState); }; /** * Process the patches in the render buffer one at a time in order until the * buffer is empty. * * @param {Object} header The conversation header container element. * @param {Object} body The conversation body container element. * @param {Object} footer The conversation footer container element. */ var processRenderBuffer = function(header, body, footer) { if (isRendering) { return; } if (!renderBuffer.length) { return; } isRendering = true; var renderable = renderBuffer.shift(); var renderPromises = renderers.map(function(renderFunc) { return renderFunc(renderable.patch); }); $.when.apply(null, renderPromises) .then(function() { isRendering = false; renderable.deferred.resolve(true); // Keep processing the buffer until it's empty. processRenderBuffer(header, body, footer); return; }) .catch(function(error) { isRendering = false; renderable.deferred.reject(error); Notification.exception(error); }); }; /** * Create a function to render the Conversation. * * @param {Object} header The conversation header container element. * @param {Object} body The conversation body container element. * @param {Object} footer The conversation footer container element. * @param {Bool} isNewConversation Has someone else already initialised a conversation? * @return {Promise} Renderer promise. */ var generateRenderFunction = function(header, body, footer, isNewConversation) { var rendererFunc = function(patch) { return Renderer.render(header, body, footer, patch); }; if (!isNewConversation) { // Looks like someone got here before us! We'd better update our // UI to make sure it matches. var initialState = StateManager.buildInitialState(viewState.midnight, viewState.loggedInUserId, viewState.id); var syncPatch = Patcher.buildPatch(initialState, viewState); rendererFunc(syncPatch); } renderers.push(rendererFunc); return function(newState) { var patch = Patcher.buildPatch(viewState, newState); var deferred = $.Deferred(); // Check if the patch has any data. Ignore empty patches. if (Object.keys(patch).length) { // Add the patch to the render buffer which gets processed in order. renderBuffer.push({ patch: patch, deferred: deferred }); } else { deferred.resolve(true); } // This is a great place to add in some console logging if you need // to debug something. You can log the current state, the next state, // and the generated patch and see exactly what will be updated. // Optimistically update the state. We're going to assume that the rendering // will always succeed. The rendering is asynchronous (annoyingly) so it's buffered // but it'll reach eventual consistency with the current state. viewState = newState; if (newState.id) { // Only cache created conversations. stateCache[newState.id] = { state: newState, messagesOffset: getMessagesOffset(), loadedAllMessages: hasLoadedAllMessages() }; } // Start processing the buffer. processRenderBuffer(header, body, footer); return deferred.promise(); }; }; /** * Create a confirm action function. * * @param {Function} actionCallback The callback function. * @return {Function} Confirm action handler. */ var generateConfirmActionHandler = function(actionCallback) { return function(e, data) { if (!viewState.loadingConfirmAction) { actionCallback(getOtherUserId()); var newState = StateManager.setLoadingConfirmAction(viewState, false); render(newState); } data.originalEvent.preventDefault(); }; }; /** * Send message event handler. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleSendMessage = function(e, data) { var target = $(e.target); var footerContainer = target.closest(SELECTORS.FOOTER_CONTAINER); var textArea = footerContainer.find(SELECTORS.MESSAGE_TEXT_AREA); var text = textArea.val().trim(); if (text !== '') { sendMessage(text); textArea.val(''); textArea.focus(); } data.originalEvent.preventDefault(); }; /** * Select message event handler. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleSelectMessage = function(e, data) { var selection = window.getSelection(); var target = $(e.target); if (selection.toString() != '') { // Bail if we're selecting. return; } if (target.is('a')) { // Clicking on a link in the message so ignore it. return; } var element = target.closest(SELECTORS.MESSAGE); var messageId = element.attr('data-message-id'); toggleSelectMessage(messageId); data.originalEvent.preventDefault(); }; /** * Handle retry sending of message. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleRetrySendMessage = function(e, data) { var target = $(e.target); var element = target.closest(SELECTORS.MESSAGE); var messageId = element.attr('data-message-id'); var messages = viewState.messages.filter(function(message) { return message.id == messageId; }); var message = messages.length ? messages[0] : null; if (message) { retrySendMessage(message); } data.originalEvent.preventDefault(); data.originalEvent.stopPropagation(); e.stopPropagation(); }; /** * Cancel edit mode event handler. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleCancelEditMode = function(e, data) { cancelEditMode(); data.originalEvent.preventDefault(); }; /** * Show the view contact page. * * @param {String} namespace Unique identifier for the Routes * @return {Function} View contact handler. */ var generateHandleViewContact = function(namespace) { return function(e, data) { var otherUserId = getOtherUserId(); var otherUser = viewState.members[otherUserId]; MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONTACT, otherUser); data.originalEvent.preventDefault(); }; }; /** * Set this conversation as a favourite. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleSetFavourite = function(e, data) { setFavourite().catch(Notification.exception); data.originalEvent.preventDefault(); }; /** * Unset this conversation as a favourite. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleUnsetFavourite = function(e, data) { unsetFavourite().catch(Notification.exception); data.originalEvent.preventDefault(); }; /** * Show the view group info page. * Set this conversation as muted. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleSetMuted = function(e, data) { setMuted().catch(Notification.exception); data.originalEvent.preventDefault(); }; /** * Unset this conversation as muted. * * @param {Object} e Element this event handler is called on. * @param {Object} data Data for this event. */ var handleUnsetMuted = function(e, data) { unsetMuted().catch(Notification.exception); data.originalEvent.preventDefault(); }; /** * Handle clicking on the checkbox that toggles deleting messages for * all users. * * @param {Object} e Element this event handler is called on. */ var handleDeleteMessagesForAllUsersToggle = function(e) { var newValue = $(e.target).prop('checked'); var newState = StateManager.setDeleteMessagesForAllUsers(viewState, newValue); render(newState); }; /** * Show the view contact page. * * @param {String} namespace Unique identifier for the Routes * @return {Function} View group info handler. */ var generateHandleViewGroupInfo = function(namespace) { return function(e, data) { MessageDrawerRouter.go( namespace, MessageDrawerRoutes.VIEW_GROUP_INFO, { id: viewState.id, name: viewState.name, subname: viewState.subname, imageUrl: viewState.imageUrl, totalMemberCount: viewState.totalMemberCount }, viewState.loggedInUserId ); data.originalEvent.preventDefault(); }; }; /** * Handle clicking on the emoji toggle button. * * @param {Object} e The event * @param {Object} data The custom interaction event data */ var handleToggleEmojiPicker = function(e, data) { var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker); render(newState); data.originalEvent.preventDefault(); }; /** * Handle clicking outside the emoji picker to close it. * * @param {Object} e The event */ var handleCloseEmojiPicker = function(e) { var target = $(e.target); if ( viewState.showEmojiPicker && !target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length && !target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length ) { var newState = StateManager.setShowEmojiPicker(viewState, false); render(newState); } }; /** * Listen to, and handle events for conversations. * * @param {string} namespace The route namespace. * @param {Object} header Conversation header container element. * @param {Object} body Conversation body container element. * @param {Object} footer Conversation footer container element. */ var registerEventListeners = function(namespace, header, body, footer) { var isLoadingMoreMessages = false; var messagesContainer = getMessagesContainer(body); var emojiPickerElement = footer.find(SELECTORS.EMOJI_PICKER); var emojiAutoCompleteContainer = footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER); var messageTextArea = footer.find(SELECTORS.MESSAGE_TEXT_AREA); var headerActivateHandlers = [ [SELECTORS.ACTION_REQUEST_BLOCK, generateConfirmActionHandler(requestBlockUser)], [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)], [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)], [SELECTORS.ACTION_REQUEST_REMOVE_CONTACT, generateConfirmActionHandler(requestRemoveContact)], [SELECTORS.ACTION_REQUEST_DELETE_CONVERSATION, generateConfirmActionHandler(requestDeleteConversation)], [SELECTORS.ACTION_CANCEL_EDIT_MODE, handleCancelEditMode], [SELECTORS.ACTION_VIEW_CONTACT, generateHandleViewContact(namespace)], [SELECTORS.ACTION_VIEW_GROUP_INFO, generateHandleViewGroupInfo(namespace)], [SELECTORS.ACTION_CONFIRM_FAVOURITE, handleSetFavourite], [SELECTORS.ACTION_CONFIRM_MUTE, handleSetMuted], [SELECTORS.ACTION_CONFIRM_UNFAVOURITE, handleUnsetFavourite], [SELECTORS.ACTION_CONFIRM_UNMUTE, handleUnsetMuted] ]; var bodyActivateHandlers = [ [SELECTORS.ACTION_CANCEL_CONFIRM, generateConfirmActionHandler(cancelRequest)], [SELECTORS.ACTION_CONFIRM_BLOCK, generateConfirmActionHandler(blockUser)], [SELECTORS.ACTION_CONFIRM_UNBLOCK, generateConfirmActionHandler(unblockUser)], [SELECTORS.ACTION_CONFIRM_ADD_CONTACT, generateConfirmActionHandler(addContact)], [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT, generateConfirmActionHandler(removeContact)], [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(deleteSelectedMessages)], [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION, generateConfirmActionHandler(deleteConversation)], [SELECTORS.ACTION_OKAY_CONFIRM, generateConfirmActionHandler(cancelRequest)], [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)], [SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST, generateConfirmActionHandler(acceptContactRequest)], [SELECTORS.ACTION_DECLINE_CONTACT_REQUEST, generateConfirmActionHandler(declineContactRequest)], [SELECTORS.MESSAGE, handleSelectMessage], [SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE, handleDeleteMessagesForAllUsersToggle], [SELECTORS.RETRY_SEND, handleRetrySendMessage] ]; var footerActivateHandlers = [ [SELECTORS.SEND_MESSAGE_BUTTON, handleSendMessage], [SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON, handleToggleEmojiPicker], [SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(requestDeleteSelectedMessages)], [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)], [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)], ]; AutoRows.init(footer); if (emojiAutoCompleteContainer.length) { initialiseEmojiAutoComplete( emojiAutoCompleteContainer[0], messageTextArea[0], function(hasSuggestions) { var newState = StateManager.setShowEmojiAutoComplete(viewState, hasSuggestions); render(newState); }, function(emoji) { var newState = StateManager.setShowEmojiAutoComplete(viewState, false); render(newState); messageTextArea.focus(); var cursorPos = messageTextArea.prop('selectionStart'); var currentText = messageTextArea.val(); var textBefore = currentText.substring(0, cursorPos).replace(/\S*$/, ''); var textAfter = currentText.substring(cursorPos).replace(/^\S*/, ''); messageTextArea.val(textBefore + emoji + textAfter); // Set the cursor position to after the inserted emoji. messageTextArea.prop('selectionStart', textBefore.length + emoji.length); messageTextArea.prop('selectionEnd', textBefore.length + emoji.length); } ); } if (emojiPickerElement.length) { initialiseEmojiPicker(emojiPickerElement[0], function(emoji) { var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker); render(newState); messageTextArea.focus(); var cursorPos = messageTextArea.prop('selectionStart'); var currentText = messageTextArea.val(); var textBefore = currentText.substring(0, cursorPos); var textAfter = currentText.substring(cursorPos, currentText.length); messageTextArea.val(textBefore + emoji + textAfter); // Set the cursor position to after the inserted emoji. messageTextArea.prop('selectionStart', cursorPos + emoji.length); messageTextArea.prop('selectionEnd', cursorPos + emoji.length); }); } CustomEvents.define(header, [ CustomEvents.events.activate ]); CustomEvents.define(body, [ CustomEvents.events.activate ]); CustomEvents.define(footer, [ CustomEvents.events.activate, CustomEvents.events.enter, CustomEvents.events.escape ]); CustomEvents.define(messagesContainer, [ CustomEvents.events.scrollTop, CustomEvents.events.scrollLock ]); messagesContainer.on(CustomEvents.events.scrollTop, function(e, data) { var hasMembers = Object.keys(viewState.members).length > 1; if (!isResetting && !isLoadingMoreMessages && !hasLoadedAllMessages() && hasMembers) { isLoadingMoreMessages = true; var newState = StateManager.setLoadingMessages(viewState, true); render(newState); loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, []) .then(function() { isLoadingMoreMessages = false; setMessagesOffset(getMessagesOffset() + LOAD_MESSAGE_LIMIT); return; }) .catch(function(error) { isLoadingMoreMessages = false; Notification.exception(error); }); } data.originalEvent.preventDefault(); }); headerActivateHandlers.forEach(function(handler) { var selector = handler[0]; var handlerFunction = handler[1]; header.on(CustomEvents.events.activate, selector, handlerFunction); }); bodyActivateHandlers.forEach(function(handler) { var selector = handler[0]; var handlerFunction = handler[1]; body.on(CustomEvents.events.activate, selector, handlerFunction); }); footerActivateHandlers.forEach(function(handler) { var selector = handler[0]; var handlerFunction = handler[1]; footer.on(CustomEvents.events.activate, selector, handlerFunction); }); footer.on(CustomEvents.events.enter, SELECTORS.MESSAGE_TEXT_AREA, function(e, data) { var enterToSend = footer.attr('data-enter-to-send'); if (enterToSend && enterToSend != 'false' && enterToSend != '0') { handleSendMessage(e, data); } }); footer.on(CustomEvents.events.escape, SELECTORS.EMOJI_PICKER_CONTAINER, handleToggleEmojiPicker); $(document.body).on('click', handleCloseEmojiPicker); PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED, function(newRouteData) { if (newMessagesPollTimer) { if (newRouteData.route != MessageDrawerRoutes.VIEW_CONVERSATION) { newMessagesPollTimer.stop(); } } }); }; /** * Reset the timer that polls for new messages. * * @param {Number} conversationId The conversation id */ var resetMessagePollTimer = function(conversationId) { if (newMessagesPollTimer) { newMessagesPollTimer.stop(); } newMessagesPollTimer = new BackOffTimer( getLoadNewMessagesCallback(conversationId, NEWEST_FIRST), BackOffTimer.getIncrementalCallback( viewState.messagePollMin * MILLISECONDS_IN_SEC, MILLISECONDS_IN_SEC, viewState.messagePollMax * MILLISECONDS_IN_SEC, viewState.messagePollAfterMax * MILLISECONDS_IN_SEC ) ); newMessagesPollTimer.start(); }; /** * Reset the state to the initial state and render the UI. * * @param {Object} body Conversation body container element. * @param {Number|null} conversationId The conversation id. * @param {Object} loggedInUserProfile The logged in user's profile. */ var resetState = function(body, conversationId, loggedInUserProfile) { // Reset all of the states back to the beginning if we're loading a new // conversation. if (newMessagesPollTimer) { newMessagesPollTimer.stop(); } loadedAllMessages = false; messagesOffset = 0; newMessagesPollTimer = null; isRendering = false; renderBuffer = []; isResetting = true; isSendingMessage = false; isDeletingConversationContent = false; sendMessageBuffer = []; var loggedInUserId = loggedInUserProfile.id; var midnight = parseInt(body.attr('data-midnight'), 10); var messagePollMin = parseInt(body.attr('data-message-poll-min'), 10); var messagePollMax = parseInt(body.attr('data-message-poll-max'), 10); var messagePollAfterMax = parseInt(body.attr('data-message-poll-after-max'), 10); var initialState = StateManager.buildInitialState( midnight, loggedInUserId, conversationId, messagePollMin, messagePollMax, messagePollAfterMax ); if (!viewState) { viewState = initialState; } render(initialState); }; /** * Load a new empty private conversation between two users or self-conversation. * * @param {Object} body Conversation body container element. * @param {Object} loggedInUserProfile The logged in user's profile. * @param {Int} otherUserId The other user's id. * @return {Promise} Renderer promise. */ var resetNoConversation = function(body, loggedInUserProfile, otherUserId) { // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. resetState(body, null, loggedInUserProfile); var resetNoConversationPromise = null; if (loggedInUserProfile.id != otherUserId) { // Private conversation between two different users. resetNoConversationPromise = Repository.getConversationBetweenUsers( loggedInUserProfile.id, otherUserId, true, true, 0, 0, LOAD_MESSAGE_LIMIT, 0, NEWEST_FIRST ); } else { // Self conversation. resetNoConversationPromise = Repository.getSelfConversation( loggedInUserProfile.id, LOAD_MESSAGE_LIMIT, 0, NEWEST_FIRST ); } return resetNoConversationPromise.then(function(conversation) { // Looks like we have a conversation after all! Let's use that. return resetByConversation(body, conversation, loggedInUserProfile); }) .catch(function() { // Can't find a conversation. Oh well. Just load up a blank one. return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId); }); }; /** * Load new messages into the conversation based on a time interval. * * @param {Object} body Conversation body container element. * @param {Number} conversationId The conversation id. * @param {Object} loggedInUserProfile The logged in user's profile. * @return {Promise} Renderer promise. */ var resetById = function(body, conversationId, loggedInUserProfile) { var cache = null; if (conversationId in stateCache) { cache = stateCache[conversationId]; } // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. resetState(body, conversationId, loggedInUserProfile); var promise = $.Deferred().resolve({}).promise(); if (cache) { // We've seen this conversation before so there is no need to // send any network requests. var newState = cache.state; // Reset some loading states just in case they were left weirdly. newState = StateManager.setLoadingMessages(newState, false); newState = StateManager.setLoadingMembers(newState, false); setMessagesOffset(cache.messagesOffset); setLoadedAllMessages(cache.loadedAllMessages); render(newState); } else { promise = loadNewConversation( conversationId, loggedInUserProfile, LOAD_MESSAGE_LIMIT, 0, NEWEST_FIRST ); } return promise.then(function() { return resetMessagePollTimer(conversationId); }); }; /** * Load new messages into the conversation based on a time interval. * * @param {Object} body Conversation body container element. * @param {Object} conversation The conversation. * @param {Object} loggedInUserProfile The logged in user's profile. * @return {Promise} Renderer promise. */ var resetByConversation = function(body, conversation, loggedInUserProfile) { var cache = null; if (conversation.id in stateCache) { cache = stateCache[conversation.id]; } // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. resetState(body, conversation.id, loggedInUserProfile); var promise = $.Deferred().resolve({}).promise(); if (cache) { // We've seen this conversation before so there is no need to // send any network requests. var newState = cache.state; // Reset some loading states just in case they were left weirdly. newState = StateManager.setLoadingMessages(newState, false); newState = StateManager.setLoadingMembers(newState, false); setMessagesOffset(cache.messagesOffset); setLoadedAllMessages(cache.loadedAllMessages); render(newState); } else { promise = loadExistingConversation( conversation, loggedInUserProfile, LOAD_MESSAGE_LIMIT, NEWEST_FIRST ); } return promise.then(function() { return resetMessagePollTimer(conversation.id); }); }; /** * Setup the conversation page. This is a rather complex function because there are a * few combinations of arguments that can be provided to this function to show the * conversation. * * There are: * 1.) A conversation object with no action or other user id (e.g. from the overview page) * 2.) A conversation id with no action or other user id (e.g. from the contacts page) * 3.) No conversation/id with an action and other other user id. (e.g. from contact page) * * @param {string} namespace The route namespace. * @param {Object} header Conversation header container element. * @param {Object} body Conversation body container element. * @param {Object} footer Conversation footer container element. * @param {Object|Number|null} conversationOrId Conversation or id or null * @param {String} action An action to take on the conversation * @param {Number} otherUserId The other user id for a private conversation * @return {Object} jQuery promise */ var show = function(namespace, header, body, footer, conversationOrId, action, otherUserId) { var conversation = null; var conversationId = null; // Check what we were given to identify the conversation. if (conversationOrId && conversationOrId !== null && typeof conversationOrId == 'object') { conversation = conversationOrId; conversationId = parseInt(conversation.id, 10); } else { conversation = null; conversationId = parseInt(conversationOrId, 10); conversationId = isNaN(conversationId) ? null : conversationId; } if (!conversationId && action && otherUserId) { // If we didn't get a conversation id got a user id then let's see if we've // previously loaded a private conversation with this user. conversationId = getCachedPrivateConversationIdFromUserId(otherUserId); } // Set attributes to aid in the restoration of unsent messages. setConversationAttributes(footer, conversationId, otherUserId); // This is a new conversation if: // 1. We don't already have a state // 2. The given conversation doesn't match the one currently loaded // 3. We have a view state without a conversation id and we weren't given one // but we were given a different other user id. This happens when the user // goes from viewing a user that they haven't yet initialised a conversation // with to viewing a different user that they also haven't initialised a // conversation with. var isNewConversation = !viewState || (viewState.id != conversationId) || (otherUserId && otherUserId != getOtherUserId()); if (!body.attr('data-init')) { // Generate the render function to bind the header, body, and footer // elements to it so that we don't need to pass them around this module. render = generateRenderFunction(header, body, footer, isNewConversation); registerEventListeners(namespace, header, body, footer); body.attr('data-init', true); } if (isNewConversation) { var renderPromise = null; var loggedInUserProfile = getLoggedInUserProfile(body); if (conversation) { renderPromise = resetByConversation(body, conversation, loggedInUserProfile, otherUserId); } else if (conversationId) { renderPromise = resetById(body, conversationId, loggedInUserProfile, otherUserId); } else { renderPromise = resetNoConversation(body, loggedInUserProfile, otherUserId); } return renderPromise .then(function() { isResetting = false; // Focus the first element that can receieve it in the header. header.find(Constants.SELECTORS.CAN_RECEIVE_FOCUS).first().focus(); return; }) .catch(function(error) { isResetting = false; Notification.exception(error); }); } // We're not loading a new conversation so we should reset the poll timer to try to load // new messages. resetMessagePollTimer(conversationId); if (viewState.type == CONVERSATION_TYPES.PRIVATE && action) { // There are special actions that the user can perform in a private (aka 1-to-1) // conversation. var currentOtherUserId = getOtherUserId(); switch (action) { case 'block': return requestBlockUser(currentOtherUserId); case 'unblock': return requestUnblockUser(currentOtherUserId); case 'add-contact': return requestAddContact(currentOtherUserId); case 'remove-contact': return requestRemoveContact(currentOtherUserId); } } // Final fallback to return a promise if we didn't need to do anything. return $.Deferred().resolve().promise(); }; /** * String describing this page used for aria-labels. * * @return {Object} jQuery promise */ var description = function() { return Str.get_string('messagedrawerviewconversation', 'core_message', viewState.name); }; /** * Set some attributes that will help reboot a conversation. * * These attributes aid in the storage and retrieval of unsent messages. * When a conversationid is not present, otheruserid can be used to create a conversation. * * @param {Object} element The element to target. * @param {Number|null} conversationId The conversationid. * @param {Number|null} otherUserId The otheruserid. */ const setConversationAttributes = function(element, conversationId, otherUserId) { element.removeAttr('data-conversation-id'); element.removeAttr('data-other-user-id'); if (conversationId) { element.attr('data-conversation-id', conversationId); } else if (otherUserId) { element.attr('data-other-user-id', otherUserId); } }; return { show: show, description: description }; }); PKB\+ !src/preferences_processor_form.jsnu[// 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 . /** * Manages the processor form on the message preferences page. * * @module core_message/preferences_processor_form * @copyright 2016 Ryan Wyllie * @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; }); PKB\Zmh src/message_drawer_events.jsnu[// 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 . /** * Events for the message drawer. * * @module core_message/message_drawer_events * @copyright 2018 Ryan Wyllie * @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', }; PKB\;z))src/message_drawer_router.jsnu[// 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 . /** * 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 * @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 }; }); PKB\mn&n&$src/message_drawer_lazy_load_list.jsnu[// 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 . /** * Lazy loaded list of items. * * @module core_message/message_drawer_lazy_load_list * @copyright 2018 Ryan Wyllie * @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 }; }); PKB\#src/message_drawer_view_contacts.jsnu[// 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 . /** * Controls the contacts page of the message drawer. * * @module core_message/message_drawer_view_contacts * @copyright 2018 Ryan Wyllie * @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[0].addEventListener('show.bs.tab', function() { ContactsSection.show(contactsSection); }); showRequestsAction[0].addEventListener('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 }; }); PKB\{,K . .#src/message_drawer_view_settings.jsnu[// 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 . /** * Controls the settings page in the message drawer. * * @module core_message/message_drawer_view_settings * @copyright 2018 Ryan Wyllie * @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, }; }); PKB\h5/src/message_drawer_view_conversation_patcher.jsnu[// 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 . /** * This module will take 2 view states from the message_drawer_view_conversation * module and generate a patch that can be given to the * message_drawer_view_conversation_renderer module to update the UI. * * This module should never modify either state. It's purely a read only * module. * * @module core_message/message_drawer_view_conversation_patcher * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/user_date', 'core_message/message_drawer_view_conversation_constants' ], function( $, UserDate, Constants ) { /** * Sort messages by day. * * @param {Array} messages The list of messages to sort. * @param {Number} midnight User's midnight timestamp. * @return {Array} messages sorted by day. */ var sortMessagesByDay = function(messages, midnight) { var messagesByDay = messages.reduce(function(carry, message) { var timeCreated = message.timeCreated ? message.timeCreated : midnight; var dayTimestamp = UserDate.getUserMidnightForTimestamp(timeCreated, midnight); if (carry.hasOwnProperty(dayTimestamp)) { carry[dayTimestamp].push(message); } else { carry[dayTimestamp] = [message]; } return carry; }, {}); return Object.keys(messagesByDay).map(function(dayTimestamp) { return { timestamp: dayTimestamp, messages: messagesByDay[dayTimestamp] }; }); }; /** * Diff 2 arrays using a match function * * @param {Array} a The first array. * @param {Array} b The second array. * @param {Function} matchFunction Function used for matching array items. * @return {Object} Object containing array items missing from a, array items missing from b * and matches */ var diffArrays = function(a, b, matchFunction) { // Make copy of it. b = b.slice(); var missingFromA = []; var missingFromB = []; var matches = []; a.forEach(function(current) { var found = false; var index = 0; for (; index < b.length; index++) { var next = b[index]; if (matchFunction(current, next)) { found = true; matches.push({ a: current, b: next }); break; } } if (found) { // This day has been processed so removed it from the list. b.splice(index, 1); } else { // If we couldn't find it in the next messages then it means // it needs to be added. missingFromB.push(current); } }); missingFromA = b; return { missingFromA: missingFromA, missingFromB: missingFromB, matches: matches }; }; /** * Find an element in a array based on a matching function. * * @param {array} array Array to search. * @param {Function} breakFunction Function to run on array item. * @return {*} The array item. */ var findPositionInArray = function(array, breakFunction) { var before = null; for (var i = 0; i < array.length; i++) { var candidate = array[i]; if (breakFunction(candidate)) { return candidate; } } return before; }; /** * Check if 2 arrays are equal. * * @param {Array} a The first array. * @param {Array} b The second array. * @return {Boolean} Are arrays equal. */ var isArrayEqual = function(a, b) { // Make shallow copies so that we don't mess with the array sorting. a = a.slice(); b = b.slice(); a.sort(); b.sort(); var aLength = a.length; var bLength = b.length; if (aLength < 1 && bLength < 1) { return true; } if (aLength != bLength) { return false; } return a.every(function(item, index) { return item == b[index]; }); }; /** * Do a shallow check to see if two objects appear to be equal. This should * only be used for pretty basic objects. * * @param {Object} a First object to compare. * @param {Object} b Second object to compare * @return {Bool} */ var isObjectEqual = function(a, b) { var aKeys = Object.keys(a); var bKeys = Object.keys(b); if (aKeys.length != bKeys.length) { return false; } return aKeys.every(function(key) { var aVal = a[key]; var bVal = b[key]; var aType = typeof aVal; var bType = typeof bVal; aType = (aVal === null) ? 'null' : aType; bType = (aVal === null) ? 'null' : bType; aType = (aType === 'object' && Array.isArray(aType)) ? 'array' : aType; bType = (bType === 'object' && Array.isArray(bType)) ? 'array' : bType; if (aType !== bType) { return false; } switch (aType) { case 'object': return isObjectEqual(aVal, bVal); case 'array': return isArrayEqual(aVal, bVal); default: return a[key] == b[key]; } }); }; /** * Compare two messages to check if they are equal. This function only checks a subset * of the message properties which we know will change rather than all properties. * * @param {Object} a The first message * @param {Object} b The second message * @return {Bool} */ var isMessageEqual = function(a, b) { return isObjectEqual( { id: a.id, state: a.sendState, text: a.text, timeCreated: a.timeCreated }, { id: b.id, state: b.sendState, text: b.text, timeCreated: b.timeCreated } ); }; /** * Build a patch based on days. * * @param {Object} current Current list current items. * @param {Array} remove List of days to remove. * @param {Array} add List of days to add. * @return {Object} Patch with elements to add and remove. */ var buildDaysPatch = function(current, remove, add) { return { remove: remove, add: add.map(function(day) { // Any days left over in the "next" list weren't in the "current" list // so they will need to be added. var before = findPositionInArray(current, function(candidate) { return day.timestamp < candidate.timestamp; }); return { before: before, value: day }; }) }; }; /** * Build the messages patch for each day. * * @param {Array} matchingDays Array of old and new messages sorted by day. * @return {Object} patch. */ var buildMessagesPatch = function(matchingDays) { var remove = []; var add = []; var update = []; // Iterate over the list of days and determine which messages in those days // have been changed. matchingDays.forEach(function(days) { var dayCurrent = days.a; var dayNext = days.b; // Find out which messages have changed in this day. This will return a list of messages // from the current state that couldn't be found in the next state and a list of messages in // the next state which couldn't be count in the current state. var messagesDiff = diffArrays(dayCurrent.messages, dayNext.messages, isMessageEqual); // Take the two arrays (list of messages changed from dayNext and list of messages changed // from dayCurrent) any work out which messages have been added/removed from the list and // which messages were just updated. var patch = diffArrays( // The messages from dayCurrent.message that weren't in dayNext.messages. messagesDiff.missingFromB, // The messages from dayNext.message that weren't in dayCurrent.messages. messagesDiff.missingFromA, function(a, b) { // This function is going to determine if the messages were // added/removed from either list or if they were simply an updated. // // If the IDs match or it was a state change (i.e. message with a temp // ID goes from pending to sent and receives an actual id) then they are // the same message which should be an update not an add/remove. return a.id == b.id || (a.sendState != b.sendState && a.timeAdded == b.timeAdded); } ); // Any messages from the current state for this day which aren't in the next state // for this day (i.e. the user deleted the message) means we need to remove them from // the UI. remove = remove.concat(patch.missingFromB); // Any messages not in the current state for this day which are in the next state // for this day (i.e. it's a new message) means we need to add it to the UI so work // out where in the list of messages it should appear (it could be a new message the // user has sent or older messages loaded as part of the conversation scroll back). patch.missingFromA.forEach(function(message) { // By default a null value for before will render the message at the bottom of // the message UI (i.e. it's the newest message). var before = null; if (message.timeCreated) { // If this message has a time created then find where it sits in the list of // message to insert it into the correct position. before = findPositionInArray(dayCurrent.messages, function(candidate) { if (message.timeCreated == candidate.timeCreated) { return message.id < candidate.id; } else { return message.timeCreated < candidate.timeCreated; } }); } add.push({ before: before, value: message, day: dayCurrent }); }); // Any message that appears in both the current state for this day and the next state // for this day means something in the message was updated. update = update.concat(patch.matches.map(function(message) { return { before: message.a, after: message.b }; })); }); return { add: add, remove: remove, update: update }; }; /** * Build a patch for this conversation. * * @param {Object} state The current state of this conversation. * @param {Object} newState The new state of this conversation. * @returns {Object} Patch with days and messsages for each day. */ var buildConversationPatch = function(state, newState) { var diff = diffArrays(state.messages, newState.messages, isMessageEqual); if (diff.missingFromA.length || diff.missingFromB.length) { // Some messages have changed so let's work out which ones by sorting // them into their respective days. var current = sortMessagesByDay(state.messages, state.midnight); var next = sortMessagesByDay(newState.messages, newState.midnight); // This diffs the arrays to work out if there are any missing days that need // to be added (i.e. we've got some new messages on a new day) or if there // are any days that need to be deleted (i.e. the user has deleted some old messages). var daysDiff = diffArrays(current, next, function(dayCurrent, dayNext) { return dayCurrent.timestamp == dayNext.timestamp; }); return { // Handle adding or removing whole days. days: buildDaysPatch(current, daysDiff.missingFromB, daysDiff.missingFromA), // Handle updating messages that don't require adding/removing a whole day. messages: buildMessagesPatch(daysDiff.matches) }; } else { return null; } }; /** * Build a patch for the header of this conversation. Check if this conversation * is a group conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} patch */ var buildHeaderPatchTypePrivate = function(state, newState) { var requireAddContact = buildRequireAddContact(state, newState); var confirmContactRequest = buildConfirmContactRequest(state, newState); var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); var requiresAddContact = requireAddContact && requireAddContact.show && !requireAddContact.hasMessages; var requiredAddContact = requireAddContact && !requireAddContact.show; // Render the header once we've got a user. var shouldRenderHeader = !oldOtherUser && newOtherUser; // We should also re-render the header if the other user requires // being added as a contact or if they did but no longer do. shouldRenderHeader = shouldRenderHeader || requiresAddContact || requiredAddContact; // Finally, we should re-render if the other user has sent this user // a contact request that is waiting for approval or if it's been approved/declined. shouldRenderHeader = shouldRenderHeader || confirmContactRequest !== null; if (shouldRenderHeader) { return { type: Constants.CONVERSATION_TYPES.PRIVATE, // We can show controls if the other user doesn't require add contact // and we aren't waiting for this user to respond to a contact request. showControls: !requiresAddContact && !confirmContactRequest, context: { id: newState.id, name: newState.name, subname: newState.subname, totalmembercount: newState.totalMemberCount, imageurl: newState.imageUrl, isfavourite: newState.isFavourite, ismuted: newState.isMuted, // Don't show favouriting if we don't have a conversation. showfavourite: newState.id !== null, userid: newOtherUser.id, showonlinestatus: newOtherUser.showonlinestatus, isonline: newOtherUser.isonline, isblocked: newOtherUser.isblocked, iscontact: newOtherUser.iscontact, cancreatecontact: newOtherUser.cancreatecontact, } }; } return null; }; /** * Build a patch for the header of this conversation. Check if this conversation * is a group conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} patch */ var buildHeaderPatchTypeSelf = function(state, newState) { var shouldRenderHeader = (state.name === null && newState.name !== null); if (shouldRenderHeader) { return { type: Constants.CONVERSATION_TYPES.SELF, // Don't display the controls for the self-conversations. showControls: false, context: { id: newState.id, name: newState.name, subname: newState.subname, imageurl: newState.imageUrl, isfavourite: newState.isFavourite, // Don't show favouriting if we don't have a conversation. showfavourite: newState.id !== null, showonlinestatus: true, } }; } return null; }; /** * Build a patch for the header of this conversation. Check if this conversation * is a group conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} patch */ var buildHeaderPatchTypePublic = function(state, newState) { var oldMemberCount = state.totalMemberCount; var newMemberCount = newState.totalMemberCount; if (oldMemberCount != newMemberCount) { return { type: Constants.CONVERSATION_TYPES.PUBLIC, showControls: true, context: { id: newState.id, name: newState.name, subname: newState.subname, totalmembercount: newState.totalMemberCount, imageurl: newState.imageUrl, isfavourite: newState.isFavourite, ismuted: newState.isMuted, // Don't show favouriting if we don't have a conversation. showfavourite: newState.id !== null } }; } else { return null; } }; /** * Find the newest or oldest message. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Number} Oldest or newest message id. */ var buildScrollToMessagePatch = function(state, newState) { var oldMessages = state.messages; var newMessages = newState.messages; if (newMessages.length < 1) { return null; } if (oldMessages.length < 1) { return newMessages[newMessages.length - 1].id; } var previousNewest = oldMessages[state.messages.length - 1]; var currentNewest = newMessages[newMessages.length - 1]; var previousOldest = oldMessages[0]; var currentOldest = newMessages[0]; if (previousNewest.id != currentNewest.id) { return currentNewest.id; } else if (previousOldest.id != currentOldest.id) { return previousOldest.id; } return null; }; /** * Check if members should be loaded. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildLoadingMembersPatch = function(state, newState) { if (!state.loadingMembers && newState.loadingMembers) { return true; } else if (state.loadingMembers && !newState.loadingMembers) { return false; } else { return null; } }; /** * Check if the messages are being loaded for the first time. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildLoadingFirstMessages = function(state, newState) { if (state.hasTriedToLoadMessages === newState.hasTriedToLoadMessages) { return null; } else if (!newState.hasTriedToLoadMessages && newState.loadingMessages) { return true; } else if (newState.hasTriedToLoadMessages && !newState.loadingMessages) { return false; } else { return null; } }; /** * Check if the messages are still being loaded * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildLoadingMessages = function(state, newState) { if (!state.loadingMessages && newState.loadingMessages) { return true; } else if (state.loadingMessages && !newState.loadingMessages) { return false; } else { return null; } }; /** * Determine if we should show the emoji picker. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildShowEmojiPicker = function(state, newState) { if (!state.showEmojiPicker && newState.showEmojiPicker) { return true; } else if (state.showEmojiPicker && !newState.showEmojiPicker) { return false; } else { return null; } }; /** * Determine if we should show the emoji auto complete. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildShowEmojiAutoComplete = function(state, newState) { if (!state.showEmojiAutoComplete && newState.showEmojiAutoComplete) { return true; } else if (state.showEmojiAutoComplete && !newState.showEmojiAutoComplete) { return false; } else { return null; } }; /** * Get the user Object of user to be blocked if pending. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object|Bool|Null} User Object if Object. */ var buildConfirmBlockUser = function(state, newState) { if (newState.pendingBlockUserIds.length) { // We currently only support a single user; var userId = newState.pendingBlockUserIds[0]; return newState.members[userId]; } else if (state.pendingBlockUserIds.length) { return false; } return null; }; /** * Get the user Object of user to be unblocked if pending. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object|Bool|Null} User Object if Object. */ var buildConfirmUnblockUser = function(state, newState) { if (newState.pendingUnblockUserIds.length) { // We currently only support a single user; var userId = newState.pendingUnblockUserIds[0]; return newState.members[userId]; } else if (state.pendingUnblockUserIds.length) { return false; } return null; }; /** * Get the user Object of user to be added as contact if pending. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object|Bool|Null} User Object if Object. */ var buildConfirmAddContact = function(state, newState) { if (newState.pendingAddContactIds.length) { // We currently only support a single user; var userId = newState.pendingAddContactIds[0]; return newState.members[userId]; } else if (state.pendingAddContactIds.length) { return false; } return null; }; /** * Get the user Object of user to be removed as contact if pending. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object|Bool|Null} User Object if Object. */ var buildConfirmRemoveContact = function(state, newState) { if (newState.pendingRemoveContactIds.length) { // We currently only support a single user; var userId = newState.pendingRemoveContactIds[0]; return newState.members[userId]; } else if (state.pendingRemoveContactIds.length) { return false; } return null; }; /** * Check if there are any messages to be deleted. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object|Null} The conversation type and if the user can delete the messages for all users. */ var buildConfirmDeleteSelectedMessages = function(state, newState) { var oldPendingCount = state.pendingDeleteMessageIds.length; var newPendingCount = newState.pendingDeleteMessageIds.length; if (newPendingCount && !oldPendingCount) { return { show: true, type: newState.type, canDeleteMessagesForAllUsers: newState.canDeleteMessagesForAllUsers }; } else if (oldPendingCount && !newPendingCount) { return { show: false }; } return null; }; /** * Check if there is a conversation to be deleted. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {int|Null} The conversation type to be deleted. */ var buildConfirmDeleteConversation = function(state, newState) { if (!state.pendingDeleteConversation && newState.pendingDeleteConversation) { return newState.type; } else if (state.pendingDeleteConversation && !newState.pendingDeleteConversation) { return false; } return null; }; /** * Check if there is a pending contact request to accept or decline. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildConfirmContactRequest = function(state, newState) { var loggedInUserId = state.loggedInUserId; var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); var oldReceivedRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) { return request.requesteduserid == loggedInUserId && request.userid == oldOtherUser.id; }); var newReceivedRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) { return request.requesteduserid == loggedInUserId && request.userid == newOtherUser.id; }); var oldRequest = oldReceivedRequests.length ? oldReceivedRequests[0] : null; var newRequest = newReceivedRequests.length ? newReceivedRequests[0] : null; if (!oldRequest && newRequest) { return newOtherUser; } else if (oldRequest && !newRequest) { return false; } else { return null; } }; /** * Check if there are any changes in blocked users. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildIsBlocked = function(state, newState) { var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); if (!oldOtherUser && !newOtherUser) { return null; } else if (!oldOtherUser && newOtherUser) { return newOtherUser.isblocked ? true : null; } else if (!newOtherUser && oldOtherUser) { return oldOtherUser.isblocked ? false : null; } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) { return false; } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) { return true; } else { return null; } }; /** * Check if there are any changes the conversation favourite state. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildIsFavourite = function(state, newState) { var oldIsFavourite = state.isFavourite; var newIsFavourite = newState.isFavourite; if (state.id === null && newState.id === null) { // The conversation isn't yet created so don't change anything. return null; } else if (state.id === null && newState.id !== null) { // The conversation was created so we can show the add favourite button. return 'show-add'; } else if (state.id !== null && newState.id === null) { // We're changing from a created conversation to a new conversation so hide // the favouriting functionality for now. return 'hide'; } else if (oldIsFavourite == newIsFavourite) { // No change. return null; } else if (!oldIsFavourite && newIsFavourite) { return 'show-remove'; } else if (oldIsFavourite && !newIsFavourite) { return 'show-add'; } else { return null; } }; /** * Check if there are any changes the conversation muted state. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {string|null} */ var buildIsMuted = function(state, newState) { var oldIsMuted = state.isMuted; var newIsMuted = newState.isMuted; if (state.id === null && newState.id === null) { // The conversation isn't yet created so don't change anything. return null; } else if (state.id === null && newState.id !== null) { // The conversation was created so we can show the mute button. return 'show-mute'; } else if (state.id !== null && newState.id === null) { // We're changing from a created conversation to a new conversation so hide // the muting functionality for now. return 'hide'; } else if (oldIsMuted == newIsMuted) { // No change. return null; } else if (!oldIsMuted && newIsMuted) { return 'show-unmute'; } else if (oldIsMuted && !newIsMuted) { return 'show-mute'; } else { return null; } }; /** * Check if there are any changes in the contact status of the current user * and other user. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildIsContact = function(state, newState) { var loggedInUserId = state.loggedInUserId; var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); var oldContactRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) { return (request.userid == loggedInUserId && request.requesteduserid == oldOtherUser.id) || (request.userid == oldOtherUser.id && request.requesteduserid == loggedInUserId); }); var newContactRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) { return (request.userid == loggedInUserId && request.requesteduserid == newOtherUser.id) || (request.userid == newOtherUser.id && request.requesteduserid == loggedInUserId); }); var oldHasContactRequests = oldContactRequests.length > 0; var newHasContactRequests = newContactRequests.length > 0; if (!oldOtherUser && !newOtherUser) { return null; } else if (oldHasContactRequests && newHasContactRequests) { return null; } else if (!oldHasContactRequests && newHasContactRequests && !newOtherUser.iscontact) { return 'pending-contact'; } else if (!oldOtherUser && newOtherUser) { return newOtherUser.iscontact ? 'contact' : null; } else if (!newOtherUser && oldOtherUser) { return oldOtherUser.iscontact ? 'non-contact' : null; } else if (oldOtherUser.iscontact && !newOtherUser.iscontact) { return newHasContactRequests ? 'pending-contact' : 'non-contact'; } else if (!oldOtherUser.iscontact && newOtherUser.iscontact) { return 'contact'; } else { return null; } }; /** * Check if a confirm action is active. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildLoadingConfirmationAction = function(state, newState) { if (!state.loadingConfirmAction && newState.loadingConfirmAction) { return true; } else if (state.loadingConfirmAction && !newState.loadingConfirmAction) { return false; } else { return null; } }; /** * Check if a edit mode is active. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildInEditMode = function(state, newState) { var oldHasSelectedMessages = state.selectedMessageIds.length > 0; var newHasSelectedMessages = newState.selectedMessageIds.length > 0; var numberOfMessagesHasChanged = state.messages.length != newState.messages.length; if (!oldHasSelectedMessages && newHasSelectedMessages) { return true; } else if (oldHasSelectedMessages && !newHasSelectedMessages) { return false; } else if (oldHasSelectedMessages && numberOfMessagesHasChanged) { return true; } else { return null; } }; /** * Build a patch for the messages selected. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} patch */ var buildSelectedMessages = function(state, newState) { var oldSelectedMessages = state.selectedMessageIds; var newSelectedMessages = newState.selectedMessageIds; if (isArrayEqual(oldSelectedMessages, newSelectedMessages)) { return null; } var diff = diffArrays(oldSelectedMessages, newSelectedMessages, function(a, b) { return a == b; }); return { count: newSelectedMessages.length, add: diff.missingFromA, remove: diff.missingFromB }; }; /** * Get a list of users from the state that are not the logged in user. Use to find group * message members or the other user in a conversation. * * @param {Object} state State * @return {Array} List of users. */ var getOtherUserFromState = function(state) { return Object.keys(state.members).reduce(function(carry, userId) { if (userId != state.loggedInUserId && !carry) { carry = state.members[userId]; } return carry; }, null); }; /** * Check if the given user requires a contact request from the logged in user. * * @param {Integer} loggedInUserId The logged in user id * @param {Object} user User record * @return {Bool} */ var requiresContactRequest = function(loggedInUserId, user) { // If a user can message then no contact request is required. if (user.canmessage) { return false; } var contactRequests = user.contactrequests.filter(function(request) { return request.userid == loggedInUserId || request.requesteduserid; }); var hasSentContactRequest = contactRequests.length > 0; return user.requirescontact && !user.iscontact && !hasSentContactRequest; }; /** * Check if other users are required to be added as contact. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} Object controlling the required to add contact dialog variables. */ var buildRequireAddContact = function(state, newState) { var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); var hadMessages = state.messages.length > 0; var hasMessages = newState.messages.length > 0; var loggedInUserId = newState.loggedInUserId; var prevRequiresContactRequest = oldOtherUser && requiresContactRequest(loggedInUserId, oldOtherUser); var nextRequiresContactRequest = newOtherUser && requiresContactRequest(loggedInUserId, newOtherUser); var confirmAddContact = buildConfirmAddContact(state, newState); var finishedAddContact = confirmAddContact === false; // Still doing first load. if (!state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) { return null; } // No users yet. if (!oldOtherUser && !newOtherUser) { return null; } // We've loaded a new user and they require a contact request. if (!oldOtherUser && nextRequiresContactRequest) { return { show: true, hasMessages: hasMessages, user: newOtherUser }; } // The logged in user has completed the confirm contact request dialogue // but the other user still requires a contact request which means the logged // in user either declined the confirmation or it failed. if (finishedAddContact && nextRequiresContactRequest) { return { show: true, hasMessages: hasMessages, user: newOtherUser }; } // Everything is loaded. if (state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) { if (!prevRequiresContactRequest && nextRequiresContactRequest) { return { show: true, hasMessages: hasMessages, user: newOtherUser }; } if (prevRequiresContactRequest && !nextRequiresContactRequest) { return { show: false, hasMessages: hasMessages }; } } // First load just completed. if (!state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) { if (nextRequiresContactRequest) { return { show: true, hasMessages: hasMessages, user: newOtherUser }; } } // Being reset. if (state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) { if (prevRequiresContactRequest) { return { show: false, hasMessages: hadMessages }; } } return null; }; /** * Check if other users are required to be unblocked. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildRequireUnblock = function(state, newState) { var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); if (!oldOtherUser && !newOtherUser) { return null; } else if (oldOtherUser && !newOtherUser) { return oldOtherUser.isblocked ? false : null; } else if (!oldOtherUser && newOtherUser) { return newOtherUser.isblocked ? true : null; } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) { return true; } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) { return false; } return null; }; /** * Check if other users can be messaged. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Bool|Null} */ var buildUnableToMessage = function(state, newState) { var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); if (newState.type == Constants.CONVERSATION_TYPES.SELF) { // Users always can send message themselves on self-conversations. return null; } if (!oldOtherUser && !newOtherUser) { return null; } else if (oldOtherUser && !newOtherUser) { return oldOtherUser.canmessage ? null : true; } else if (!oldOtherUser && newOtherUser) { return newOtherUser.canmessage ? null : true; } else if (!oldOtherUser.canmessage && newOtherUser.canmessage) { return false; } else if (oldOtherUser.canmessage && !newOtherUser.canmessage) { return true; } return null; }; /** * Build patch for footer information for a private conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} containing footer state type. */ var buildFooterPatchTypePrivate = function(state, newState) { var loadingFirstMessages = buildLoadingFirstMessages(state, newState); var inEditMode = buildInEditMode(state, newState); var requireAddContact = buildRequireAddContact(state, newState); var requireUnblock = buildRequireUnblock(state, newState); var unableToMessage = buildUnableToMessage(state, newState); var showRequireAddContact = requireAddContact !== null ? requireAddContact.show && requireAddContact.hasMessages : null; var otherUser = getOtherUserFromState(newState); var generateReturnValue = function(checkValue, successReturn) { if (checkValue) { return successReturn; } else if (checkValue !== null && !checkValue) { if (!otherUser) { return {type: 'content'}; } else if (otherUser.isblocked) { return {type: 'unblock'}; } else if (newState.messages.length && requiresContactRequest(newState.loggedInUserId, otherUser)) { return { type: 'add-contact', user: otherUser }; } else if (!otherUser.canmessage && (otherUser.requirescontact && !otherUser.iscontact)) { return {type: 'unable-to-message'}; } } return null; }; if ( loadingFirstMessages === null && inEditMode === null && requireAddContact === null && requireUnblock === null ) { return null; } var checks = [ [loadingFirstMessages, {type: 'placeholder'}], [inEditMode, {type: 'edit-mode'}], [unableToMessage, {type: 'unable-to-message'}], [requireUnblock, {type: 'unblock'}], [showRequireAddContact, {type: 'add-contact', user: otherUser}] ]; for (var i = 0; i < checks.length; i++) { var checkValue = checks[i][0]; var successReturn = checks[i][1]; var result = generateReturnValue(checkValue, successReturn); if (result !== null) { return result; } } return { type: 'content' }; }; /** * Build patch for footer information for a public conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} containing footer state type. */ var buildFooterPatchTypePublic = function(state, newState) { var loadingFirstMessages = buildLoadingFirstMessages(state, newState); var inEditMode = buildInEditMode(state, newState); if (loadingFirstMessages === null && inEditMode === null) { return null; } if (loadingFirstMessages) { return {type: 'placeholder'}; } if (inEditMode) { return {type: 'edit-mode'}; } return { type: 'content' }; }; /** * Check if we're viewing a different conversation. If so then we need to * reset the UI. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {bool|null} If a reset needs to occur */ var buildReset = function(state, newState) { var oldType = state.type; var newType = newState.type; var oldConversationId = state.id; var newConversationId = newState.id; var oldMemberIds = Object.keys(state.members); var newMemberIds = Object.keys(newState.members); oldMemberIds.sort(); newMemberIds.sort(); var membersUnchanged = oldMemberIds.every(function(id, index) { return id == newMemberIds[index]; }); if (oldType != newType) { // If we've changed conversation type then we need to reset. return true; } else if (oldConversationId && !newConversationId) { // We previously had a conversation id but no longer do. This likely means // the user is viewing the conversation with someone they've never spoken to // before. return true; } else if (oldConversationId && newConversationId && oldConversationId != newConversationId) { // If we had a conversation id and it's changed then we need to reset. return true; } else if (!oldConversationId && !newConversationId && !membersUnchanged) { // If we never had a conversation id but the members of the conversation have // changed then we need to reset. This can happen if the user goes from viewing // a user they've never had a conversation with to viewing a different user that // they've never had a conversation with. return true; } return null; }; /** * We should show this message always, for all the self-conversations. * * The message should be hidden when it's not a self-conversation. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {bool} */ var buildSelfConversationMessage = function(state, newState) { if (state.type != newState.type) { return (newState.type == Constants.CONVERSATION_TYPES.SELF); } return null; }; /** * We should show the contact request sent message if the user just sent * a contact request to the other user * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {string|false|null} */ var buildContactRequestSent = function(state, newState) { var loggedInUserId = newState.loggedInUserId; var oldOtherUser = getOtherUserFromState(state); var newOtherUser = getOtherUserFromState(newState); var oldSentRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) { return request.userid == loggedInUserId; }); var newSentRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) { return request.userid == loggedInUserId; }); var oldRequest = oldSentRequests.length > 0; var newRequest = newSentRequests.length > 0; if (!oldRequest && newRequest && !newOtherUser.iscontact) { return newOtherUser.fullname; } else if (oldOtherUser && !oldOtherUser.iscontact && newRequest && newOtherUser.iscontact) { // Contact request accepted. return false; } else if (oldRequest && !newRequest) { return false; } else { return null; } }; /** * Build the full patch comparing the current state and the new state. This patch is used by * the conversation renderer to render the UI on any update. * * @param {Object} state The current state. * @param {Object} newState The new state. * @return {Object} Patch containing all information changed. */ var buildPatch = function(state, newState) { var config = { all: { reset: buildReset, conversation: buildConversationPatch, scrollToMessage: buildScrollToMessagePatch, loadingMembers: buildLoadingMembersPatch, loadingFirstMessages: buildLoadingFirstMessages, loadingMessages: buildLoadingMessages, confirmDeleteSelectedMessages: buildConfirmDeleteSelectedMessages, inEditMode: buildInEditMode, selectedMessages: buildSelectedMessages, isFavourite: buildIsFavourite, isMuted: buildIsMuted, showEmojiPicker: buildShowEmojiPicker, showEmojiAutoComplete: buildShowEmojiAutoComplete } }; // These build functions are only applicable to private conversations. config[Constants.CONVERSATION_TYPES.PRIVATE] = { header: buildHeaderPatchTypePrivate, footer: buildFooterPatchTypePrivate, confirmBlockUser: buildConfirmBlockUser, confirmUnblockUser: buildConfirmUnblockUser, confirmAddContact: buildConfirmAddContact, confirmRemoveContact: buildConfirmRemoveContact, confirmContactRequest: buildConfirmContactRequest, confirmDeleteConversation: buildConfirmDeleteConversation, isBlocked: buildIsBlocked, isContact: buildIsContact, loadingConfirmAction: buildLoadingConfirmationAction, requireAddContact: buildRequireAddContact, contactRequestSent: buildContactRequestSent }; // These build functions are only applicable to public (group) conversations. config[Constants.CONVERSATION_TYPES.PUBLIC] = { header: buildHeaderPatchTypePublic, footer: buildFooterPatchTypePublic, }; // These build functions are only applicable to self-conversations. config[Constants.CONVERSATION_TYPES.SELF] = { header: buildHeaderPatchTypeSelf, footer: buildFooterPatchTypePublic, confirmDeleteConversation: buildConfirmDeleteConversation, selfConversationMessage: buildSelfConversationMessage }; var patchConfig = $.extend({}, config.all); if (newState.type && newState.type in config) { // Add the type specific builders to the patch config. patchConfig = $.extend(patchConfig, config[newState.type]); } return Object.keys(patchConfig).reduce(function(patch, key) { var buildFunc = patchConfig[key]; var value = buildFunc(state, newState); if (value !== null) { patch[key] = value; } return patch; }, {}); }; return { buildPatch: buildPatch }; }); PKB\66src/notification_preference.jsnu[// 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 . /** * Controls the preference for an individual notification type on the * message preference page. * * @module core_message/notification_preference * @copyright 2016 Ryan Wyllie * @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; }); PKB\))#src/message_drawer_view_overview.jsnu[// 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 . /** * Controls the overview page of the message drawer. * * @module core_message/message_drawer_view_overview * @copyright 2018 Ryan Wyllie * @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-bs-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 }; }); PKB\ͽ3uu1src/message_drawer_view_conversation_constants.jsnu[// 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 . /** * Constant values for the conversation page in the message drawer. * * @module core_message/message_drawer_view_conversation_constants * @copyright 2018 Ryan Wyllie * @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 }; }); PKB\}B)src/message_drawer_routes.jsnu[// 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 . /** * Available routes for the message drawer. * * @module core_message/message_drawer_routes * @copyright 2018 Ryan Wyllie * @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' }; }); PKB\f "src/message_drawer_view_contact.jsnu[// 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 . /** * Controls the contact page in the message drawer. * * @module core_message/message_drawer_view_contact * @copyright 2018 Ryan Wyllie * @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 }; }); PKB\.FBB!src/message_drawer_view_search.jsnu[// 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 . /** * Controls the search page of the message drawer. * * @module core_message/message_drawer_view_search * @copyright 2018 Ryan Wyllie * @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/templates', 'core_message/message_repository', 'core_message/message_drawer_events', ], function( $, CustomEvents, Notification, PubSub, Str, Templates, Repository, Events ) { var MESSAGE_SEARCH_LIMIT = 50; var USERS_SEARCH_LIMIT = 50; var USERS_INITIAL_SEARCH_LIMIT = 3; var SELECTORS = { BLOCK_ICON_CONTAINER: '[data-region="block-icon-container"]', CANCEL_SEARCH_BUTTON: '[data-action="cancel-search"]', CONTACTS_CONTAINER: '[data-region="contacts-container"]', CONTACTS_LIST: '[data-region="contacts-container"] [data-region="list"]', EMPTY_MESSAGE_CONTAINER: '[data-region="empty-message-container"]', LIST: '[data-region="list"]', LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]', LOADING_PLACEHOLDER: '[data-region="loading-placeholder"]', MESSAGES_LIST: '[data-region="messages-container"] [data-region="list"]', MESSAGES_CONTAINER: '[data-region="messages-container"]', NON_CONTACTS_CONTAINER: '[data-region="non-contacts-container"]', NON_CONTACTS_LIST: '[data-region="non-contacts-container"] [data-region="list"]', SEARCH_ICON_CONTAINER: '[data-region="search-icon-container"]', SEARCH_ACTION: '[data-action="search"]', SEARCH_INPUT: '[data-region="search-input"]', SEARCH_RESULTS_CONTAINER: '[data-region="search-results-container"]', LOAD_MORE_USERS: '[data-action="load-more-users"]', LOAD_MORE_MESSAGES: '[data-action="load-more-messages"]', BUTTON_TEXT: '[data-region="button-text"]', NO_RESULTS_CONTAINTER: '[data-region="no-results-container"]', ALL_CONTACTS_CONTAINER: '[data-region="all-contacts-container"]' }; var TEMPLATES = { CONTACTS_LIST: 'core_message/message_drawer_contacts_list', NON_CONTACTS_LIST: 'core_message/message_drawer_non_contacts_list', MESSAGES_LIST: 'core_message/message_drawer_messages_list' }; /** * Get the logged in user id. * * @param {Object} body Search body container element. * @return {Number} User id. */ var getLoggedInUserId = function(body) { return body.attr('data-user-id'); }; /** * Show the no messages container element. * * @param {Object} body Search body container element. * @return {Object} No messages container element. */ var getEmptyMessageContainer = function(body) { return body.find(SELECTORS.EMPTY_MESSAGE_CONTAINER); }; /** * Get the search loading icon. * * @param {Object} header Search header container element. * @return {Object} Loading icon element. */ var getLoadingIconContainer = function(header) { return header.find(SELECTORS.LOADING_ICON_CONTAINER); }; /** * Get the loading container element. * * @param {Object} body Search body container element. * @return {Object} Loading container element. */ var getLoadingPlaceholder = function(body) { return body.find(SELECTORS.LOADING_PLACEHOLDER); }; /** * Get the search icon container. * * @param {Object} header Search header container element. * @return {Object} Search icon container. */ var getSearchIconContainer = function(header) { return header.find(SELECTORS.SEARCH_ICON_CONTAINER); }; /** * Get the search input container. * * @param {Object} header Search header container element. * @return {Object} Search input container. */ var getSearchInput = function(header) { return header.find(SELECTORS.SEARCH_INPUT); }; /** * Get the search results container. * * @param {Object} body Search body container element. * @return {Object} Search results container. */ var getSearchResultsContainer = function(body) { return body.find(SELECTORS.SEARCH_RESULTS_CONTAINER); }; /** * Get the search contacts container. * * @param {Object} body Search body container element. * @return {Object} Search contacts container. */ var getContactsContainer = function(body) { return body.find(SELECTORS.CONTACTS_CONTAINER); }; /** * Get the search non contacts container. * * @param {Object} body Search body container element. * @return {Object} Search non contacts container. */ var getNonContactsContainer = function(body) { return body.find(SELECTORS.NON_CONTACTS_CONTAINER); }; /** * Get the search messages container. * * @param {Object} body Search body container element. * @return {Object} Search messages container. */ var getMessagesContainer = function(body) { return body.find(SELECTORS.MESSAGES_CONTAINER); }; /** * Show the messages empty container. * * @param {Object} body Search body container element. */ var showEmptyMessage = function(body) { getEmptyMessageContainer(body).removeClass('hidden'); }; /** * Hide the messages empty container. * * @param {Object} body Search body container element. */ var hideEmptyMessage = function(body) { getEmptyMessageContainer(body).addClass('hidden'); }; /** * Show the loading icon. * * @param {Object} header Search header container element. */ var showLoadingIcon = function(header) { getLoadingIconContainer(header).removeClass('hidden'); }; /** * Hide the loading icon. * * @param {Object} header Search header container element. */ var hideLoadingIcon = function(header) { getLoadingIconContainer(header).addClass('hidden'); }; /** * Show loading placeholder. * * @param {Object} body Search body container element. */ var showLoadingPlaceholder = function(body) { getLoadingPlaceholder(body).removeClass('hidden'); }; /** * Hide loading placeholder. * * @param {Object} body Search body container element. */ var hideLoadingPlaceholder = function(body) { getLoadingPlaceholder(body).addClass('hidden'); }; /** * Show search icon. * * @param {Object} header Search header container element. */ var showSearchIcon = function(header) { getSearchIconContainer(header).removeClass('hidden'); }; /** * Hide search icon. * * @param {Object} header Search header container element. */ var hideSearchIcon = function(header) { getSearchIconContainer(header).addClass('hidden'); }; /** * Show search results. * * @param {Object} body Search body container element. */ var showSearchResults = function(body) { getSearchResultsContainer(body).removeClass('hidden'); }; /** * Hide search results. * * @param {Object} body Search body container element. */ var hideSearchResults = function(body) { getSearchResultsContainer(body).addClass('hidden'); }; /** * Show the no search results message. * * @param {Object} body Search body container element. */ var showNoSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden'); container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden'); container.find(SELECTORS.NO_RESULTS_CONTAINTER).removeClass('hidden'); }; /** * Hide the no search results message. * * @param {Object} body Search body container element. */ var hideNoSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden'); container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden'); container.find(SELECTORS.NO_RESULTS_CONTAINTER).addClass('hidden'); }; /** * Show the whole contacts results area. * * @param {Object} body Search body container element. */ var showAllContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden'); }; /** * Hide the whole contacts results area. * * @param {Object} body Search body container element. */ var hideAllContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden'); }; /** * Show the contacts results. * * @param {Object} body Search body container element. */ var showContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.CONTACTS_CONTAINER).removeClass('hidden'); }; /** * Hide the contacts results. * * @param {Object} body Search body container element. */ var hideContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.CONTACTS_CONTAINER).addClass('hidden'); }; /** * Show the non contacts results. * * @param {Object} body Search body container element. */ var showNonContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.NON_CONTACTS_CONTAINER).removeClass('hidden'); }; /** * Hide the non contacts results. * * @param {Object} body Search body container element. */ var hideNonContactsSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.NON_CONTACTS_CONTAINER).addClass('hidden'); }; /** * Show the messages results. * * @param {Object} body Search body container element. */ var showMessagesSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden'); }; /** * Hide the messages results. * * @param {Object} body Search body container element. */ var hideMessagesSearchResults = function(body) { var container = getSearchResultsContainer(body); container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden'); }; /** * Disable the search input. * * @param {Object} header Search header container element. */ var disableSearchInput = function(header) { getSearchInput(header).prop('disabled', true); }; /** * Enable the search input. * * @param {Object} header Search header container element. */ var enableSearchInput = function(header) { getSearchInput(header).prop('disabled', false); }; /** * Clear the search input. * * @param {Object} header Search header container element. */ var clearSearchInput = function(header) { getSearchInput(header).val(''); }; /** * Clear all search results * * @param {Object} body Search body container element. */ var clearAllSearchResults = function(body) { body.find(SELECTORS.CONTACTS_LIST).empty(); body.find(SELECTORS.NON_CONTACTS_LIST).empty(); body.find(SELECTORS.MESSAGES_LIST).empty(); hideNoSearchResults(body); showAllContactsSearchResults(body); showContactsSearchResults(body); showNonContactsSearchResults(body); showMessagesSearchResults(body); showLoadMoreUsersButton(body); showLoadMoreMessagesButton(body); }; /** * Update the body and header to indicate the search is loading. * * @param {Object} header Search header container element. * @param {Object} body Search body container element. */ var startLoading = function(header, body) { hideSearchIcon(header); hideEmptyMessage(body); hideSearchResults(body); showLoadingIcon(header); showLoadingPlaceholder(body); disableSearchInput(header); }; /** * Update the body and header to indicate the search has stopped loading. * * @param {Object} header Search header container element. * @param {Object} body Search body container element. */ var stopLoading = function(header, body) { showSearchIcon(header); hideEmptyMessage(body); showSearchResults(body); hideLoadingIcon(header); hideLoadingPlaceholder(body); enableSearchInput(header); }; /** * Show the more users loading icon. * * @param {Object} root The more users container element. */ var showUsersLoadingIcon = function(root) { var button = root.find(SELECTORS.LOAD_MORE_USERS); button.prop('disabled', true); button.find(SELECTORS.BUTTON_TEXT).addClass('hidden'); button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden'); }; /** * Hide the more users loading icon. * * @param {Object} root The more users container element. */ var hideUsersLoadingIcon = function(root) { var button = root.find(SELECTORS.LOAD_MORE_USERS); button.prop('disabled', false); button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden'); button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden'); }; /** * Show the load more users button. * * @param {Object} root The users container element. */ var showLoadMoreUsersButton = function(root) { root.find(SELECTORS.LOAD_MORE_USERS).removeClass('hidden'); }; /** * Hide the load more users button. * * @param {Object} root The users container element. */ var hideLoadMoreUsersButton = function(root) { root.find(SELECTORS.LOAD_MORE_USERS).addClass('hidden'); }; /** * Show the messages are loading icon. * * @param {Object} root Messages root element. */ var showMessagesLoadingIcon = function(root) { var button = root.find(SELECTORS.LOAD_MORE_MESSAGES); button.prop('disabled', true); button.find(SELECTORS.BUTTON_TEXT).addClass('hidden'); button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden'); }; /** * Hide the messages are loading icon. * * @param {Object} root Messages root element. */ var hideMessagesLoadingIcon = function(root) { var button = root.find(SELECTORS.LOAD_MORE_MESSAGES); button.prop('disabled', false); button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden'); button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden'); }; /** * Show the load more messages button. * * @param {Object} root The messages container element. */ var showLoadMoreMessagesButton = function(root) { root.find(SELECTORS.LOAD_MORE_MESSAGES).removeClass('hidden'); }; /** * Hide the load more messages button. * * @param {Object} root The messages container element. */ var hideLoadMoreMessagesButton = function(root) { root.find(SELECTORS.LOAD_MORE_MESSAGES).addClass('hidden'); }; /** * Find a contact in the search results. * * @param {Object} root Search results container element. * @param {Number} userId User id. * @return {Object} User container element. */ var findContact = function(root, userId) { return root.find('[data-contact-user-id="' + userId + '"]'); }; /** * Add a contact to the search results. * * @param {Object} root Search results container. * @param {Object} contact User in contacts list. */ var addContact = function(root, contact) { var nonContactsContainer = getNonContactsContainer(root); var nonContact = findContact(nonContactsContainer, contact.userid); if (nonContact.length) { nonContact.remove(); var contactsContainer = getContactsContainer(root); contactsContainer.removeClass('hidden'); contactsContainer.find(SELECTORS.LIST).append(nonContact); } if (!nonContactsContainer.find(SELECTORS.LIST).children().length) { nonContactsContainer.addClass('hidden'); } }; /** * Remove a contact from the contacts results. * * @param {Object} root Search results container. * @param {Object} userId Contact user id. */ var removeContact = function(root, userId) { var contactsContainer = getContactsContainer(root); var contact = findContact(contactsContainer, userId); if (contact.length) { contact.remove(); var nonContactsContainer = getNonContactsContainer(root); nonContactsContainer.removeClass('hidden'); nonContactsContainer.find(SELECTORS.LIST).append(contact); } if (!contactsContainer.find(SELECTORS.LIST).children().length) { contactsContainer.addClass('hidden'); } }; /** * Show the contact is blocked icon. * * @param {Object} root Search results container. * @param {Object} userId Contact user id. */ var blockContact = function(root, userId) { var contact = findContact(root, userId); if (contact.length) { contact.find(SELECTORS.BLOCK_ICON_CONTAINER).removeClass('hidden'); } }; /** * Hide the contact is blocked icon. * * @param {Object} root Search results container. * @param {Object} userId Contact user id. */ var unblockContact = function(root, userId) { var contact = findContact(root, userId); if (contact.length) { contact.find(SELECTORS.BLOCK_ICON_CONTAINER).addClass('hidden'); } }; /** * Highlight words in search results. * * @param {String} content HTML to search. * @param {String} searchText Search text. * @return {String} searchText with search wrapped in matchtext span. */ var highlightSearch = function(content, searchText) { if (!content) { return ''; } var regex = new RegExp('(' + searchText + ')', 'gi'); return content.replace(regex, '$1'); }; /** * Render contacts in the contacts search results. * * @param {Object} root Search results container. * @param {Array} contacts List of contacts. * @return {Promise} Renderer promise. */ var renderContacts = function(root, contacts) { var container = getContactsContainer(root); var frompanel = root.attr('data-in-panel'); var list = container.find(SELECTORS.LIST); return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: contacts, frompanel: frompanel}) .then(function(html) { list.append(html); return html; }); }; /** * Render non contacts in the contacts search results. * * @param {Object} root Search results container. * @param {Array} nonContacts List of non contacts. * @return {Promise} Renderer promise. */ var renderNonContacts = function(root, nonContacts) { var container = getNonContactsContainer(root); var frompanel = root.attr('data-in-panel'); var list = container.find(SELECTORS.LIST); return Templates.render(TEMPLATES.NON_CONTACTS_LIST, {noncontacts: nonContacts, frompanel: frompanel}) .then(function(html) { list.append(html); return html; }); }; /** * Render messages in the messages search results. * * @param {Object} root Search results container. * @param {Array} messages List of messages. * @return {Promise} Renderer promise. */ var renderMessages = function(root, messages) { var container = getMessagesContainer(root); var frompanel = root.attr('data-in-panel'); var list = container.find(SELECTORS.LIST); return Templates.render(TEMPLATES.MESSAGES_LIST, {messages: messages, frompanel: frompanel}) .then(function(html) { list.append(html); return html; }); }; /** * Load more users from the repository and render the results into the users search results. * * @param {Object} root Search results container. * @param {Number} loggedInUserId Current logged in user. * @param {String} text Search text. * @param {Number} limit Number of users to get. * @param {Number} offset Load users from * @return {Object} jQuery promise */ var loadMoreUsers = function(root, loggedInUserId, text, limit, offset) { var loadedAll = false; showUsersLoadingIcon(root); return Repository.searchUsers(loggedInUserId, text, limit + 1, offset) .then(function(results) { var contacts = results.contacts; var noncontacts = results.noncontacts; if (contacts.length <= limit && noncontacts.length <= limit) { loadedAll = true; return { contacts: contacts, noncontacts: noncontacts }; } else { return { contacts: contacts.slice(0, limit), noncontacts: noncontacts.slice(0, limit) }; } }) .then(function(results) { var contactsCount = results.contacts.length; var nonContactsCount = results.noncontacts.length; if (contactsCount) { results.contacts.forEach(function(contact) { contact.highlight = highlightSearch(contact.fullname, text); }); } if (nonContactsCount) { results.noncontacts.forEach(function(contact) { contact.highlight = highlightSearch(contact.fullname, text); }); } return $.when( contactsCount ? renderContacts(root, results.contacts) : true, nonContactsCount ? renderNonContacts(root, results.noncontacts) : true ) .then(function() { return { contactsCount: contactsCount, nonContactsCount: nonContactsCount }; }); }) .then(function(counts) { hideUsersLoadingIcon(root); if (loadedAll) { hideLoadMoreUsersButton(root); } return counts; }) .catch(function(error) { hideUsersLoadingIcon(root); // Rethrow error for other handlers. throw error; }); }; /** * Load more messages from the repository and render the results into the messages search results. * * @param {Object} root Search results container. * @param {Number} loggedInUserId Current logged in user. * @param {String} text Search text. * @param {Number} limit Number of messages to get. * @param {Number} offset Load messages from * @return {Object} jQuery promise */ var loadMoreMessages = function(root, loggedInUserId, text, limit, offset) { var loadedAll = false; showMessagesLoadingIcon(root); return Repository.searchMessages(loggedInUserId, text, limit + 1, offset) .then(function(results) { var messages = results.contacts; if (messages.length <= limit) { loadedAll = true; return messages; } else { return messages.slice(0, limit); } }) .then(function(messages) { if (messages.length) { messages.forEach(function(message) { message.lastmessage = highlightSearch(message.lastmessage, text); }); return renderMessages(root, messages) .then(function() { return messages.length; }); } else { return messages.length; } }) .then(function(count) { hideMessagesLoadingIcon(root); if (loadedAll) { hideLoadMoreMessagesButton(root); } return count; }) .catch(function(error) { hideMessagesLoadingIcon(root); // Rethrow error for other handlers. throw error; }); }; /** * Search for users and messages. * * @param {Object} header Search header container element. * @param {Object} body Search body container element. * @param {String} searchText Search text. * @param {Number} usersLimit The users limit. * @param {Number} usersOffset The users offset. * @param {Number} messagesLimit The message limit. * @param {Number} messagesOffset The message offset. * @return {Object} jQuery promise */ var search = function(header, body, searchText, usersLimit, usersOffset, messagesLimit, messagesOffset) { var loggedInUserId = getLoggedInUserId(body); startLoading(header, body); clearAllSearchResults(body); return $.when( loadMoreUsers(body, loggedInUserId, searchText, usersLimit, usersOffset), loadMoreMessages(body, loggedInUserId, searchText, messagesLimit, messagesOffset) ) .then(function(userCounts, messagesCount) { var contactsCount = userCounts.contactsCount; var nonContactsCount = userCounts.nonContactsCount; stopLoading(header, body); if (!contactsCount && !nonContactsCount && !messagesCount) { showNoSearchResults(body); } else { if (!contactsCount && !nonContactsCount) { hideAllContactsSearchResults(body); } else { if (!contactsCount) { hideContactsSearchResults(body); } if (!nonContactsCount) { hideNonContactsSearchResults(body); } } if (!messagesCount) { hideMessagesSearchResults(body); } } return; }); }; /** * Listen to and handle events for searching. * * @param {Object} header Search header container element. * @param {Object} body Search body container element. */ var registerEventListeners = function(header, body) { var loggedInUserId = getLoggedInUserId(body); var searchInput = getSearchInput(header); var searchText = ''; var messagesOffset = 0; var usersOffset = 0; var searchEventHandler = function(e, data) { searchText = searchInput.val().trim(); if (searchText !== '') { messagesOffset = 0; usersOffset = 0; search( header, body, searchText, USERS_INITIAL_SEARCH_LIMIT, usersOffset, MESSAGE_SEARCH_LIMIT, messagesOffset ) .then(function() { searchInput.focus(); usersOffset = usersOffset + USERS_INITIAL_SEARCH_LIMIT; messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT; return; }) .catch(Notification.exception); } data.originalEvent.preventDefault(); }; CustomEvents.define(searchInput, [CustomEvents.events.enter]); CustomEvents.define(header, [CustomEvents.events.activate]); CustomEvents.define(body, [CustomEvents.events.activate]); searchInput.on(CustomEvents.events.enter, searchEventHandler); header.on(CustomEvents.events.activate, SELECTORS.SEARCH_ACTION, searchEventHandler); body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_MESSAGES, function(e, data) { if (searchText !== '') { loadMoreMessages(body, loggedInUserId, searchText, MESSAGE_SEARCH_LIMIT, messagesOffset) .then(function() { messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT; return; }) .catch(Notification.exception); } data.originalEvent.preventDefault(); }); body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_USERS, function(e, data) { if (searchText !== '') { loadMoreUsers(body, loggedInUserId, searchText, USERS_SEARCH_LIMIT, usersOffset) .then(function() { usersOffset = usersOffset + USERS_SEARCH_LIMIT; return; }) .catch(Notification.exception); } data.originalEvent.preventDefault(); }); header.on(CustomEvents.events.activate, SELECTORS.CANCEL_SEARCH_BUTTON, function() { clearSearchInput(header); showEmptyMessage(body); showSearchIcon(header); hideSearchResults(body); hideLoadingIcon(header); hideLoadingPlaceholder(body); usersOffset = 0; messagesOffset = 0; }); PubSub.subscribe(Events.CONTACT_ADDED, function(userId) { addContact(body, userId); }); PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) { removeContact(body, userId); }); PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) { blockContact(body, userId); }); PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) { unblockContact(body, userId); }); }; /** * Setup the search page. * * @param {string} namespace The route namespace. * @param {Object} header Contacts header container element. * @param {Object} body Contacts body container element. * @return {Object} jQuery promise */ var show = function(namespace, header, body) { if (!body.attr('data-init')) { registerEventListeners(header, body); body.attr('data-init', true); } var searchInput = getSearchInput(header); searchInput.focus(); return $.Deferred().resolve().promise(); }; /** * String describing this page used for aria-labels. * * @param {string} namespace The route namespace. * @param {Object} header Contacts header container element. * @return {Object} jQuery promise */ var description = function(namespace, header) { if (typeof header !== 'object') { return Str.get_string('messagedrawerviewsearch', 'core_message'); } var searchInput = getSearchInput(header); var searchText = searchInput.val().trim(); return Str.get_string('messagedrawerviewsearch', 'core_message', searchText); }; return { show: show, description: description }; }); PKB\}+iW W src/message_popover.jsnu[// 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 . /** * Controls the message popover in the nav bar. * * @module core_message/message_popover * @copyright 2018 Ryan Wyllie * @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')); 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, }; }); PKB\j7pcpcsrc/message_drawer.jsnu[// 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 . /** * Controls the message drawer. * * @module core_message/message_drawer * @copyright 2018 Ryan Wyllie * @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', 'core/toast', 'core/str', 'core/config', 'core/ajax', 'core/local/aria/focuslock', 'core/modal_backdrop', 'core/templates', 'core/local/aria/selectors', ], function( $, CustomEvents, PubSub, ViewContact, ViewContacts, ViewConversation, ViewGroupInfo, ViewOverview, ViewSearch, ViewSettings, Router, Routes, Events, Helper, Pending, Drawer, Toast, Str, Config, Ajax, FocusLock, ModalBackdrop, Templates, AriaSelectors, ) { var SELECTORS = { DRAWER: '[data-region="right-hand-drawer"]', PANEL_BODY_CONTAINER: '[data-region="panel-body-container"]', PANEL_HEADER_CONTAINER: '[data-region="panel-header-container"]', VIEW_CONTACT: '[data-region="view-contact"]', VIEW_CONTACTS: '[data-region="view-contacts"]', VIEW_CONVERSATION: '[data-region="view-conversation"]', VIEW_CONVERSATION_WITH_ID: '[data-region="view-conversation"][data-conversation-id]', VIEW_CONVERSATION_WITH_USER: '[data-region="view-conversation"][data-other-user-id]', VIEW_GROUP_INFO: '[data-region="view-group-info"]', VIEW_OVERVIEW: '[data-region="view-overview"]', VIEW_SEARCH: '[data-region="view-search"]', VIEW_SETTINGS: '[data-region="view-settings"]', ROUTES: '[data-route]', ROUTES_BACK: '[data-route-back]', HEADER_CONTAINER: '[data-region="header-container"]', BODY_CONTAINER: '[data-region="body-container"]', FOOTER_CONTAINER: '[data-region="footer-container"]', CLOSE_BUTTON: '[data-action="closedrawer"]', MESSAGE_INDEX: '[data-region="message-index"]', MESSAGE_TEXT_AREA: '[data-region="send-message-txt"]', }; /** * 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]); }); }; let backdropPromise = null; /** * Set the focus on the drawer. * * This method also creates or destroy any necessary backdrop zone and focus trap. * * @param {Object} root The message drawer container. * @param {Boolean} hasFocus Whether the drawer has focus or not. */ var setFocus = function(root, hasFocus) { var drawerRoot = Drawer.getDrawerRoot(root); if (!drawerRoot.length) { return; } if (!backdropPromise) { backdropPromise = Templates.render('core/modal_backdrop', {}) .then(html => new ModalBackdrop(html)); } const backdropWithAdjustments = backdropPromise.then(modalBackdrop => { const messageDrawerZIndex = window.getComputedStyle(drawerRoot[0]).zIndex; if (messageDrawerZIndex) { modalBackdrop.setZIndex(messageDrawerZIndex - 1); } modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => { PubSub.publish(Events.HIDE, {}); e.preventDefault(); }); return modalBackdrop; }); if (hasFocus) { FocusLock.trapFocus(root[0]); // eslint-disable-next-line promise/catch-or-return backdropWithAdjustments.then(modalBackdrop => { if (modalBackdrop) { modalBackdrop.show(); const pageWrapper = document.getElementById('page'); pageWrapper.style.overflow = 'hidden'; // Set the focus on the close button so when we press enter, it closes the drawer as it did before var closeButton = root.find(SELECTORS.CLOSE_BUTTON); if (closeButton.length) { closeButton.focus(); } } return modalBackdrop; }); } else { // eslint-disable-next-line promise/catch-or-return backdropWithAdjustments.then(modalBackdrop => { if (modalBackdrop) { FocusLock.untrapFocus(); var button = $(SELECTORS.DRAWER).attr('data-origin'); if (button) { $('#' + button).focus(); } modalBackdrop.hide(); const pageWrapper = document.getElementById('page'); pageWrapper.style.overflow = 'visible'; } return modalBackdrop; }); } }; /** * Show the message drawer. * * @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) { setFocus(root, true); 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) { setFocus(root, false); 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); }; /** * Store an unsent message. * * Don't store this if the user has already seen the unsent message. * This avoids spamming and ensures the user is only reminded once per unsent message. * If the unsent message is sent, this attribute is removed and notification is possible again (see sendMessage). */ const storeUnsentMessage = async() => { const messageTextArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA); if (messageTextArea.value.trim().length > 0 && !messageTextArea.hasAttribute('data-unsent-message-viewed')) { let message = messageTextArea.value; let conversationid = 0; let otheruserid = 0; // We don't always have a conversation to link the unsent message to, so let's check for that. const conversationId = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID); if (conversationId) { const conversationWithId = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID); conversationid = conversationWithId.getAttribute('data-conversation-id'); } // Store the 'other' user id if it is there. This can be used to create conversations. const conversationUser = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER); if (conversationUser) { const conversationWithUser = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER); otheruserid = conversationWithUser.getAttribute('data-other-user-id'); } setStoredUnsentMessage(message, conversationid, otheruserid); } }; /** * Get the stored unsent message from the session via web service. * * @returns {Promise} */ const getStoredUnsentMessage = () => Ajax.call([{ methodname: 'core_message_get_unsent_message', args: {} }])[0]; /** * Set the unsent message value in the session via web service. * * SendBeacon is used here because this is called on 'beforeunload'. * * @param {string} message The message string. * @param {number} conversationid The conversation id. * @param {number} otheruserid The other user id. * @returns {Promise} */ const setStoredUnsentMessage = (message, conversationid, otheruserid) => { const method = 'core_message_set_unsent_message'; const requestUrl = new URL(`${Config.wwwroot}/lib/ajax/service.php`); requestUrl.searchParams.set('sesskey', Config.sesskey); requestUrl.searchParams.set('info', method); navigator.sendBeacon(requestUrl, JSON.stringify([{ index: 0, methodname: method, args: { message: message, conversationid: conversationid, otheruserid: otheruserid, } }])); }; /** * Check for an unsent message. * * @param {String} uniqueId Unique identifier for the Routes. * @param {Object} root The message drawer container. */ const getUnsentMessage = async(uniqueId, root) => { let type; let messageRoot; // We need to check if we are on the message/index page. // This logic is needed to handle the two message widgets here and ensure we are targetting the right one. const messageIndex = document.querySelector(SELECTORS.MESSAGE_INDEX); if (messageIndex !== null) { type = 'index'; messageRoot = document.getElementById(`message-index-${uniqueId}`); if (!messageRoot) { // This is not the correct widget. return; } } else { type = 'drawer'; messageRoot = document.getElementById(`message-drawer-${uniqueId}`); } const storedMessage = await getStoredUnsentMessage(); const messageTextArea = messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA); if (storedMessage.message && messageTextArea !== null) { showUnsentMessage(messageTextArea, storedMessage, type, uniqueId, root); } }; /** * Show an unsent message. * * There are two message widgets on the message/index page. * Because of that, we need to try and target the correct widget. * * @param {String} textArea The textarea element. * @param {Object} stored The stored message content. * @param {String} type Is this from the drawer or index page? * @param {String} uniqueId Unique identifier for the Routes. * @param {Object} root The message drawer container. */ const showUnsentMessage = (textArea, stored, type, uniqueId, root) => { // The user has already been notified. if (textArea.hasAttribute('data-unsent-message-viewed')) { return; } // Depending on the type, show the conversation with the data we have available. // A conversation can be continued if there is a conversationid. // If the user was messaging a new non-contact, we won't have a conversationid yet. // In that case, we use the otheruserid value to start a conversation with them. switch (type) { case 'index': // Show the conversation in the main panel on the message/index page. if (stored.conversationid) { Router.go(uniqueId, Routes.VIEW_CONVERSATION, stored.conversationid, 'frompanel'); // There was no conversation id, let's get a conversation going using the user id. } else if (stored.otheruserid) { Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid); } break; case 'drawer': // Open the drawer and show the conversation. if (stored.conversationid) { let args = { conversationid: stored.conversationid }; Helper.showConversation(args); // There was no conversation id, let's get a conversation going using the user id. } else if (stored.otheruserid) { show(uniqueId, root); Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid); } break; } // Populate the text area. textArea.value = stored.message; textArea.setAttribute('data-unsent-message-viewed', 1); // Notify the user. Toast.add(Str.get_string('unsentmessagenotification', 'core_message')); }; /** * 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, CustomEvents.events.escape]); 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(); }); // Close the message drawer if the drawer is visible and the click happened outside the drawer and the toggle button. $(document).on(CustomEvents.events.activate, e => { var drawer = $(e.target).closest(SELECTORS.DRAWER); var toggleButtonId = $(SELECTORS.DRAWER)?.attr('data-origin'); var toggleButton = ''; if (toggleButtonId !== undefined && toggleButtonId) { toggleButton = $(e.target).closest("#" + toggleButtonId); } if (!drawer.length && !toggleButton.length && isVisible(root)) { // Determine if the element that was clicked is focusable. var focusableElement = $(e.target).closest(AriaSelectors.elements.focusable); if (focusableElement.length) { // We need to move the focus to the clicked element after the drawer is hidden, // so we need to clear the `data-origin` attribute first. $(SELECTORS.DRAWER).attr('data-origin', ''); } // Hide the drawer. hide(root); // Move the focus to the clicked element if it is focusable. if (focusableElement.length) { focusableElement.focus(); } } }); // These are theme-specific to help us fix random behat fails. // These events target those events defined in BS3 and BS4 onwards. root[0].querySelectorAll('.collapse').forEach((collapse) => { collapse.addEventListener('hide.bs.collapse', (e) => { var pendingPromise = new Pending(); e.target.addEventListener('hidden.bs.collapse', function() { pendingPromise.resolve(); }, {once: true}); }); }); root[0].querySelectorAll('.collapse').forEach((collapse) => { collapse.addEventListener('show.bs.collapse', (e) => { var pendingPromise = new Pending(); e.target.addEventListener('shown.bs.collapse', function() { pendingPromise.resolve(); }, {once: true}); }); }); $(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) { const buttonElement = document.getElementById(buttonid); if (isVisible(root)) { hide(root); buttonElement?.setAttribute('aria-expanded', false); $(SELECTORS.JUMPTO).attr('tabindex', -1); } else { show(namespace, root); buttonElement?.setAttribute('aria-expanded', true); setJumpFrom(buttonid); $(SELECTORS.JUMPTO).attr('tabindex', 0); } }); root.on(CustomEvents.events.escape, function() { PubSub.publish(Events.HIDE, {}); }); } PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) { setJumpFrom(args.buttonid); show(namespace, root); Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid); }); var closebutton = root.find(SELECTORS.CLOSE_BUTTON); closebutton.on(CustomEvents.events.activate, function(e, data) { data.originalEvent.preventDefault(); setFocus(root, false); var button = $(SELECTORS.DRAWER).attr('data-origin'); if (button) { $('#' + button).focus(); } PubSub.publish(Events.TOGGLE_VISIBILITY, button); }); PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) { setJumpFrom(args.buttonid); show(namespace, root); Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid); }); PubSub.subscribe(Events.SHOW_SETTINGS, function() { show(namespace, root); Router.go(namespace, Routes.VIEW_SETTINGS); }); PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) { var filteredPreferences = preferences.filter(function(preference) { return 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); } }); // If our textarea is modified, remove the attribute which indicates the user has seen the unsent message notification. // This will allow the user to be notified again. const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA); if (textArea) { textArea.addEventListener('keyup', function() { textArea.removeAttribute('data-unsent-message-viewed'); }); } // Catch any unsent messages and store them. window.addEventListener('beforeunload', storeUnsentMessage); }; /** * 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(); // Get and show any unsent message. getUnsentMessage(uniqueId, root); }; return { init: init, }; }); PKB\k uusrc/notification_processor.jsnu[// 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 . /** * Represents the notification processor (e.g. email, popup, jabber) * * @module core_message/notification_processor * @copyright 2016 Ryan Wyllie * @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; }); PKB\y!src/message_user_button.jsnu[// 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 . /** * Module to message a user from their profile page. * * @module core_message/message_user_button * @copyright 2019 Mark Nelson * @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 }; }); PKB\$'YY&src/message_notification_preference.jsnu[// 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 . /** * Controls the preference for an individual notification type on the * message preference page. * * @module core_message/message_notification_preference * @copyright 2016 Ryan Wyllie * @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; }); PKB\JՑsrc/toggle_contact_button.jsnu[// 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 . /** * Module to add/remove contact using ajax. * * @module core_message/toggle_contact_button * @copyright 2016 Ryan Wyllie * @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 border-0'); 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 }; }); PKB\ g+!+!&src/notification_processor_settings.jsnu[// 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 . /** * Load the settings for a message processor. * * @module core_message/notification_processor_settings * @copyright 2016 Ryan Wyllie * @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(); PKB\4vibbsrc/message_preferences.jsnu[// 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 . /** * Controls the message preference page. * * @module core_message/message_preferences * @copyright 2016 Ryan Wyllie * @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; }); PKB\v src/message_drawer_helper.jsnu[// 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 . /** * Provides some helper functions to trigger actions in the message drawer. * * @module core_message/message_drawer_helper * @copyright 2018 Ryan Wyllie * @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} */ 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); }; PKB\=EIzz0src/message_drawer_view_conversation_renderer.jsnu[// 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 . /** * This module updates the UI for the conversation page in the message * drawer. * * The module will take a patch from the message_drawer_view_conversation_patcher * module and update the UI to reflect the changes. * * This is the only module that ever modifies the UI of the conversation page. * * @module core_message/message_drawer_view_conversation_renderer * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define( [ 'jquery', 'core/notification', 'core/str', 'core/templates', 'core/user_date', 'core_message/message_drawer_view_conversation_constants', 'core/aria', 'core/truncate', ], function( $, Notification, Str, Templates, UserDate, Constants, Aria, Truncate, ) { var SELECTORS = Constants.SELECTORS; var TEMPLATES = Constants.TEMPLATES; var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES; /** * Get the messages container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var getMessagesContainer = function(body) { return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER); }; /** * Show the messages container element. * * @param {Object} body Conversation body container element. */ var showMessagesContainer = function(body) { getMessagesContainer(body).removeClass('hidden'); }; /** * Hide the messages container element. * * @param {Object} body Conversation body container element. */ var hideMessagesContainer = function(body) { getMessagesContainer(body).addClass('hidden'); }; /** * Get the self-conversation message container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var getSelfConversationMessageContainer = function(body) { return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER); }; /** * Hide the self-conversation message container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var hideSelfConversationMessageContainer = function(body) { return getSelfConversationMessageContainer(body).addClass('hidden'); }; /** * Get the contact request sent container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var getContactRequestSentContainer = function(body) { return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER); }; /** * Hide the contact request sent container element. * * @param {Object} body Conversation body container element. * @return {Object} The messages container element. */ var hideContactRequestSentContainer = function(body) { return getContactRequestSentContainer(body).addClass('hidden'); }; /** * Get the footer container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer container element. */ var getFooterContentContainer = function(footer) { return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER); }; /** * Show the footer container element. * * @param {Object} footer Conversation footer container element. */ var showFooterContent = function(footer) { getFooterContentContainer(footer).removeClass('hidden'); }; /** * Hide the footer container element. * * @param {Object} footer Conversation footer container element. */ var hideFooterContent = function(footer) { getFooterContentContainer(footer).addClass('hidden'); }; /** * Get the footer edit mode container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer container element. */ var getFooterEditModeContainer = function(footer) { return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER); }; /** * Show the footer edit mode container element. * * @param {Object} footer Conversation footer container element. */ var showFooterEditMode = function(footer) { getFooterEditModeContainer(footer).removeClass('hidden'); }; /** * Hide the footer edit mode container element. * * @param {Object} footer Conversation footer container element. */ var hideFooterEditMode = function(footer) { getFooterEditModeContainer(footer).addClass('hidden'); }; /** * Get the footer placeholder. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer placeholder container element. */ var getFooterPlaceholderContainer = function(footer) { return footer.find(SELECTORS.PLACEHOLDER_CONTAINER); }; /** * Show the footer placeholder * * @param {Object} footer Conversation footer container element. */ var showFooterPlaceholder = function(footer) { getFooterPlaceholderContainer(footer).removeClass('hidden'); }; /** * Hide the footer placeholder * * @param {Object} footer Conversation footer container element. */ var hideFooterPlaceholder = function(footer) { getFooterPlaceholderContainer(footer).addClass('hidden'); }; /** * Get the footer Require add as contact container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer Require add as contact container element. */ var getFooterRequireContactContainer = function(footer) { return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER); }; /** * Show the footer add as contact dialogue. * * @param {Object} footer Conversation footer container element. */ var showFooterRequireContact = function(footer) { getFooterRequireContactContainer(footer).removeClass('hidden'); }; /** * Hide the footer add as contact dialogue. * * @param {Object} footer Conversation footer container element. */ var hideFooterRequireContact = function(footer) { getFooterRequireContactContainer(footer).addClass('hidden'); }; /** * Get the footer Required to unblock contact container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer Required to unblock contact container element. */ var getFooterRequireUnblockContainer = function(footer) { return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER); }; /** * Show the footer Required to unblock contact container element. * * @param {Object} footer Conversation footer container element. */ var showFooterRequireUnblock = function(footer) { getFooterRequireUnblockContainer(footer).removeClass('hidden'); }; /** * Hide the footer Required to unblock contact container element. * * @param {Object} footer Conversation footer container element. */ var hideFooterRequireUnblock = function(footer) { getFooterRequireUnblockContainer(footer).addClass('hidden'); }; /** * Get the footer Unable to message contact container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The footer Unable to message contact container element. */ var getFooterUnableToMessageContainer = function(footer) { return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER); }; /** * Show the footer Unable to message contact container element. * * @param {Object} footer Conversation footer container element. */ var showFooterUnableToMessage = function(footer) { getFooterUnableToMessageContainer(footer).removeClass('hidden'); }; /** * Hide the footer Unable to message contact container element. * * @param {Object} footer Conversation footer container element. */ var hideFooterUnableToMessage = function(footer) { getFooterUnableToMessageContainer(footer).addClass('hidden'); }; /** * Hide all header elements. * * @param {Object} header Conversation header container element. */ var hideAllHeaderElements = function(header) { hideHeaderContent(header); hideHeaderEditMode(header); hideHeaderPlaceholder(header); }; /** * Hide all footer dialogues and messages. * * @param {Object} footer Conversation footer container element. */ var hideAllFooterElements = function(footer) { hideFooterContent(footer); hideFooterEditMode(footer); hideFooterPlaceholder(footer); hideFooterRequireContact(footer); hideFooterRequireUnblock(footer); hideFooterUnableToMessage(footer); }; /** * Get the content placeholder container element. * * @param {Object} body Conversation body container element. * @return {Object} The body placeholder container element. */ var getContentPlaceholderContainer = function(body) { return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER); }; /** * Show the content placeholder. * * @param {Object} body Conversation body container element. */ var showContentPlaceholder = function(body) { getContentPlaceholderContainer(body).removeClass('hidden'); }; /** * Hide the content placeholder. * * @param {Object} body Conversation body container element. */ var hideContentPlaceholder = function(body) { getContentPlaceholderContainer(body).addClass('hidden'); }; /** * Get the header content container element. * * @param {Object} header Conversation header container element. * @return {Object} The header content container element. */ var getHeaderContent = function(header) { return header.find(SELECTORS.HEADER); }; /** * Show the header content. * * @param {Object} header Conversation header container element. */ var showHeaderContent = function(header) { getHeaderContent(header).removeClass('hidden'); }; /** * Hide the header content. * * @param {Object} header Conversation header container element. */ var hideHeaderContent = function(header) { getHeaderContent(header).addClass('hidden'); }; /** * Get the header edit mode container element. * * @param {Object} header Conversation header container element. * @return {Object} The header content container element. */ var getHeaderEditMode = function(header) { return header.find(SELECTORS.HEADER_EDIT_MODE); }; /** * Show the header edit mode container. * * @param {Object} header Conversation header container element. */ var showHeaderEditMode = function(header) { getHeaderEditMode(header).removeClass('hidden'); }; /** * Hide the header edit mode container. * * @param {Object} header Conversation header container element. */ var hideHeaderEditMode = function(header) { getHeaderEditMode(header).addClass('hidden'); }; /** * Get the header placeholder container element. * * @param {Object} header Conversation header container element. * @return {Object} The header placeholder container element. */ var getHeaderPlaceholderContainer = function(header) { return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER); }; /** * Show the header placeholder. * * @param {Object} header Conversation header container element. */ var showHeaderPlaceholder = function(header) { getHeaderPlaceholderContainer(header).removeClass('hidden'); }; /** * Hide the header placeholder. * * @param {Object} header Conversation header container element. */ var hideHeaderPlaceholder = function(header) { getHeaderPlaceholderContainer(header).addClass('hidden'); }; /** * Get the emoji picker container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The emoji picker container element. */ var getEmojiPickerContainer = function(footer) { return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER); }; /** * Get the emoji picker container element. * * @param {Object} footer Conversation footer container element. * @return {Object} The emoji picker container element. */ var getEmojiAutoCompleteContainer = function(footer) { return footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER); }; /** * Get a message element. * * @param {Object} body Conversation body container element. * @param {Number} messageId the Message id. * @return {Object} A message element from the conversation. */ var getMessageElement = function(body, messageId) { var messagesContainer = getMessagesContainer(body); return messagesContainer.find('[data-message-id="' + messageId + '"]'); }; /** * Get the day container element. The day container element holds a list of messages for that day. * * @param {Object} body Conversation body container element. * @param {Number} dayTimeCreated Midnight timestamp for the day. * @return {Object} jQuery object */ var getDayElement = function(body, dayTimeCreated) { var messagesContainer = getMessagesContainer(body); return messagesContainer.find('[data-day-id="' + dayTimeCreated + '"]'); }; /** * Get the more messages loading icon container element. * * @param {Object} body Conversation body container element. * @return {Object} The more messages loading container element. */ var getMoreMessagesLoadingIconContainer = function(body) { return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER); }; /** * Show the more messages loading icon. * * @param {Object} body Conversation body container element. */ var showMoreMessagesLoadingIcon = function(body) { getMoreMessagesLoadingIconContainer(body).removeClass('hidden'); }; /** * Hide the more messages loading icon. * * @param {Object} body Conversation body container element. */ var hideMoreMessagesLoadingIcon = function(body) { getMoreMessagesLoadingIconContainer(body).addClass('hidden'); }; /** * Get the confirm dialogue container element. * * @param {Object} root The container element to search. * @return {Object} The confirm dialogue container element. */ var getConfirmDialogueContainer = function(root) { return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER); }; /** * Show the confirm dialogue container element. * * @param {Object} root The container element containing a dialogue. */ var showConfirmDialogueContainer = function(root) { var container = getConfirmDialogueContainer(root); var siblings = container.siblings(':not(.hidden)'); Aria.hide(siblings.get()); siblings.attr('data-confirm-dialogue-hidden', true); container.removeClass('hidden'); }; /** * Hide the confirm dialogue container element. * * @param {Object} root The container element containing a dialogue. */ var hideConfirmDialogueContainer = function(root) { var container = getConfirmDialogueContainer(root); var siblings = container.siblings('[data-confirm-dialogue-hidden="true"]'); Aria.unhide(siblings.get()); siblings.removeAttr('data-confirm-dialogue-hidden'); container.addClass('hidden'); }; /** * Set the number of selected messages. * * @param {Object} header The header container element. * @param {Number} value The new number to display. */ var setMessagesSelectedCount = function(header, value) { getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value); }; /** * Format message for the mustache template, transform camelCase properties to lowercase properties. * * @param {Array} messages Array of message objects. * @param {Object} datesCache Cache timestamps and their formatted date string. * @return {Array} Messages formated for mustache template. */ var formatMessagesForTemplate = function(messages, datesCache) { return messages.map(function(message) { return { id: message.id, isread: message.isRead, fromloggedinuser: message.fromLoggedInUser, userfrom: message.userFrom, text: message.text, formattedtime: message.timeCreated ? datesCache[message.timeCreated] : null }; }); }; /** * Create rendering promises for each day containing messages. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Array} days Array of days containing messages. * @param {Object} datesCache Cache timestamps and their formatted date string. * @return {Promise} Days rendering promises. */ var renderAddDays = function(header, body, footer, days, datesCache) { var messagesContainer = getMessagesContainer(body); var daysRenderPromises = days.map(function(data) { var timestampDate = new Date(data.value.timestamp * 1000); return Templates.render(TEMPLATES.DAY, { timestamp: data.value.timestamp, currentyear: timestampDate.getFullYear() === (new Date()).getFullYear(), messages: formatMessagesForTemplate(data.value.messages, datesCache) }); }); return $.when.apply($, daysRenderPromises).then(function() { // Wait until all of the rendering is done for each of the days // to ensure they are added to the page in the correct order. days.forEach(function(data, index) { daysRenderPromises[index] .then(function(html) { if (data.before) { var element = getDayElement(body, data.before.timestamp); return $(html).insertBefore(element); } else { return messagesContainer.append(html); } }) .catch(function() { // Fail silently. }); }); return; }); }; /** * Add (more) messages to day containers. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Array} messages List of messages. * @param {Object} datesCache Cache timestamps and their formatted date string. * @return {Promise} Messages rendering promises. */ var renderAddMessages = function(header, body, footer, messages, datesCache) { var messagesData = messages.map(function(data) { return data.value; }); var formattedMessages = formatMessagesForTemplate(messagesData, datesCache); return Templates.render(TEMPLATES.MESSAGES, {messages: formattedMessages}) .then(function(html) { var messageList = $(html); messages.forEach(function(data) { var messageHtml = messageList.find('[data-message-id="' + data.value.id + '"]'); if (data.before) { var element = getMessageElement(body, data.before.id); return messageHtml.insertBefore(element); } else { var dayContainer = getDayElement(body, data.day.timestamp); var dayMessagesContainer = dayContainer.find(SELECTORS.DAY_MESSAGES_CONTAINER); return dayMessagesContainer.append(messageHtml); } }); return; }); }; /** * Update existing messages. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Array} messages List of messages. * @param {Object} datesCache Cache timestamps and their formatted date string. */ var renderUpdateMessages = function(header, body, footer, messages, datesCache) { messages.forEach(function(message) { var before = message.before; var after = message.after; var element = getMessageElement(body, before.id); if (before.id != after.id) { element.attr('data-message-id', after.id); } if (before.timeCreated != after.timeCreated) { var formattedTime = datesCache[after.timeCreated]; element.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden'); element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass('hidden'); } if (before.sendState != after.sendState) { var loading = element.find(SELECTORS.LOADING_ICON_CONTAINER); var time = element.find(SELECTORS.TIME_CREATED); var retry = element.find(SELECTORS.RETRY_SEND); loading.addClass('hidden'); Aria.hide(loading.get()); time.addClass('hidden'); Aria.hide(time.get()); retry.addClass('hidden'); Aria.hide(retry.get()); element.removeClass('border border-danger'); switch (after.sendState) { case 'pending': loading.removeClass('hidden'); Aria.unhide(loading.get()); break; case 'error': retry.removeClass('hidden'); Aria.unhide(retry.get()); element.addClass('border border-danger'); break; case 'sent': time.removeClass('hidden'); Aria.unhide(time.get()); break; } } if (before.text != after.text) { element.find(SELECTORS.TEXT_CONTAINER).html(after.text); } if (before.errorMessage != after.errorMessage) { var messageContainer = element.find(SELECTORS.ERROR_MESSAGE_CONTAINER); var message = messageContainer.find(SELECTORS.ERROR_MESSAGE); if (after.errorMessage) { messageContainer.removeClass('hidden'); Aria.unhide(messageContainer.get()); message.text(after.errorMessage); } else { messageContainer.addClass('hidden'); Aria.unhide(messageContainer.get()); message.text(''); } } }); }; /** * Remove days from conversation. * * @param {Object} body The body container element. * @param {Array} days Array of days to be removed. */ var renderRemoveDays = function(body, days) { days.forEach(function(data) { getDayElement(body, data.timestamp).remove(); }); }; /** * Remove messages from conversation. * * @param {Object} body The body container element. * @param {Array} messages Array of messages to be removed. */ var renderRemoveMessages = function(body, messages) { messages.forEach(function(data) { getMessageElement(body, data.id).remove(); }); }; /** * Render the full conversation base on input from the statemanager. * * This will pre-load all of the formatted timestamps for each message that * needs to render to reduce the number of networks requests. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data The conversation diff. * @return {Object} jQuery promise. */ var renderConversation = function(header, body, footer, data) { var renderingPromises = []; var hasAddDays = data.days.add.length > 0; var hasAddMessages = data.messages.add.length > 0; var hasUpdateMessages = data.messages.update.length > 0; var timestampsToFormat = []; var datesCachePromise = $.Deferred().resolve({}).promise(); if (hasAddDays) { // Search for all of the timeCreated values in all of the messages in all of // the days that we need to render. timestampsToFormat = timestampsToFormat.concat(data.days.add.reduce(function(carry, day) { return carry.concat(day.value.messages.reduce(function(timestamps, message) { if (message.timeCreated) { timestamps.push(message.timeCreated); } return timestamps; }, [])); }, [])); } if (hasAddMessages) { // Search for all of the timeCreated values in all of the messages that we // need to render. timestampsToFormat = timestampsToFormat.concat(data.messages.add.reduce(function(timestamps, message) { if (message.value.timeCreated) { timestamps.push(message.value.timeCreated); } return timestamps; }, [])); } if (hasUpdateMessages) { timestampsToFormat = timestampsToFormat.concat(data.messages.update.reduce(function(timestamps, message) { if (message.before.timeCreated != message.after.timeCreated) { timestamps.push(message.after.timeCreated); } return timestamps; }, [])); } if (timestampsToFormat.length) { // If we have timestamps then pre-load the formatted version of each of them // in a single request to the server. This saves the templates doing multiple // individual requests. datesCachePromise = Str.get_string('strftimetime24', 'core_langconfig') .then(function(format) { var requests = timestampsToFormat.map(function(timestamp) { return { timestamp: timestamp, format: format }; }); return UserDate.get(requests); }) .then(function(formattedTimes) { return timestampsToFormat.reduce(function(carry, timestamp, index) { carry[timestamp] = formattedTimes[index]; return carry; }, {}); }); } if (hasAddDays) { renderingPromises.push(datesCachePromise.then(function(datesCache) { return renderAddDays(header, body, footer, data.days.add, datesCache); })); } if (hasAddMessages) { renderingPromises.push(datesCachePromise.then(function(datesCache) { return renderAddMessages(header, body, footer, data.messages.add, datesCache); })); } if (hasUpdateMessages) { renderingPromises.push(datesCachePromise.then(function(datesCache) { return renderUpdateMessages(header, body, footer, data.messages.update, datesCache); })); } if (data.days.remove.length > 0) { renderRemoveDays(body, data.days.remove); } if (data.messages.remove.length > 0) { renderRemoveMessages(body, data.messages.remove); } return $.when.apply($, renderingPromises); }; /** * Render the conversation header. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data Data for header. * @return {Object} jQuery promise */ var renderHeader = function(header, body, footer, data) { var headerContainer = getHeaderContent(header); var template = TEMPLATES.HEADER_PUBLIC; data.context.showrouteback = (header.attr('data-from-panel') === "false"); if (data.type == CONVERSATION_TYPES.PRIVATE) { template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS; } else if (data.type == CONVERSATION_TYPES.SELF) { template = TEMPLATES.HEADER_SELF; } return Templates.render(template, data.context) .then(function(html, js) { Templates.replaceNodeContents(headerContainer, html, js); return; }); }; /** * Render the conversation footer. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data Data for footer. * @return {Object} jQuery promise. */ var renderFooter = function(header, body, footer, data) { hideAllFooterElements(footer); switch (data.type) { case 'placeholder': return showFooterPlaceholder(footer); case 'add-contact': return Str.get_strings([ { key: 'requirecontacttomessage', component: 'core_message', param: data.user.fullname }, { key: 'isnotinyourcontacts', component: 'core_message', param: data.user.fullname } ]) .then(function(strings) { var title = strings[1]; var text = strings[0]; var footerContainer = getFooterRequireContactContainer(footer); footerContainer.find(SELECTORS.TITLE).text(title); footerContainer.find(SELECTORS.TEXT).text(text); showFooterRequireContact(footer); return strings; }); case 'edit-mode': return showFooterEditMode(footer); case 'content': return showFooterContent(footer); case 'unblock': return showFooterRequireUnblock(footer); case 'unable-to-message': return showFooterUnableToMessage(footer); } return true; }; /** * Scroll to a message in the conversation. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Number} messageId Message id. */ var renderScrollToMessage = function(header, body, footer, messageId) { var messagesContainer = getMessagesContainer(body); var messageElement = getMessageElement(body, messageId); var position = messageElement.position(); // Scroll the message container down to the top of the message element. if (position) { var scrollTop = messagesContainer.scrollTop() + position.top; messagesContainer.scrollTop(scrollTop); } }; /** * Hide or show the conversation header. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} isLoadingMembers Members loading. */ var renderLoadingMembers = function(header, body, footer, isLoadingMembers) { if (isLoadingMembers) { hideHeaderContent(header); showHeaderPlaceholder(header); } else { showHeaderContent(header); hideHeaderPlaceholder(header); } }; /** * Hide or show loading conversation messages. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} isLoadingFirstMessages Messages loading. */ var renderLoadingFirstMessages = function(header, body, footer, isLoadingFirstMessages) { if (isLoadingFirstMessages) { hideMessagesContainer(body); showContentPlaceholder(body); } else { showMessagesContainer(body); hideContentPlaceholder(body); } }; /** * Hide or show loading more messages. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} isLoading Messages loading. */ var renderLoadingMessages = function(header, body, footer, isLoading) { if (isLoading) { showMoreMessagesLoadingIcon(body); } else { hideMoreMessagesLoadingIcon(body); } }; /** * Hide or show the emoji picker. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} show Should the emoji picker be visible. */ var renderShowEmojiPicker = function(header, body, footer, show) { var container = getEmojiPickerContainer(footer); if (show) { container.removeClass('hidden'); Aria.unhide(container.get()); container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus(); } else { container.addClass('hidden'); Aria.hide(container.get()); } }; /** * Hide or show the emoji auto complete. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} show Should the emoji picker be visible. */ var renderShowEmojiAutoComplete = function(header, body, footer, show) { var container = getEmojiAutoCompleteContainer(footer); if (show) { container.removeClass('hidden'); Aria.unhide(container.get()); } else { container.addClass('hidden'); Aria.hide(container.get()); } }; /** * Show a confirmation dialogue * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {String} buttonSelectors Selectors for the buttons to show. * @param {String} bodyText Text to show in dialogue. * @param {String} headerText Text to show in dialogue header. * @param {Bool} canCancel Can this dialogue be cancelled. * @param {Bool} skipHeader Skip blanking out the header * @param {Bool} showOk Show an 'Okay' button for a dialogue which will close it */ var showConfirmDialogue = function( header, body, footer, buttonSelectors, bodyText, headerText, canCancel, skipHeader, showOk ) { var dialogue = getConfirmDialogueContainer(body); var buttons = buttonSelectors.map(function(selector) { return dialogue.find(selector); }); var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON); var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON); var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT); var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER); dialogue.find('button').addClass('hidden'); if (canCancel) { cancelButton.removeClass('hidden'); } else { cancelButton.addClass('hidden'); } if (showOk) { okayButton.removeClass('hidden'); } else { okayButton.addClass('hidden'); } if (headerText) { // Create the dialogue header. dialogueHeader = $('

    '); dialogueHeader.text(headerText); // Prepend it to the confirmation body. var confirmDialogue = dialogue.find(SELECTORS.CONFIRM_DIALOGUE); confirmDialogue.prepend(dialogueHeader); } else if (dialogueHeader.length) { // Header text is empty but dialogue header is present, so remove it. dialogueHeader.remove(); } buttons.forEach(function(button) { button.removeClass('hidden'); }); text.text(bodyText); showConfirmDialogueContainer(footer); showConfirmDialogueContainer(body); if (!skipHeader) { showConfirmDialogueContainer(header); } dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus(); }; /** * Hide the dialogue * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @return {Bool} always true. */ var hideConfirmDialogue = function(header, body, footer) { var dialogue = getConfirmDialogueContainer(body); var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON); var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON); var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT); var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER); hideCheckDeleteDialogue(body); hideConfirmDialogueContainer(body); hideConfirmDialogueContainer(footer); hideConfirmDialogueContainer(header); dialogue.find('button').addClass('hidden'); cancelButton.removeClass('hidden'); okayButton.removeClass('hidden'); text.text(''); // Remove dialogue header if present. if (dialogueHeader.length) { dialogueHeader.remove(); } header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus(); return true; }; /** * Render the confirm block user dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} user User to block. * @return {Object} jQuery promise */ var renderConfirmBlockUser = function(header, body, footer, user) { if (user) { const username = truncateUsername(user.fullname); if (user.canmessageevenifblocked) { return Str.get_string('cantblockuser', 'core_message', username) .then(function(string) { return showConfirmDialogue(header, body, footer, [], string, '', false, false, true); }); } else { return Str.get_string('blockuserconfirm', 'core_message', username) .then(function(string) { return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_BLOCK], string, '', true, false); }); } } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the confirm unblock user dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} user User to unblock. * @return {Object} jQuery promise */ var renderConfirmUnblockUser = function(header, body, footer, user) { if (user) { return Str.get_string('unblockuserconfirm', 'core_message', truncateUsername(user.fullname)) .then(function(string) { return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_UNBLOCK], string, '', true, false); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the add user as contact dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} user User to add as contact. * @return {Object} jQuery promise */ var renderConfirmAddContact = function(header, body, footer, user) { if (user) { // Truncate long usernames. const userFullName = Truncate.truncate(user.fullname, { length: 30, words: true, ellipsis: '...' }); return Str.get_string('addcontactconfirm', 'core_message', userFullName) .then(function(string) { return showConfirmDialogue( header, body, footer, [SELECTORS.ACTION_CONFIRM_ADD_CONTACT], string, '', true, false ); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the remove user from contacts dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} user User to remove from contacts. * @return {Object} jQuery promise */ var renderConfirmRemoveContact = function(header, body, footer, user) { if (user) { return Str.get_string('removecontactconfirm', 'core_message', truncateUsername(user.fullname)) .then(function(string) { return showConfirmDialogue( header, body, footer, [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT], string, '', true, false ); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the delete selected messages dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data If the dialogue should show and checkbox shows to delete message for all users. * @return {Object} jQuery promise */ var renderConfirmDeleteSelectedMessages = function(header, body, footer, data) { var showmessage = null; if (data.type == CONVERSATION_TYPES.SELF) { // Message displayed to self-conversations is slighly different. showmessage = 'deleteselectedmessagesconfirmselfconversation'; } else { // This other message should be displayed. if (data.canDeleteMessagesForAllUsers) { showCheckDeleteDialogue(body); showmessage = 'deleteforeveryoneselectedmessagesconfirm'; } else { showmessage = 'deleteselectedmessagesconfirm'; } } if (data.show) { return Str.get_string(showmessage, 'core_message') .then(function(string) { return showConfirmDialogue( header, body, footer, [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES], string, '', true, false ); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the confirm delete conversation dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {int|Null} type The conversation type to be removed. * @return {Object} jQuery promise */ var renderConfirmDeleteConversation = function(header, body, footer, type) { var showmessage = null; if (type == CONVERSATION_TYPES.SELF) { // Message displayed to self-conversations is slighly different. showmessage = 'deleteallselfconfirm'; } else if (type) { // This other message should be displayed. showmessage = 'deleteallconfirm'; } if (showmessage) { return Str.get_string(showmessage, 'core_message') .then(function(string) { return showConfirmDialogue( header, body, footer, [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION], string, '', true, false ); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Render the confirm delete conversation dialogue. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} user The other user object. * @return {Object} jQuery promise */ var renderConfirmContactRequest = function(header, body, footer, user) { if (user) { return Str.get_string('userwouldliketocontactyou', 'core_message', truncateUsername(user.fullname)) .then(function(string) { var buttonSelectors = [ SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST, SELECTORS.ACTION_DECLINE_CONTACT_REQUEST ]; return showConfirmDialogue(header, body, footer, buttonSelectors, string, '', false, true); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Show the checkbox to allow delete message for all. * * @param {Object} body The body container element. */ var showCheckDeleteDialogue = function(body) { var dialogue = getConfirmDialogueContainer(body); var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER); checkboxRegion.removeClass('hidden'); }; /** * Hide the checkbox to allow delete message for all. * * @param {Object} body The body container element. */ var hideCheckDeleteDialogue = function(body) { var dialogue = getConfirmDialogueContainer(body); var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER); var checkbox = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE); checkbox.prop('checked', false); checkboxRegion.addClass('hidden'); }; /** * Show or hide the block / unblock option in the header dropdown menu. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} isBlocked is user blocked. */ var renderIsBlocked = function(header, body, footer, isBlocked) { if (isBlocked) { header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass('hidden'); header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass('hidden'); } else { header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass('hidden'); header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass('hidden'); } }; /** * Show or hide the favourite / unfavourite option in the header dropdown menu * and the favourite star in the header title. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} state is this conversation a favourite. */ var renderIsFavourite = function(header, body, footer, state) { var favouriteIcon = header.find(SELECTORS.FAVOURITE_ICON_CONTAINER); var addFavourite = header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE); var removeFavourite = header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE); switch (state) { case 'hide': favouriteIcon.addClass('hidden'); addFavourite.addClass('hidden'); removeFavourite.addClass('hidden'); break; case 'show-add': favouriteIcon.addClass('hidden'); addFavourite.removeClass('hidden'); removeFavourite.addClass('hidden'); break; case 'show-remove': favouriteIcon.removeClass('hidden'); addFavourite.addClass('hidden'); removeFavourite.removeClass('hidden'); break; } }; /** * Show or hide the mute / unmute option in the header dropdown menu * and the muted icon in the header title. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {string} state The state of the conversation as defined by the patcher. */ var renderIsMuted = function(header, body, footer, state) { var muteIcon = header.find(SELECTORS.MUTED_ICON_CONTAINER); var setMuted = header.find(SELECTORS.ACTION_CONFIRM_MUTE); var unsetMuted = header.find(SELECTORS.ACTION_CONFIRM_UNMUTE); switch (state) { case 'hide': muteIcon.addClass('hidden'); setMuted.addClass('hidden'); unsetMuted.addClass('hidden'); break; case 'show-mute': muteIcon.addClass('hidden'); setMuted.removeClass('hidden'); unsetMuted.addClass('hidden'); break; case 'show-unmute': muteIcon.removeClass('hidden'); setMuted.addClass('hidden'); unsetMuted.removeClass('hidden'); break; } }; /** * Show or hide the add / remove user as contact option in the header dropdown menu. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} state the contact state. */ var renderIsContact = function(header, body, footer, state) { var addContact = header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT); var removeContact = header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT); switch (state) { case 'pending-contact': addContact.addClass('hidden'); removeContact.addClass('hidden'); break; case 'contact': addContact.addClass('hidden'); removeContact.removeClass('hidden'); break; case 'non-contact': addContact.removeClass('hidden'); removeContact.addClass('hidden'); break; } }; /** * Show or hide confirm action from confirm dialogue is loading. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} isLoading confirm action is loading. */ var renderLoadingConfirmAction = function(header, body, footer, isLoading) { var dialogue = getConfirmDialogueContainer(body); var buttons = dialogue.find('button'); var buttonText = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT); var loadingIcon = dialogue.find(SELECTORS.LOADING_ICON_CONTAINER); if (isLoading) { buttons.prop('disabled', true); buttonText.addClass('hidden'); loadingIcon.removeClass('hidden'); } else { buttons.prop('disabled', false); buttonText.removeClass('hidden'); loadingIcon.addClass('hidden'); } }; /** * Show or hide the header and footer content for edit mode. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Bool} inEditMode In edit mode or not. */ var renderInEditMode = function(header, body, footer, inEditMode) { var messages = null; if (inEditMode) { messages = body.find(SELECTORS.MESSAGE_NOT_SELECTED); messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden'); hideHeaderContent(header); showHeaderEditMode(header); } else { messages = getMessagesContainer(body); messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden'); messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden'); showHeaderContent(header); hideHeaderEditMode(header); } }; /** * Select or unselect messages. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data The messages to select or unselect. */ var renderSelectedMessages = function(header, body, footer, data) { var hasSelectedMessages = data.count > 0; if (data.add.length) { data.add.forEach(function(messageId) { var message = getMessageElement(body, messageId); message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden'); message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass('hidden'); message.attr('aria-checked', true); }); } if (data.remove.length) { data.remove.forEach(function(messageId) { var message = getMessageElement(body, messageId); if (hasSelectedMessages) { message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden'); } message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden'); message.attr('aria-checked', false); }); } setMessagesSelectedCount(header, data.count); }; /** * Show or hide the require add contact panel. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} data Whether the user has to be added a a contact. * @return {Object} jQuery promise */ var renderRequireAddContact = function(header, body, footer, data) { if (data.show && !data.hasMessages) { return Str.get_strings([ { key: 'requirecontacttomessage', component: 'core_message', param: data.user.fullname }, { key: 'isnotinyourcontacts', component: 'core_message', param: data.user.fullname } ]) .then(function(strings) { var title = strings[1]; var text = strings[0]; return showConfirmDialogue( header, body, footer, [SELECTORS.ACTION_REQUEST_ADD_CONTACT], text, title, false, true ); }); } else { return hideConfirmDialogue(header, body, footer); } }; /** * Show or hide the self-conversation message. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {Object} displayMessage should the message be displayed?. * @return {Object|true} jQuery promise */ var renderSelfConversationMessage = function(header, body, footer, displayMessage) { var container = getSelfConversationMessageContainer(body); if (displayMessage) { container.removeClass('hidden'); } else { container.addClass('hidden'); } return true; }; /** * Show or hide the require add contact panel. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @param {String} userFullName Full name of the other user. * @return {Object|true} jQuery promise */ var renderContactRequestSent = function(header, body, footer, userFullName) { var container = getContactRequestSentContainer(body); if (userFullName) { return Str.get_string('yourcontactrequestpending', 'core_message', truncateUsername(userFullName)) .then(function(string) { container.find(SELECTORS.TEXT).text(string); container.removeClass('hidden'); return string; }); } else { container.addClass('hidden'); return true; } }; /** * Reset the UI to the initial state. * * @param {Object} header The header container element. * @param {Object} body The body container element. * @param {Object} footer The footer container element. * @return {Bool} */ var renderReset = function(header, body, footer) { hideConfirmDialogue(header, body, footer); hideContactRequestSentContainer(body); hideSelfConversationMessageContainer(body); hideAllHeaderElements(header); showHeaderPlaceholder(header); hideAllFooterElements(footer); showFooterPlaceholder(footer); return true; }; var render = function(header, body, footer, patch) { var configs = [ { // Resetting the UI needs to come first, if it's required. reset: renderReset }, { // Any async rendering (stuff that requires templates, strings etc) should // go in here. conversation: renderConversation, header: renderHeader, footer: renderFooter, confirmBlockUser: renderConfirmBlockUser, confirmUnblockUser: renderConfirmUnblockUser, confirmAddContact: renderConfirmAddContact, confirmRemoveContact: renderConfirmRemoveContact, confirmDeleteSelectedMessages: renderConfirmDeleteSelectedMessages, confirmDeleteConversation: renderConfirmDeleteConversation, confirmContactRequest: renderConfirmContactRequest, requireAddContact: renderRequireAddContact, selfConversationMessage: renderSelfConversationMessage, contactRequestSent: renderContactRequestSent }, { loadingMembers: renderLoadingMembers, loadingFirstMessages: renderLoadingFirstMessages, loadingMessages: renderLoadingMessages, isBlocked: renderIsBlocked, isContact: renderIsContact, isFavourite: renderIsFavourite, isMuted: renderIsMuted, loadingConfirmAction: renderLoadingConfirmAction, inEditMode: renderInEditMode, showEmojiPicker: renderShowEmojiPicker, showEmojiAutoComplete: renderShowEmojiAutoComplete, }, { // Scrolling should be last to make sure everything // on the page is visible. scrollToMessage: renderScrollToMessage, selectedMessages: renderSelectedMessages } ]; // Helper function to process each of the configs above. var processConfig = function(config) { var results = []; for (var key in patch) { if (config.hasOwnProperty(key)) { var renderFunc = config[key]; var patchValue = patch[key]; results.push(renderFunc(header, body, footer, patchValue)); } } return results; }; // The first config is special because it resets the UI. var renderingPromises = processConfig(configs[0]); // The second config is special because it contains async rendering. renderingPromises = renderingPromises.concat(processConfig(configs[1])); // Wait for the async rendering to complete before processing the // rest of the configs, in order. return $.when.apply($, renderingPromises) .then(function() { for (var i = 2; i < configs.length; i++) { processConfig(configs[i]); } return; }) .catch(Notification.exception); }; /** * Truncate long usernames. * * @param {String} username Text to truncate. * @return {String} Truncated string. */ var truncateUsername = function(username) { return Truncate.truncate(username, { length: 30, words: true, ellipsis: '...' }); }; return { render: render, }; }); PKB\AVV)build/message_drawer_view_overview.min.jsnu[/** * Controls the overview page of the message drawer. * * @module core_message/message_drawer_view_overview * @copyright 2018 Ryan Wyllie * @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-bs-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.mapPKB\#~9~9-build/message_drawer_view_overview.min.js.mapnu[{"version":3,"file":"message_drawer_view_overview.min.js","sources":["../src/message_drawer_view_overview.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the overview page of the message drawer.\n *\n * @module core_message/message_drawer_view_overview\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/key_codes',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_view_overview_section',\n 'core_message/message_repository',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n KeyCodes,\n PubSub,\n Str,\n Router,\n Routes,\n MessageDrawerEvents,\n Section,\n MessageRepository,\n Constants\n) {\n\n var SELECTORS = {\n CONTACT_REQUEST_COUNT: '[data-region=\"contact-request-count\"]',\n FAVOURITES: '[data-region=\"view-overview-favourites\"]',\n GROUP_MESSAGES: '[data-region=\"view-overview-group-messages\"]',\n MESSAGES: '[data-region=\"view-overview-messages\"]',\n SEARCH_INPUT: '[data-region=\"view-overview-search-input\"]',\n SECTION_TOGGLE_BUTTON: '[data-bs-toggle]'\n };\n\n // Categories displayed in the message drawer. Some methods (such as filterCountsByType) are expecting their value\n // will be the same as the defined in the CONVERSATION_TYPES, except for the favourite.\n var OVERVIEW_SECTION_TYPES = {\n PRIVATE: [Constants.CONVERSATION_TYPES.PRIVATE, Constants.CONVERSATION_TYPES.SELF],\n PUBLIC: [Constants.CONVERSATION_TYPES.PUBLIC],\n FAVOURITE: null\n };\n\n var loadAllCountsPromise = null;\n\n /**\n * Load the total and unread conversation counts from the server for this user. This function\n * returns a jQuery promise that will be resolved with the counts.\n *\n * The request is only sent once per page load and will be cached for subsequent\n * calls to this function.\n *\n * @param {Number} loggedInUserId The logged in user's id\n * @return {Object} jQuery promise\n */\n var loadAllCounts = function(loggedInUserId) {\n if (loadAllCountsPromise === null) {\n loadAllCountsPromise = MessageRepository.getAllConversationCounts(loggedInUserId);\n }\n\n return loadAllCountsPromise;\n };\n\n /**\n * Filter a set of counts to return only the count for the given type.\n *\n * This is used on the result returned by the loadAllCounts function.\n *\n * @param {Object} counts Conversation counts indexed by conversation type.\n * @param {Array|null} types The conversation types handlded by this section (null for all conversation types).\n * @param {bool} includeFavourites If this section includes favourites\n * @return {Number}\n */\n var filterCountsByTypes = function(counts, types, includeFavourites) {\n var total = 0;\n\n if (types && types.length) {\n total = types.reduce(function(carry, type) {\n return carry + counts.types[type];\n }, total);\n }\n\n if (includeFavourites) {\n total += counts.favourites;\n }\n\n return total;\n };\n\n /**\n * Opens one of the sections based on whether the section has unread conversations\n * or any conversations\n *\n * Default section priority is favourites, groups, then messages. A section can increase\n * in priority if it has conversations in it. It can increase even further if it has\n * unread conversations.\n *\n * @param {Array} sections List of section roots, total counts, and unread counts.\n */\n var openSection = function(sections) {\n var isAlreadyOpen = sections.some(function(section) {\n var sectionRoot = section[0];\n return Section.isVisible(sectionRoot);\n });\n\n if (isAlreadyOpen) {\n // The user has already opened a section so there is nothing to do.\n return;\n }\n\n // Order the sections so that sections with unread conversations are prioritised\n // over sections without and sections with total conversations are prioritised\n // over sections without.\n sections.sort(function(a, b) {\n var aTotal = a[1];\n var aUnread = a[2];\n var bTotal = b[1];\n var bUnread = b[2];\n\n if (aUnread > 0 && bUnread == 0) {\n return -1;\n } else if (aUnread == 0 && bUnread > 0) {\n return 1;\n } else if (aTotal > 0 && bTotal == 0) {\n return -1;\n } else if (aTotal == 0 && bTotal > 0) {\n return 1;\n } else {\n return 0;\n }\n });\n\n // Get the root of the first section after sorting.\n var sectionRoot = sections[0][0];\n var button = sectionRoot.find(SELECTORS.SECTION_TOGGLE_BUTTON);\n // Click it to expand it.\n button.click();\n };\n\n /**\n * Get the search input text element.\n *\n * @param {Object} header Overview header container element.\n * @return {Object} The search input element.\n */\n var getSearchInput = function(header) {\n return header.find(SELECTORS.SEARCH_INPUT);\n };\n\n /**\n * Get the logged in user id.\n *\n * @param {Object} body Overview body container element.\n * @return {String} Logged in user id.\n */\n var getLoggedInUserId = function(body) {\n return body.attr('data-user-id');\n };\n\n /**\n * Decrement the contact request count. If the count is zero or below then\n * hide the count.\n *\n * @param {Object} header Conversation header container element.\n * @return {Function} A function to handle decrementing the count.\n */\n var decrementContactRequestCount = function(header) {\n return function() {\n var countContainer = header.find(SELECTORS.CONTACT_REQUEST_COUNT);\n var count = parseInt(countContainer.text(), 10);\n count = isNaN(count) ? 0 : count - 1;\n\n if (count <= 0) {\n countContainer.addClass('hidden');\n } else {\n countContainer.text(count);\n }\n };\n };\n\n /**\n * Listen to, and handle event in the overview header.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} header Conversation header container element.\n */\n var registerEventListeners = function(namespace, header) {\n var searchInput = getSearchInput(header);\n var ignoredKeys = [KeyCodes.tab, KeyCodes.shift, KeyCodes.ctrl, KeyCodes.alt];\n\n searchInput.on('click', function() {\n Router.go(namespace, Routes.VIEW_SEARCH);\n });\n searchInput.on('keydown', function(e) {\n if (ignoredKeys.indexOf(e.keyCode) < 0 && e.key != 'Meta') {\n Router.go(namespace, Routes.VIEW_SEARCH);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, decrementContactRequestCount(header));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, decrementContactRequestCount(header));\n };\n\n /**\n * Setup the overview page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} header Overview header container element.\n * @param {Object} body Overview body container element.\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body) {\n if (!header.attr('data-init')) {\n registerEventListeners(namespace, header);\n header.attr('data-init', true);\n }\n var fromPanel = header.attr('data-in-panel') ? 'frompanel' : null;\n\n getSearchInput(header).val('');\n var loggedInUserId = getLoggedInUserId(body);\n var allCounts = loadAllCounts(loggedInUserId);\n\n var sections = [\n // Favourite conversations section.\n [body.find(SELECTORS.FAVOURITES), OVERVIEW_SECTION_TYPES.FAVOURITE, true],\n // Group conversations section.\n [body.find(SELECTORS.GROUP_MESSAGES), OVERVIEW_SECTION_TYPES.PUBLIC, false],\n // Private conversations section.\n [body.find(SELECTORS.MESSAGES), OVERVIEW_SECTION_TYPES.PRIVATE, false]\n ];\n\n sections.forEach(function(args) {\n var sectionRoot = args[0];\n var sectionTypes = args[1];\n var includeFavourites = args[2];\n var totalCountPromise = allCounts.then(function(result) {\n return filterCountsByTypes(result.total, sectionTypes, includeFavourites);\n });\n var unreadCountPromise = allCounts.then(function(result) {\n return filterCountsByTypes(result.unread, sectionTypes, includeFavourites);\n });\n\n Section.show(namespace, null, sectionRoot, null, sectionTypes, includeFavourites,\n totalCountPromise, unreadCountPromise, fromPanel);\n });\n\n return allCounts.then(function(result) {\n var sectionParams = sections.map(function(section) {\n var sectionRoot = section[0];\n var sectionTypes = section[1];\n var includeFavourites = section[2];\n var totalCount = filterCountsByTypes(result.total, sectionTypes, includeFavourites);\n var unreadCount = filterCountsByTypes(result.unread, sectionTypes, includeFavourites);\n\n return [sectionRoot, totalCount, unreadCount];\n });\n\n // Open up one of the sections for the user.\n return openSection(sectionParams);\n });\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @return {Object} jQuery promise\n */\n var description = function() {\n return Str.get_string('messagedrawerviewoverview', 'core_message');\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","KeyCodes","PubSub","Str","Router","Routes","MessageDrawerEvents","Section","MessageRepository","Constants","SELECTORS","OVERVIEW_SECTION_TYPES","PRIVATE","CONVERSATION_TYPES","SELF","PUBLIC","FAVOURITE","loadAllCountsPromise","filterCountsByTypes","counts","types","includeFavourites","total","length","reduce","carry","type","favourites","getSearchInput","header","find","decrementContactRequestCount","countContainer","count","parseInt","text","isNaN","addClass","show","namespace","body","attr","searchInput","ignoredKeys","tab","shift","ctrl","alt","on","go","VIEW_SEARCH","e","indexOf","keyCode","key","subscribe","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","registerEventListeners","fromPanel","val","loggedInUserId","getLoggedInUserId","allCounts","getAllConversationCounts","loadAllCounts","sections","forEach","args","sectionRoot","sectionTypes","totalCountPromise","then","result","unreadCountPromise","unread","some","section","isVisible","sort","a","b","aTotal","aUnread","bTotal","bUnread","click","openSection","map","description","get_string"],"mappings":"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"}PKB\XD0D0build/message_repository.min.jsnu[/** * Retrieves messages from the server. * * @module core_message/message_repository * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_repository",["jquery","core/ajax","core/notification","core_message/message_drawer_view_conversation_constants"],(function($,Ajax,Notification,Constants){var CONVERSATION_TYPES=Constants.CONVERSATION_TYPES,sendMessagesToUser=function(toUserId,messages){var request={methodname:"core_message_send_instant_messages",args:{messages:messages.map((function(message){return{touserid:toUserId,text:message}}))}};return Ajax.call([request])[0].then((function(results){var errors=results.reduce((function(carry,result){return result.errormessage&&carry.push(result.errormessage),carry}),[]);if(errors.length)throw new Error(errors.join("\n"));return results})).then((function(results){return results.map((function(result){return{id:result.msgid,text:result.text,timecreated:result.timecreated,useridfrom:result.useridfrom,conversationid:result.conversationid,candeletemessagesforallusers:result.candeletemessagesforallusers}}))}))},sendMessagesToConversation=function(conversationId,messages){var request={methodname:"core_message_send_messages_to_conversation",args:{conversationid:conversationId,messages:messages.map((function(message){return{text:message}}))}};return Ajax.call([request])[0]};return{countUnreadConversations:function(args){var request={methodname:"core_message_get_unread_conversations_count",args:args},promise=Ajax.call([request])[0];return promise.fail(Notification.exception),promise},getContacts:function(userId,limit,offset){var args={userid:userId};void 0!==limit&&(args.limitnum=limit),void 0!==offset&&(args.limitfrom=offset);var request={methodname:"core_message_get_user_contacts",args:args};return Ajax.call([request])[0]},blockUser:function(userId,blockedUserId){var requests=[{methodname:"core_message_block_user",args:{userid:userId,blockeduserid:blockedUserId}},{methodname:"core_message_get_member_info",args:{referenceuserid:userId,userids:[blockedUserId],includecontactrequests:!0,includeprivacyinfo:!0}}];return $.when.apply(null,Ajax.call(requests)).then((function(reponse1,profiles){return profiles.length?profiles[0]:{}}))},unblockUser:function(userId,unblockedUserId){var requests=[{methodname:"core_message_unblock_user",args:{userid:userId,unblockeduserid:unblockedUserId}},{methodname:"core_message_get_member_info",args:{referenceuserid:userId,userids:[unblockedUserId],includecontactrequests:!0,includeprivacyinfo:!0}}];return $.when.apply(null,Ajax.call(requests)).then((function(reponse1,profiles){return profiles.length?profiles[0]:{}}))},createContactRequest:function(userId,requestUserIds){var request={methodname:"core_message_create_contact_request",args:{userid:userId,requesteduserid:requestUserIds}};return Ajax.call([request])[0]},deleteContacts:function(userId,contactUserIds){var requests=[{methodname:"core_message_delete_contacts",args:{userid:userId,userids:contactUserIds}},{methodname:"core_message_get_member_info",args:{referenceuserid:userId,userids:contactUserIds,includecontactrequests:!0,includeprivacyinfo:!0}}];return $.when.apply(null,Ajax.call(requests)).then((function(response1,profiles){return profiles}))},getMessages:function(currentUserId,conversationId,limit,offset,newestFirst,timeFrom){var args={currentuserid:currentUserId,convid:conversationId,newest:!!newestFirst};void 0!==limit&&(args.limitnum=limit),void 0!==offset&&(args.limitfrom=offset),void 0!==timeFrom&&(args.timefrom=timeFrom);var request={methodname:"core_message_get_conversation_messages",args:args};return Ajax.call([request])[0]},searchUsers:function(userId,searchString,limit,offset){var args={userid:userId,search:searchString};void 0!==limit&&(args.limitnum=limit),void 0!==offset&&(args.limitfrom=offset);var request={methodname:"core_message_message_search_users",args:args};return Ajax.call([request])[0]},searchMessages:function(userId,searchString,limit,offset){var args={userid:userId,search:searchString};void 0!==limit&&(args.limitnum=limit),void 0!==offset&&(args.limitfrom=offset);var request={methodname:"core_message_data_for_messagearea_search_messages",args:args};return Ajax.call([request])[0]},sendMessagesToUser:sendMessagesToUser,sendMessageToUser:function(toUserId,text){return sendMessagesToUser(toUserId,[text]).then((function(results){return results[0]}))},sendMessagesToConversation:sendMessagesToConversation,sendMessageToConversation:function(conversationId,text){return sendMessagesToConversation(conversationId,[text]).then((function(result){return result[0]}))},savePreferences:function(userId,preferences){var request={methodname:"core_user_update_user_preferences",args:{userid:userId,preferences:preferences}};return Ajax.call([request])[0]},getPreferences:function(userId){var request={methodname:"core_user_get_user_preferences",args:{userid:userId}};return Ajax.call([request])[0]},deleteMessages:function(userId,messageIds){return $.when.apply(null,Ajax.call(messageIds.map((function(messageId){return{methodname:"core_message_delete_message",args:{messageid:messageId,userid:userId}}}))))},deleteMessagesForAllUsers:function(userId,messageIds){return $.when.apply(null,Ajax.call(messageIds.map((function(messageId){return{methodname:"core_message_delete_message_for_all_users",args:{messageid:messageId,userid:userId}}}))))},deleteConversation:function(userId,conversationId){var request={methodname:"core_message_delete_conversations_by_id",args:{userid:userId,conversationids:[conversationId]}};return Ajax.call([request])[0]},getContactRequests:function(userId){var request={methodname:"core_message_get_contact_requests",args:{userid:userId}};return Ajax.call([request])[0]},acceptContactRequest:function(sendingUserId,recipientUserId){var requests=[{methodname:"core_message_confirm_contact_request",args:{userid:sendingUserId,requesteduserid:recipientUserId}},{methodname:"core_message_get_member_info",args:{referenceuserid:recipientUserId,userids:[sendingUserId],includecontactrequests:!0,includeprivacyinfo:!0}}];return $.when.apply(null,Ajax.call(requests)).then((function(reponse1,profiles){return profiles.length?profiles[0]:{}}))},declineContactRequest:function(sendingUserId,recipientUserId){var requests=[{methodname:"core_message_decline_contact_request",args:{userid:sendingUserId,requesteduserid:recipientUserId}},{methodname:"core_message_get_member_info",args:{referenceuserid:recipientUserId,userids:[sendingUserId],includecontactrequests:!0,includeprivacyinfo:!0}}];return $.when.apply(null,Ajax.call(requests)).then((function(reponse1,profiles){return profiles.length?profiles[0]:{}}))},getConversation:function(loggedInUserId,conversationId,includeContactRequests,includePrivacyInfo,memberLimit,memberOffset,messageLimit,messageOffset,newestMessagesFirst){var args={userid:loggedInUserId,conversationid:conversationId};null!=includeContactRequests&&(args.includecontactrequests=includeContactRequests),null!=includePrivacyInfo&&(args.includeprivacyinfo=includePrivacyInfo),null!=memberLimit&&(args.memberlimit=memberLimit),null!=memberOffset&&(args.memberoffset=memberOffset),null!=messageLimit&&(args.messagelimit=messageLimit),null!=messageOffset&&(args.messageoffset=messageOffset),null!=newestMessagesFirst&&(args.newestmessagesfirst=newestMessagesFirst);var request={methodname:"core_message_get_conversation",args:args};return Ajax.call([request])[0]},getConversationBetweenUsers:function(loggedInUserId,otherUserId,includeContactRequests,includePrivacyInfo,memberLimit,memberOffset,messageLimit,messageOffset,newestMessagesFirst){var args={userid:loggedInUserId,otheruserid:otherUserId};null!=includeContactRequests&&(args.includecontactrequests=includeContactRequests),null!=includePrivacyInfo&&(args.includeprivacyinfo=includePrivacyInfo),null!=memberLimit&&(args.memberlimit=memberLimit),null!=memberOffset&&(args.memberoffset=memberOffset),null!=messageLimit&&(args.messagelimit=messageLimit),null!=messageOffset&&(args.messageoffset=messageOffset),null!=newestMessagesFirst&&(args.newestmessagesfirst=newestMessagesFirst);var request={methodname:"core_message_get_conversation_between_users",args:args};return Ajax.call([request])[0]},getSelfConversation:function(loggedInUserId,messageLimit,messageOffset,newestMessagesFirst){var args={userid:loggedInUserId};null!=messageLimit&&(args.messagelimit=messageLimit),null!=messageOffset&&(args.messageoffset=messageOffset),null!=newestMessagesFirst&&(args.newestmessagesfirst=newestMessagesFirst);var request={methodname:"core_message_get_self_conversation",args:args};return Ajax.call([request])[0]},getConversations:function(userId,type,limit,offset,favourites,mergeself){var args={userid:userId,type:type};null!=limit&&(args.limitnum=limit),null!=offset&&(args.limitfrom=offset),null!=favourites&&(args.favourites=favourites),null!=mergeself&&(args.mergeself=mergeself);var request={methodname:"core_message_get_conversations",args:args};return Ajax.call([request])[0].then((function(result){return result.conversations.length&&(result.conversations=result.conversations.map((function(conversation){if(conversation.type==CONVERSATION_TYPES.PRIVATE||conversation.type==CONVERSATION_TYPES.SELF){var otherUser=conversation.members.length?conversation.members[0]:null;otherUser&&(conversation.name=conversation.name?conversation.name:otherUser.fullname,conversation.imageurl=conversation.imageurl?conversation.imageurl:otherUser.profileimageurl)}return conversation}))),result}))},getConversationMembers:function(conversationId,loggedInUserId,limit,offset,includeContactRequests){var args={userid:loggedInUserId,conversationid:conversationId};null!=limit&&(args.limitnum=limit),null!=offset&&(args.limitfrom=offset),null!=includeContactRequests&&(args.includecontactrequests=includeContactRequests);var request={methodname:"core_message_get_conversation_members",args:args};return Ajax.call([request])[0]},setFavouriteConversations:function(userId,conversationIds){var request={methodname:"core_message_set_favourite_conversations",args:{userid:userId,conversations:conversationIds}};return Ajax.call([request])[0]},setMutedConversations:function(userId,conversationIds){var request={methodname:"core_message_mute_conversations",args:{userid:userId,conversationids:conversationIds}};return Ajax.call([request])[0]},unsetFavouriteConversations:function(userId,conversationIds){var request={methodname:"core_message_unset_favourite_conversations",args:{userid:userId,conversations:conversationIds}};return Ajax.call([request])[0]},unsetMutedConversations:function(userId,conversationIds){var request={methodname:"core_message_unmute_conversations",args:{userid:userId,conversationids:conversationIds}};return Ajax.call([request])[0]},getMemberInfo:function(referenceUserId,userIds,includeContactRequests,includePrivacyInfo){var args={referenceuserid:referenceUserId,userids:userIds};void 0!==includeContactRequests&&(args.includecontactrequests=includeContactRequests),void 0!==includePrivacyInfo&&(args.includeprivacyinfo=includePrivacyInfo);var request={methodname:"core_message_get_member_info",args:args};return Ajax.call([request])[0]},markAllConversationMessagesAsRead:function(userId,conversationId){var request={methodname:"core_message_mark_all_conversation_messages_as_read",args:{userid:userId,conversationid:conversationId}};return Ajax.call([request])[0]},getUserMessagePreferences:function(userId){var request={methodname:"core_message_get_user_message_preferences",args:{userid:userId}};return Ajax.call([request])[0]},getTotalConversationCounts:function(userId){var request={methodname:"core_message_get_conversation_counts",args:{userid:userId}};return Ajax.call([request])[0]},getUnreadConversationCounts:function(userId){var request={methodname:"core_message_get_unread_conversation_counts",args:{userid:userId}};return Ajax.call([request])[0]},getAllConversationCounts:function(userId){var requests=[{methodname:"core_message_get_conversation_counts",args:{userid:userId}},{methodname:"core_message_get_unread_conversation_counts",args:{userid:userId}}];return $.when.apply(null,Ajax.call(requests)).then((function(total,unread){return{total:total,unread:unread}}))}}})); //# sourceMappingURL=message_repository.min.js.mapPKB\^#build/notification_processor.min.jsnu[/** * Represents the notification processor (e.g. email, popup, jabber) * * @module core_message/notification_processor * @copyright 2016 Ryan Wyllie * @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.mapPKB\T^^$build/notification_preference.min.jsnu[/** * Controls the preference for an individual notification type on the * message preference page. * * @module core_message/notification_preference * @copyright 2016 Ryan Wyllie * @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.mapPKB\[{6$build/message_preferences.min.js.mapnu[{"version":3,"file":"message_preferences.min.js","sources":["../src/message_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 .\n\n/**\n * Controls the message preference page.\n *\n * @module core_message/message_preferences\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/notification',\n 'core_message/message_notification_preference', 'core/custom_interaction_events'],\n function($, Ajax, Notification, MessageNotificationPreference, CustomEvents) {\n\n var SELECTORS = {\n PREFERENCE: '[data-state]',\n PREFERENCES_CONTAINER: '[data-region=\"preferences-container\"]',\n CONTACTABLE_PRIVACY_CONTAINER: '[data-region=\"privacy-setting-container\"]',\n };\n\n /**\n * Constructor for the MessagePreferences.\n *\n * @class\n * @param {object} element The root element for the message preferences\n */\n var MessagePreferences = function(element) {\n this.root = $(element);\n this.userId = this.root.find(SELECTORS.PREFERENCES_CONTAINER).attr('data-user-id');\n\n this.registerEventListeners();\n };\n\n /**\n * Check if the preferences have been disabled on this page.\n *\n * @method preferencesDisabled\n * @return {bool}\n */\n MessagePreferences.prototype.preferencesDisabled = function() {\n return this.root.find(SELECTORS.PREFERENCES_CONTAINER).hasClass('disabled');\n };\n\n /**\n * Update the contactable privacy user preference in the DOM and\n * send a request to update on the server.\n *\n * @return {Promise}\n * @method saveContactablePrivacySetting\n */\n MessagePreferences.prototype.saveContactablePrivacySetting = function() {\n var container = this.root.find(SELECTORS.CONTACTABLE_PRIVACY_CONTAINER);\n var value = $(\"input[type='radio']:checked\").val();\n\n if (container.hasClass('loading')) {\n return $.Deferred().resolve();\n }\n\n container.addClass('loading');\n\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n userid: this.userId,\n preferences: [\n {\n type: container.attr('data-preference-key'),\n value: value,\n }\n ]\n }\n };\n\n return Ajax.call([request])[0]\n .fail(Notification.exception)\n .always(function() {\n container.removeClass('loading');\n });\n };\n\n /**\n * Create all of the event listeners for the message preferences page.\n *\n * @method registerEventListeners\n */\n MessagePreferences.prototype.registerEventListeners = function() {\n CustomEvents.define(this.root, [\n CustomEvents.events.activate\n ]);\n\n this.root.on('change', function(e) {\n // Add listener for privacy setting radio buttons change.\n if (e.target.name == 'message_blocknoncontacts') {\n this.saveContactablePrivacySetting();\n } else {\n // Add listener for processor preferences.\n if (!this.preferencesDisabled()) {\n var preferencesContainer = $(e.target).closest(SELECTORS.PREFERENCES_CONTAINER);\n var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE);\n var messagePreference = new MessageNotificationPreference(preferencesContainer, this.userId);\n\n preferenceElement.addClass('loading');\n messagePreference.save().always(function() {\n preferenceElement.removeClass('loading');\n });\n }\n }\n }.bind(this));\n };\n\n return MessagePreferences;\n});\n"],"names":["define","$","Ajax","Notification","MessageNotificationPreference","CustomEvents","SELECTORS","MessagePreferences","element","root","userId","this","find","attr","registerEventListeners","prototype","preferencesDisabled","hasClass","saveContactablePrivacySetting","container","value","val","Deferred","resolve","addClass","request","methodname","args","userid","preferences","type","call","fail","exception","always","removeClass","events","activate","on","e","target","name","preferencesContainer","closest","preferenceElement","messagePreference","save","bind"],"mappings":";;;;;;;AAsBAA,0CAAO,CAAC,SAAU,YAAa,oBACvB,+CAAgD,mCAChD,SAASC,EAAGC,KAAMC,aAAcC,8BAA+BC,kBAE/DC,qBACY,eADZA,gCAEuB,wCAFvBA,wCAG+B,4CAS/BC,mBAAqB,SAASC,cACzBC,KAAOR,EAAEO,cACTE,OAASC,KAAKF,KAAKG,KAAKN,iCAAiCO,KAAK,qBAE9DC,iCASTP,mBAAmBQ,UAAUC,oBAAsB,kBACxCL,KAAKF,KAAKG,KAAKN,iCAAiCW,SAAS,aAUpEV,mBAAmBQ,UAAUG,8BAAgC,eACrDC,UAAYR,KAAKF,KAAKG,KAAKN,yCAC3Bc,MAAQnB,EAAE,+BAA+BoB,SAEzCF,UAAUF,SAAS,kBACZhB,EAAEqB,WAAWC,UAGxBJ,UAAUK,SAAS,eAEfC,QAAU,CACVC,WAAY,oCACZC,KAAM,CACFC,OAAQjB,KAAKD,OACbmB,YAAa,CACT,CACIC,KAAMX,UAAUN,KAAK,uBACrBO,MAAOA,iBAMhBlB,KAAK6B,KAAK,CAACN,UAAU,GACvBO,KAAK7B,aAAa8B,WAClBC,QAAO,WACJf,UAAUgB,YAAY,eASlC5B,mBAAmBQ,UAAUD,uBAAyB,WAClDT,aAAaL,OAAOW,KAAKF,KAAM,CAC3BJ,aAAa+B,OAAOC,gBAGnB5B,KAAK6B,GAAG,SAAU,SAASC,MAEP,4BAAjBA,EAAEC,OAAOC,UACJvB,yCAGAP,KAAKK,sBAAuB,KACzB0B,qBAAuBzC,EAAEsC,EAAEC,QAAQG,QAAQrC,iCAC3CsC,kBAAoB3C,EAAEsC,EAAEC,QAAQG,QAAQrC,sBACxCuC,kBAAoB,IAAIzC,8BAA8BsC,qBAAsB/B,KAAKD,QAErFkC,kBAAkBpB,SAAS,WAC3BqB,kBAAkBC,OAAOZ,QAAO,WAC5BU,kBAAkBT,YAAY,gBAI5CY,KAAKpC,QAGJJ"}PKB\))-build/message_drawer_view_conversation.min.jsnu[/** * Controls the conversation page in the message drawer. * * This function handles all of the user actions that the user can take * when interacting with the conversation page. * * It maintains a view state which is a data representation of the view * and only operates on that data. * * The view state is immutable and should never be modified directly. Instead * all changes to the view state should be done using the StateManager which * will generate a new version of the view state with the requested changes. * * After any changes to the view state the module will call the render function * to ask the renderer to update the UI. * * General rules for this module: * 1.) Never modify viewState directly. All changes should be via the StateManager. * 2.) Call render() with the new state when you want to update the UI * 3.) Never modify the UI directly in this module. This module is only concerned * with the data in the view state. * * The general flow for a user interaction will be something like: * User interaction: User clicks "confirm block" button to block the other user * 1.) This module is hears the click * 2.) This module sends a request to the server to block the user * 3.) The server responds with the new user profile * 4.) This module generates a new state using the StateManager with the updated * user profile. * 5.) This module asks the Patcher to generate a patch from the current state and * the newly generated state. This patch tells the renderer what has changed * between the states. * 6.) This module gives the Renderer the generated patch. The renderer updates * the UI with changes according to the patch. * * @module core_message/message_drawer_view_conversation * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_conversation",["jquery","core/auto_rows","core/backoff_timer","core/custom_interaction_events","core/notification","core/pending","core/pubsub","core/str","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_view_conversation_constants","core_message/message_drawer_view_conversation_patcher","core_message/message_drawer_view_conversation_renderer","core_message/message_drawer_view_conversation_state_manager","core_message/message_drawer_router","core_message/message_drawer_routes","core/emoji/auto_complete","core/emoji/picker"],(function($,AutoRows,BackOffTimer,CustomEvents,Notification,Pending,PubSub,Str,Repository,MessageDrawerEvents,Constants,Patcher,Renderer,StateManager,MessageDrawerRouter,MessageDrawerRoutes,initialiseEmojiAutoComplete,initialiseEmojiPicker){var stateCache={},viewState=null,loadedAllMessages=!1,messagesOffset=0,newMessagesPollTimer=null,isRendering=!1,renderBuffer=[],isResetting=!0,isSendingMessage=!1,isDeletingConversationContent=!1,sendMessageBuffer=[],render=null,renderers=[],NEWEST_FIRST=Constants.NEWEST_MESSAGES_FIRST,LOAD_MESSAGE_LIMIT=Constants.LOAD_MESSAGE_LIMIT,MILLISECONDS_IN_SEC=Constants.MILLISECONDS_IN_SEC,SELECTORS=Constants.SELECTORS,CONVERSATION_TYPES=Constants.CONVERSATION_TYPES,getOtherUserId=function(){if(!viewState||viewState.type==CONVERSATION_TYPES.PUBLIC)return null;var loggedInUserId=viewState.loggedInUserId;if(viewState.type==CONVERSATION_TYPES.SELF)return loggedInUserId;var otherUserIds=Object.keys(viewState.members).filter((function(userId){return loggedInUserId!=userId}));return otherUserIds.length?otherUserIds[0]:null},getMessagesOffset=function(){return messagesOffset},setMessagesOffset=function(value){messagesOffset=value,stateCache[viewState.id].messagesOffset=value},hasLoadedAllMessages=function(){return loadedAllMessages},setLoadedAllMessages=function(value){loadedAllMessages=value,stateCache[viewState.id].loadedAllMessages=value},formatConversationForEvent=function(state){return{id:state.id,name:state.name,subname:state.subname,imageUrl:state.imageUrl,isFavourite:state.isFavourite,isMuted:state.isMuted,type:state.type,totalMemberCount:state.totalMemberCount,loggedInUserId:state.loggedInUserId,messages:state.messages.map((function(message){return $.extend({},message)})),members:Object.keys(state.members).map((function(id){var formattedMember=$.extend({},state.members[id]);return formattedMember.contactrequests=state.members[id].contactrequests.map((function(request){return $.extend({},request)})),formattedMember}))}},updateStateFromConversation=function(conversation,loggedInUserId){var otherUser=null;if(conversation.type==CONVERSATION_TYPES.PRIVATE){var otherUsers=conversation.members.filter((function(member){return member.id!=loggedInUserId}));otherUser=otherUsers.length?otherUsers[0]:null}else conversation.type==CONVERSATION_TYPES.SELF&&(otherUser=conversation.members[0]);var name=conversation.name,imageUrl=conversation.imageurl;conversation.type!=CONVERSATION_TYPES.PUBLIC&&(name=name||otherUser?otherUser.fullname:"",imageUrl=imageUrl||otherUser?otherUser.profileimageurl:"");var newState=StateManager.addMembers(viewState,conversation.members);return newState=StateManager.setName(newState,name),newState=StateManager.setSubname(newState,conversation.subname),newState=StateManager.setType(newState,conversation.type),newState=StateManager.setImageUrl(newState,imageUrl),newState=StateManager.setTotalMemberCount(newState,conversation.membercount),newState=StateManager.setIsFavourite(newState,conversation.isfavourite),newState=StateManager.setIsMuted(newState,conversation.ismuted),newState=StateManager.addMessages(newState,conversation.messages),newState=StateManager.setCanDeleteMessagesForAllUsers(newState,conversation.candeletemessagesforallusers)},loadMessages=function(conversationId,limit,offset,newestFirst,ignoreList,timeFrom){return Repository.getMessages(viewState.loggedInUserId,conversationId,limit?limit+1:limit,offset,newestFirst,timeFrom).then((function(result){return result.id!=viewState.id&&(result.messages=[],result.id in stateCache&&delete stateCache[result.id]),result})).then((function(result){return result.messages.length&&ignoreList.length&&(result.messages=result.messages.filter((function(message){return ignoreList.indexOf(parseInt(message.id,10))<0}))),result})).then((function(result){return limit?(result.messages.length>limit?result.messages=result.messages.slice(0,-1):setLoadedAllMessages(!0),result):result})).then((function(result){var membersToAdd=result.members.filter((function(member){return!(member.id in viewState.members)})),newState=StateManager.addMembers(viewState,membersToAdd);return newState=StateManager.addMessages(newState,result.messages),newState=StateManager.setLoadingMessages(newState,!1),render(newState).then((function(){return result}))})).catch((function(error){var newState=StateManager.setLoadingMessages(viewState,!1);throw render(newState),error}))},markConversationAsRead=function(conversationId){var loggedInUserId=viewState.loggedInUserId,pendingPromise=new Pending("core_message/message_drawer_view_conversation:markConversationAsRead");return Repository.markAllConversationMessagesAsRead(loggedInUserId,conversationId).then((function(){var newState=StateManager.markMessagesAsRead(viewState,viewState.messages);return PubSub.publish(MessageDrawerEvents.CONVERSATION_READ,conversationId),render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},requestBlockUser=function(userId){cancelRequest(userId);var newState=StateManager.addPendingBlockUsersById(viewState,[userId]);render(newState)},blockUser=function(userId){var newState=StateManager.setLoadingConfirmAction(viewState,!0),pendingPromise=new Pending("core_message/message_drawer_view_conversation:blockUser");return render(newState),Repository.blockUser(viewState.loggedInUserId,userId).then((function(profile){var newState=StateManager.addMembers(viewState,[profile]);return newState=StateManager.removePendingBlockUsersById(newState,[userId]),newState=StateManager.setLoadingConfirmAction(newState,!1),PubSub.publish(MessageDrawerEvents.CONTACT_BLOCKED,userId),render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},requestUnblockUser=function(userId){cancelRequest(userId);var newState=StateManager.addPendingUnblockUsersById(viewState,[userId]);render(newState)},unblockUser=function(userId){var newState=StateManager.setLoadingConfirmAction(viewState,!0),pendingPromise=new Pending("core_message/message_drawer_view_conversation:unblockUser");return render(newState),Repository.unblockUser(viewState.loggedInUserId,userId).then((function(profile){var newState=StateManager.addMembers(viewState,[profile]);return newState=StateManager.removePendingUnblockUsersById(newState,[userId]),newState=StateManager.setLoadingConfirmAction(newState,!1),PubSub.publish(MessageDrawerEvents.CONTACT_UNBLOCKED,userId),render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},requestRemoveContact=function(userId){cancelRequest(userId);var newState=StateManager.addPendingRemoveContactsById(viewState,[userId]);render(newState)},removeContact=function(userId){var newState=StateManager.setLoadingConfirmAction(viewState,!0),pendingPromise=new Pending("core_message/message_drawer_view_conversation:removeContact");return render(newState),Repository.deleteContacts(viewState.loggedInUserId,[userId]).then((function(profiles){var newState=StateManager.addMembers(viewState,profiles);return newState=StateManager.removePendingRemoveContactsById(newState,[userId]),newState=StateManager.setLoadingConfirmAction(newState,!1),PubSub.publish(MessageDrawerEvents.CONTACT_REMOVED,userId),render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},requestAddContact=function(userId){cancelRequest(userId);var newState=StateManager.addPendingAddContactsById(viewState,[userId]);render(newState)},addContact=function(userId){var newState=StateManager.setLoadingConfirmAction(viewState,!0),pendingPromise=new Pending("core_message/message_drawer_view_conversation:addContactRequests");return render(newState),Repository.createContactRequest(viewState.loggedInUserId,userId).then((function(response){if(!response.request)throw new Error(response.warnings[0].message);return response.request})).then((function(request){var newState=StateManager.removePendingAddContactsById(viewState,[userId]);return newState=StateManager.addContactRequests(newState,[request]),newState=StateManager.setLoadingConfirmAction(newState,!1),render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},requestDeleteSelectedMessages=function(userId){var selectedMessageIds=viewState.selectedMessageIds;cancelRequest(userId);var newState=StateManager.addPendingDeleteMessagesById(viewState,selectedMessageIds);render(newState)},deleteSelectedMessages=function(){var pendingPromise=new Pending("core_message/message_drawer_view_conversation:deleteSelectedMessages"),messageIds=viewState.pendingDeleteMessageIds,sentMessages=viewState.messages.filter((function(message){return messageIds.indexOf(message.id)>=0&&("sent"==message.sendState||null===message.sendState)})),newState=StateManager.setLoadingConfirmAction(viewState,!0);render(newState);var deleteMessagesPromise=$.Deferred().resolve().promise();if(sentMessages.length){var sentMessageIds=sentMessages.map((function(message){return message.id}));deleteMessagesPromise=newState.deleteMessagesForAllUsers?Repository.deleteMessagesForAllUsers(viewState.loggedInUserId,sentMessageIds):Repository.deleteMessages(viewState.loggedInUserId,sentMessageIds)}return isDeletingConversationContent=!0,newMessagesPollTimer&&newMessagesPollTimer.stop(),deleteMessagesPromise.then((function(){var newState=StateManager.removeMessagesById(viewState,messageIds);newState=StateManager.removePendingDeleteMessagesById(newState,messageIds),newState=StateManager.removeSelectedMessagesById(newState,messageIds),newState=StateManager.setLoadingConfirmAction(newState,!1),newState=StateManager.setDeleteMessagesForAllUsers(newState,!1);var prevLastMessage=viewState.messages[viewState.messages.length-1],newLastMessage=newState.messages.length?newState.messages[newState.messages.length-1]:null;if(newLastMessage&&newLastMessage.id!=prevLastMessage.id){var conversation=formatConversationForEvent(newState);PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE,conversation)}else newState.messages.length||PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED,newState.id);return isDeletingConversationContent=!1,render(newState)})).then((function(result){return pendingPromise.resolve(),result})).catch(Notification.exception)},requestDeleteConversation=function(userId){cancelRequest(userId);var newState=StateManager.setPendingDeleteConversation(viewState,!0);render(newState)},deleteConversation=function(){var pendingPromise=new Pending("core_message/message_drawer_view_conversation:markConversationAsRead"),newState=StateManager.setLoadingConfirmAction(viewState,!0);return render(newState),isDeletingConversationContent=!0,newMessagesPollTimer&&newMessagesPollTimer.stop(),Repository.deleteConversation(viewState.loggedInUserId,viewState.id).then((function(){var newState=StateManager.removeMessages(viewState,viewState.messages);return newState=StateManager.removeSelectedMessagesById(newState,viewState.selectedMessageIds),newState=StateManager.setPendingDeleteConversation(newState,!1),newState=StateManager.setLoadingConfirmAction(newState,!1),PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED,newState.id),isDeletingConversationContent=!1,render(newState)})).then((function(result){return pendingPromise.resolve(),result}))},cancelRequest=function(userId){var pendingDeleteMessageIds=viewState.pendingDeleteMessageIds,newState=StateManager.removePendingAddContactsById(viewState,[userId]);newState=StateManager.removePendingRemoveContactsById(newState,[userId]),newState=StateManager.removePendingUnblockUsersById(newState,[userId]),newState=StateManager.removePendingBlockUsersById(newState,[userId]),newState=StateManager.removePendingDeleteMessagesById(newState,pendingDeleteMessageIds),newState=StateManager.setPendingDeleteConversation(newState,!1),newState=StateManager.setDeleteMessagesForAllUsers(newState,!1),render(newState)},acceptContactRequest=function(userId){var pendingPromise=new Pending("core_message/message_drawer_view_conversation:acceptContactRequest"),loggedInUserId=viewState.loggedInUserId,requests=viewState.members[userId].contactrequests.filter((function(request){return request.requesteduserid==loggedInUserId})),request=requests[0],newState=StateManager.setLoadingConfirmAction(viewState,!0);return render(newState),Repository.acceptContactRequest(userId,loggedInUserId).then((function(profile){var newState=StateManager.removeContactRequests(viewState,[request]);return newState=StateManager.addMembers(viewState,[profile]),newState=StateManager.setLoadingConfirmAction(newState,!1),render(newState)})).then((function(){PubSub.publish(MessageDrawerEvents.CONTACT_ADDED,viewState.members[userId]),PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED,request)})).then((function(result){return pendingPromise.resolve(),result}))},declineContactRequest=function(userId){var pendingPromise=new Pending("core_message/message_drawer_view_conversation:declineContactRequest"),loggedInUserId=viewState.loggedInUserId,requests=viewState.members[userId].contactrequests.filter((function(request){return request.requesteduserid==loggedInUserId})),request=requests[0],newState=StateManager.setLoadingConfirmAction(viewState,!0);return render(newState),Repository.declineContactRequest(userId,loggedInUserId).then((function(profile){var newState=StateManager.removeContactRequests(viewState,[request]);return newState=StateManager.addMembers(viewState,[profile]),newState=StateManager.setLoadingConfirmAction(newState,!1),render(newState)})).then((function(){PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_DECLINED,request)})).then((function(result){return pendingPromise.resolve(),result}))},processSendMessageBuffer=function(){if(!isSendingMessage&&sendMessageBuffer.length){var pendingPromise=new Pending("core_message/message_drawer_view_conversation:processSendMessageBuffer");isSendingMessage=!0;var messagesToSend=sendMessageBuffer.slice();sendMessageBuffer=[];var conversationId=viewState.id,newConversationId=null,messagesText=messagesToSend.map((function(message){return message.text})),messageIds=messagesToSend.map((function(message){return message.id})),sendMessagePromise=null,newCanDeleteMessagesForAllUsers=null;if(conversationId||viewState.type==CONVERSATION_TYPES.PUBLIC)sendMessagePromise=Repository.sendMessagesToConversation(conversationId,messagesText);else{var otherUserId=getOtherUserId();sendMessagePromise=Repository.sendMessagesToUser(otherUserId,messagesText).then((function(messages){return messages.length&&(newConversationId=parseInt(messages[0].conversationid,10),newCanDeleteMessagesForAllUsers=messages[0].candeletemessagesforallusers),messages}))}sendMessagePromise.then((function(messages){var newMessageIds=messages.map((function(message){return message.id})),data=[],selectedToRemove=[],selectedToAdd=[];messagesToSend.forEach((function(oldMessage,index){var newMessage=messages[index];data.push([oldMessage,newMessage]),viewState.selectedMessageIds.indexOf(oldMessage.id)>=0&&(selectedToRemove.push(oldMessage.id),selectedToAdd.push(newMessage.id))}));var newState=StateManager.updateMessages(viewState,data);newState=StateManager.setMessagesSendSuccessById(newState,newMessageIds),selectedToRemove.length&&(newState=StateManager.removeSelectedMessagesById(newState,selectedToRemove)),selectedToAdd.length&&(newState=StateManager.addSelectedMessagesById(newState,selectedToAdd));var conversation=formatConversationForEvent(newState);newState.id||(newState=StateManager.setId(newState,newConversationId),conversation.id=newConversationId,resetMessagePollTimer(newConversationId),PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED,conversation),newState=StateManager.setCanDeleteMessagesForAllUsers(newState,newCanDeleteMessagesForAllUsers)),render(newState),isSendingMessage=!1,processSendMessageBuffer(),PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE,conversation)})).then((function(result){return pendingPromise.resolve(),result})).catch((function(e){var errorMessage;errorMessage=e.message?$.Deferred().resolve(e.message).promise():Str.get_string("unknownerror","core");var handleFailedMessages=function(errorMessage){var newState=StateManager.setMessagesSendFailById(viewState,messageIds,errorMessage);render(newState),isSendingMessage=!1,processSendMessageBuffer()};errorMessage.then(handleFailedMessages).then((function(result){return pendingPromise.resolve(),result})).catch((function(e){var finalError=e.message||"Something went wrong!";handleFailedMessages(finalError)}))}))}};const previewText=function(text){let plaintext=text.replace(//gi,"");return plaintext=plaintext.replace(//gi,""),plaintext=plaintext.replace(/<\/div>/gi,"\n"),plaintext=plaintext.replace(/<\/li>/gi,"\n"),plaintext=plaintext.replace(/
  • /gi," * "),plaintext=plaintext.replace(/<\/ul>/gi,"\n"),plaintext=plaintext.replace(/<\/p>/gi,"\n"),plaintext=plaintext.replace(/]*>/gi,"\n"),plaintext=plaintext.replace(/<[^>]+>/gi,""),plaintext=plaintext.replace(/\n+/gi,"\n"),plaintext.replace(/\n/gi,"
    ")};var processRenderBuffer=function(header,body,footer){if(!isRendering&&renderBuffer.length){isRendering=!0;var renderable=renderBuffer.shift(),renderPromises=renderers.map((function(renderFunc){return renderFunc(renderable.patch)}));$.when.apply(null,renderPromises).then((function(){isRendering=!1,renderable.deferred.resolve(!0),processRenderBuffer(header,body,footer)})).catch((function(error){isRendering=!1,renderable.deferred.reject(error),Notification.exception(error)}))}},generateConfirmActionHandler=function(actionCallback){return function(e,data){if(!viewState.loadingConfirmAction){actionCallback(getOtherUserId());var newState=StateManager.setLoadingConfirmAction(viewState,!1);render(newState)}data.originalEvent.preventDefault()}},handleSendMessage=function(e,data){var textArea=$(e.target).closest(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.MESSAGE_TEXT_AREA),text=textArea.val().trim();""!==text&&(!function(text){var id="temp"+Date.now();let loadingmessage={id:id,useridfrom:viewState.loggedInUserId,text:previewText(text),timecreated:null};var newState=StateManager.addMessages(viewState,[loadingmessage]);render(newState);var message={id:id,useridfrom:viewState.loggedInUserId,text:text,timecreated:null};sendMessageBuffer.push(message),processSendMessageBuffer();const textArea=document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);textArea&&textArea.removeAttribute("data-unsent-message-viewed")}(text),textArea.val(""),textArea.focus()),data.originalEvent.preventDefault()},handleSelectMessage=function(e,data){var selection=window.getSelection(),target=$(e.target);""==selection.toString()&&(target.is("a")||(!function(messageId){var newState=viewState;newState=viewState.selectedMessageIds.indexOf(messageId)>-1?StateManager.removeSelectedMessagesById(viewState,[messageId]):StateManager.addSelectedMessagesById(viewState,[messageId]),render(newState)}(target.closest(SELECTORS.MESSAGE).attr("data-message-id")),data.originalEvent.preventDefault()))},handleRetrySendMessage=function(e,data){var messageId=$(e.target).closest(SELECTORS.MESSAGE).attr("data-message-id"),messages=viewState.messages.filter((function(message){return message.id==messageId})),message=messages.length?messages[0]:null;message&&function(message){var newState=StateManager.setMessagesSendPendingById(viewState,[message.id]);render(newState),sendMessageBuffer.push(message),processSendMessageBuffer()}(message),data.originalEvent.preventDefault(),data.originalEvent.stopPropagation(),e.stopPropagation()},handleCancelEditMode=function(e,data){!function(){cancelRequest(getOtherUserId());var newState=StateManager.removeSelectedMessagesById(viewState,viewState.selectedMessageIds);render(newState)}(),data.originalEvent.preventDefault()},generateHandleViewContact=function(namespace){return function(e,data){var otherUserId=getOtherUserId(),otherUser=viewState.members[otherUserId];MessageDrawerRouter.go(namespace,MessageDrawerRoutes.VIEW_CONTACT,otherUser),data.originalEvent.preventDefault()}},handleSetFavourite=function(e,data){var userId,conversationId,pendingPromise;(userId=viewState.loggedInUserId,conversationId=viewState.id,pendingPromise=new Pending("core_message/message_drawer_view_conversation:setFavourite"),Repository.setFavouriteConversations(userId,[conversationId]).then((function(){var newState=StateManager.setIsFavourite(viewState,!0);return render(newState)})).then((function(){return PubSub.publish(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE,formatConversationForEvent(viewState))})).then((function(result){return pendingPromise.resolve(),result}))).catch(Notification.exception),data.originalEvent.preventDefault()},handleUnsetFavourite=function(e,data){var userId,conversationId,pendingPromise;(userId=viewState.loggedInUserId,conversationId=viewState.id,pendingPromise=new Pending("core_message/message_drawer_view_conversation:unsetFavourite"),Repository.unsetFavouriteConversations(userId,[conversationId]).then((function(){var newState=StateManager.setIsFavourite(viewState,!1);return render(newState)})).then((function(){return PubSub.publish(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE,formatConversationForEvent(viewState))})).then((function(result){return pendingPromise.resolve(),result}))).catch(Notification.exception),data.originalEvent.preventDefault()},handleSetMuted=function(e,data){var userId,conversationId,pendingPromise;(userId=viewState.loggedInUserId,conversationId=viewState.id,pendingPromise=new Pending("core_message/message_drawer_view_conversation:markConversationAsRead"),Repository.setMutedConversations(userId,[conversationId]).then((function(){var newState=StateManager.setIsMuted(viewState,!0);return render(newState)})).then((function(){return PubSub.publish(MessageDrawerEvents.CONVERSATION_SET_MUTED,formatConversationForEvent(viewState))})).then((function(result){return pendingPromise.resolve(),result}))).catch(Notification.exception),data.originalEvent.preventDefault()},handleUnsetMuted=function(e,data){var userId,conversationId;(userId=viewState.loggedInUserId,conversationId=viewState.id,Repository.unsetMutedConversations(userId,[conversationId]).then((function(){var newState=StateManager.setIsMuted(viewState,!1);return render(newState)})).then((function(){return PubSub.publish(MessageDrawerEvents.CONVERSATION_UNSET_MUTED,formatConversationForEvent(viewState))}))).catch(Notification.exception),data.originalEvent.preventDefault()},handleDeleteMessagesForAllUsersToggle=function(e){var newValue=$(e.target).prop("checked"),newState=StateManager.setDeleteMessagesForAllUsers(viewState,newValue);render(newState)},generateHandleViewGroupInfo=function(namespace){return function(e,data){MessageDrawerRouter.go(namespace,MessageDrawerRoutes.VIEW_GROUP_INFO,{id:viewState.id,name:viewState.name,subname:viewState.subname,imageUrl:viewState.imageUrl,totalMemberCount:viewState.totalMemberCount},viewState.loggedInUserId),data.originalEvent.preventDefault()}},handleToggleEmojiPicker=function(e,data){var newState=StateManager.setShowEmojiPicker(viewState,!viewState.showEmojiPicker);render(newState),data.originalEvent.preventDefault()},handleCloseEmojiPicker=function(e){var target=$(e.target);if(viewState.showEmojiPicker&&!target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length&&!target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length){var newState=StateManager.setShowEmojiPicker(viewState,!1);render(newState)}},registerEventListeners=function(namespace,header,body,footer){var isLoadingMoreMessages=!1,messagesContainer=function(body){return body.find(SELECTORS.MESSAGES_CONTAINER)}(body),emojiPickerElement=footer.find(SELECTORS.EMOJI_PICKER),emojiAutoCompleteContainer=footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER),messageTextArea=footer.find(SELECTORS.MESSAGE_TEXT_AREA),headerActivateHandlers=[[SELECTORS.ACTION_REQUEST_BLOCK,generateConfirmActionHandler(requestBlockUser)],[SELECTORS.ACTION_REQUEST_UNBLOCK,generateConfirmActionHandler(requestUnblockUser)],[SELECTORS.ACTION_REQUEST_ADD_CONTACT,generateConfirmActionHandler(requestAddContact)],[SELECTORS.ACTION_REQUEST_REMOVE_CONTACT,generateConfirmActionHandler(requestRemoveContact)],[SELECTORS.ACTION_REQUEST_DELETE_CONVERSATION,generateConfirmActionHandler(requestDeleteConversation)],[SELECTORS.ACTION_CANCEL_EDIT_MODE,handleCancelEditMode],[SELECTORS.ACTION_VIEW_CONTACT,generateHandleViewContact(namespace)],[SELECTORS.ACTION_VIEW_GROUP_INFO,generateHandleViewGroupInfo(namespace)],[SELECTORS.ACTION_CONFIRM_FAVOURITE,handleSetFavourite],[SELECTORS.ACTION_CONFIRM_MUTE,handleSetMuted],[SELECTORS.ACTION_CONFIRM_UNFAVOURITE,handleUnsetFavourite],[SELECTORS.ACTION_CONFIRM_UNMUTE,handleUnsetMuted]],bodyActivateHandlers=[[SELECTORS.ACTION_CANCEL_CONFIRM,generateConfirmActionHandler(cancelRequest)],[SELECTORS.ACTION_CONFIRM_BLOCK,generateConfirmActionHandler(blockUser)],[SELECTORS.ACTION_CONFIRM_UNBLOCK,generateConfirmActionHandler(unblockUser)],[SELECTORS.ACTION_CONFIRM_ADD_CONTACT,generateConfirmActionHandler(addContact)],[SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT,generateConfirmActionHandler(removeContact)],[SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES,generateConfirmActionHandler(deleteSelectedMessages)],[SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION,generateConfirmActionHandler(deleteConversation)],[SELECTORS.ACTION_OKAY_CONFIRM,generateConfirmActionHandler(cancelRequest)],[SELECTORS.ACTION_REQUEST_ADD_CONTACT,generateConfirmActionHandler(requestAddContact)],[SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,generateConfirmActionHandler(acceptContactRequest)],[SELECTORS.ACTION_DECLINE_CONTACT_REQUEST,generateConfirmActionHandler(declineContactRequest)],[SELECTORS.MESSAGE,handleSelectMessage],[SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE,handleDeleteMessagesForAllUsersToggle],[SELECTORS.RETRY_SEND,handleRetrySendMessage]],footerActivateHandlers=[[SELECTORS.SEND_MESSAGE_BUTTON,handleSendMessage],[SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON,handleToggleEmojiPicker],[SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES,generateConfirmActionHandler(requestDeleteSelectedMessages)],[SELECTORS.ACTION_REQUEST_ADD_CONTACT,generateConfirmActionHandler(requestAddContact)],[SELECTORS.ACTION_REQUEST_UNBLOCK,generateConfirmActionHandler(requestUnblockUser)]];AutoRows.init(footer),emojiAutoCompleteContainer.length&&initialiseEmojiAutoComplete(emojiAutoCompleteContainer[0],messageTextArea[0],(function(hasSuggestions){var newState=StateManager.setShowEmojiAutoComplete(viewState,hasSuggestions);render(newState)}),(function(emoji){var newState=StateManager.setShowEmojiAutoComplete(viewState,!1);render(newState),messageTextArea.focus();var cursorPos=messageTextArea.prop("selectionStart"),currentText=messageTextArea.val(),textBefore=currentText.substring(0,cursorPos).replace(/\S*$/,""),textAfter=currentText.substring(cursorPos).replace(/^\S*/,"");messageTextArea.val(textBefore+emoji+textAfter),messageTextArea.prop("selectionStart",textBefore.length+emoji.length),messageTextArea.prop("selectionEnd",textBefore.length+emoji.length)})),emojiPickerElement.length&&initialiseEmojiPicker(emojiPickerElement[0],(function(emoji){var newState=StateManager.setShowEmojiPicker(viewState,!viewState.showEmojiPicker);render(newState),messageTextArea.focus();var cursorPos=messageTextArea.prop("selectionStart"),currentText=messageTextArea.val(),textBefore=currentText.substring(0,cursorPos),textAfter=currentText.substring(cursorPos,currentText.length);messageTextArea.val(textBefore+emoji+textAfter),messageTextArea.prop("selectionStart",cursorPos+emoji.length),messageTextArea.prop("selectionEnd",cursorPos+emoji.length)})),CustomEvents.define(header,[CustomEvents.events.activate]),CustomEvents.define(body,[CustomEvents.events.activate]),CustomEvents.define(footer,[CustomEvents.events.activate,CustomEvents.events.enter,CustomEvents.events.escape]),CustomEvents.define(messagesContainer,[CustomEvents.events.scrollTop,CustomEvents.events.scrollLock]),messagesContainer.on(CustomEvents.events.scrollTop,(function(e,data){var hasMembers=Object.keys(viewState.members).length>1;if(!isResetting&&!isLoadingMoreMessages&&!hasLoadedAllMessages()&&hasMembers){isLoadingMoreMessages=!0;var newState=StateManager.setLoadingMessages(viewState,!0);render(newState),loadMessages(viewState.id,LOAD_MESSAGE_LIMIT,getMessagesOffset(),NEWEST_FIRST,[]).then((function(){isLoadingMoreMessages=!1,setMessagesOffset(getMessagesOffset()+LOAD_MESSAGE_LIMIT)})).catch((function(error){isLoadingMoreMessages=!1,Notification.exception(error)}))}data.originalEvent.preventDefault()})),headerActivateHandlers.forEach((function(handler){var selector=handler[0],handlerFunction=handler[1];header.on(CustomEvents.events.activate,selector,handlerFunction)})),bodyActivateHandlers.forEach((function(handler){var selector=handler[0],handlerFunction=handler[1];body.on(CustomEvents.events.activate,selector,handlerFunction)})),footerActivateHandlers.forEach((function(handler){var selector=handler[0],handlerFunction=handler[1];footer.on(CustomEvents.events.activate,selector,handlerFunction)})),footer.on(CustomEvents.events.enter,SELECTORS.MESSAGE_TEXT_AREA,(function(e,data){var enterToSend=footer.attr("data-enter-to-send");enterToSend&&"false"!=enterToSend&&"0"!=enterToSend&&handleSendMessage(e,data)})),footer.on(CustomEvents.events.escape,SELECTORS.EMOJI_PICKER_CONTAINER,handleToggleEmojiPicker),$(document.body).on("click",handleCloseEmojiPicker),PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED,(function(newRouteData){newMessagesPollTimer&&newRouteData.route!=MessageDrawerRoutes.VIEW_CONVERSATION&&newMessagesPollTimer.stop()}))},resetMessagePollTimer=function(conversationId){newMessagesPollTimer&&newMessagesPollTimer.stop(),newMessagesPollTimer=new BackOffTimer(function(conversationId,newestFirst){return function(){var messages=viewState.messages,mostRecentMessage=messages.length?messages[messages.length-1]:null,lastTimeCreated=mostRecentMessage?mostRecentMessage.timeCreated:null;if(lastTimeCreated&&!isResetting&&!isSendingMessage&&!isDeletingConversationContent){for(var ignoreMessageIds=[],i=messages.length-1;i>=0;i--){var message=messages[i];if(message.timeCreated!==lastTimeCreated)break;ignoreMessageIds.push(message.id)}return loadMessages(conversationId,0,0,newestFirst,ignoreMessageIds,lastTimeCreated).then((function(result){if(result.messages.length){newMessagesPollTimer.restart();var conversation=formatConversationForEvent(viewState);return PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE,conversation),markConversationAsRead(conversationId)}return result}))}return $.Deferred().resolve().promise()}}(conversationId,NEWEST_FIRST),BackOffTimer.getIncrementalCallback(viewState.messagePollMin*MILLISECONDS_IN_SEC,MILLISECONDS_IN_SEC,viewState.messagePollMax*MILLISECONDS_IN_SEC,viewState.messagePollAfterMax*MILLISECONDS_IN_SEC)),newMessagesPollTimer.start()},resetState=function(body,conversationId,loggedInUserProfile){newMessagesPollTimer&&newMessagesPollTimer.stop(),loadedAllMessages=!1,messagesOffset=0,newMessagesPollTimer=null,isRendering=!1,renderBuffer=[],isResetting=!0,isSendingMessage=!1,isDeletingConversationContent=!1,sendMessageBuffer=[];var loggedInUserId=loggedInUserProfile.id,midnight=parseInt(body.attr("data-midnight"),10),messagePollMin=parseInt(body.attr("data-message-poll-min"),10),messagePollMax=parseInt(body.attr("data-message-poll-max"),10),messagePollAfterMax=parseInt(body.attr("data-message-poll-after-max"),10),initialState=StateManager.buildInitialState(midnight,loggedInUserId,conversationId,messagePollMin,messagePollMax,messagePollAfterMax);viewState||(viewState=initialState),render(initialState)},resetNoConversation=function(body,loggedInUserProfile,otherUserId){resetState(body,null,loggedInUserProfile);return(loggedInUserProfile.id!=otherUserId?Repository.getConversationBetweenUsers(loggedInUserProfile.id,otherUserId,!0,!0,0,0,LOAD_MESSAGE_LIMIT,0,NEWEST_FIRST):Repository.getSelfConversation(loggedInUserProfile.id,LOAD_MESSAGE_LIMIT,0,NEWEST_FIRST)).then((function(conversation){return resetByConversation(body,conversation,loggedInUserProfile)})).catch((function(){return function(loggedInUserProfile,otherUserId){var loggedInUserId=loggedInUserProfile.id,conversationType=loggedInUserId==otherUserId?CONVERSATION_TYPES.SELF:CONVERSATION_TYPES.PRIVATE,newState=StateManager.setLoadingMembers(viewState,!0);return newState=StateManager.setLoadingMessages(newState,!0),render(newState),Repository.getMemberInfo(loggedInUserId,[otherUserId],!0,!0).then((function(profiles){if(profiles.length)return profiles[0];throw new Error("Unable to load other user profile")})).then((function(profile){var members=conversationType==CONVERSATION_TYPES.SELF?[profile]:[profile,loggedInUserProfile],newState=StateManager.addMembers(viewState,members);return newState=StateManager.setLoadingMembers(newState,!1),newState=StateManager.setLoadingMessages(newState,!1),newState=StateManager.setName(newState,profile.fullname),newState=StateManager.setType(newState,conversationType),newState=StateManager.setImageUrl(newState,profile.profileimageurl),newState=StateManager.setTotalMemberCount(newState,members.length),render(newState),profile})).catch((function(error){var newState=StateManager.setLoadingMembers(viewState,!1);render(newState),Notification.exception(error)}))}(loggedInUserProfile,otherUserId)}))},resetById=function(body,conversationId,loggedInUserProfile){var cache=null;conversationId in stateCache&&(cache=stateCache[conversationId]),resetState(body,conversationId,loggedInUserProfile);var promise=$.Deferred().resolve({}).promise();if(cache){var newState=cache.state;newState=StateManager.setLoadingMessages(newState,!1),newState=StateManager.setLoadingMembers(newState,!1),setMessagesOffset(cache.messagesOffset),setLoadedAllMessages(cache.loadedAllMessages),render(newState)}else promise=function(conversationId,loggedInUserProfile,messageLimit,messageOffset,newestFirst){var loggedInUserId=loggedInUserProfile.id,newState=StateManager.setLoadingMembers(viewState,!0);return newState=StateManager.setLoadingMessages(newState,!0),render(newState),Repository.getConversation(loggedInUserId,conversationId,!0,!0,0,0,messageLimit+1,messageOffset,newestFirst).then((function(conversation){return conversation.messages.length>messageLimit?conversation.messages=conversation.messages.slice(1):setLoadedAllMessages(!0),setMessagesOffset(messageOffset+messageLimit),conversation})).then((function(conversation){conversation.members.filter((function(member){return member.id==loggedInUserProfile.id})).length<1&&(conversation.members=conversation.members.concat([loggedInUserProfile]));var newState=updateStateFromConversation(conversation,loggedInUserProfile.id);return newState=StateManager.setLoadingMembers(newState,!1),newState=StateManager.setLoadingMessages(newState,!1),render(newState).then((function(){return conversation}))})).then((function(){return markConversationAsRead(conversationId)})).catch((function(error){var newState=StateManager.setLoadingMembers(viewState,!1);newState=StateManager.setLoadingMessages(newState,!1),render(newState),Notification.exception(error)}))}(conversationId,loggedInUserProfile,LOAD_MESSAGE_LIMIT,0,NEWEST_FIRST);return promise.then((function(){return resetMessagePollTimer(conversationId)}))},resetByConversation=function(body,conversation,loggedInUserProfile){var cache=null;conversation.id in stateCache&&(cache=stateCache[conversation.id]),resetState(body,conversation.id,loggedInUserProfile);var promise=$.Deferred().resolve({}).promise();if(cache){var newState=cache.state;newState=StateManager.setLoadingMessages(newState,!1),newState=StateManager.setLoadingMembers(newState,!1),setMessagesOffset(cache.messagesOffset),setLoadedAllMessages(cache.loadedAllMessages),render(newState)}else promise=function(conversation,loggedInUserProfile,messageLimit,newestFirst){conversation.members.filter((function(member){return member.id==loggedInUserProfile.id})).length<1&&(conversation.members=conversation.members.concat([loggedInUserProfile]));var messageCount=conversation.messages.length,hasLoadedEnoughMessages=messageCount>=messageLimit,newState=updateStateFromConversation(conversation,loggedInUserProfile.id);return newState=StateManager.setLoadingMembers(newState,!1),newState=StateManager.setLoadingMessages(newState,!hasLoadedEnoughMessages),render(newState).then((function(){return hasLoadedEnoughMessages?{messages:conversation.messages}:loadMessages(conversation.id,messageLimit,messageCount,newestFirst,[])})).then((function(){var messages=viewState.messages;return setMessagesOffset(messages.length),markConversationAsRead(viewState.id),messages})).catch(Notification.exception)}(conversation,loggedInUserProfile,LOAD_MESSAGE_LIMIT,NEWEST_FIRST);return promise.then((function(){return resetMessagePollTimer(conversation.id)}))};const setConversationAttributes=function(element,conversationId,otherUserId){element.removeAttr("data-conversation-id"),element.removeAttr("data-other-user-id"),conversationId?element.attr("data-conversation-id",conversationId):otherUserId&&element.attr("data-other-user-id",otherUserId)};return{show:function(namespace,header,body,footer,conversationOrId,action,otherUserId){var userId,conversation=null,conversationId=null;conversationOrId&&null!==conversationOrId&&"object"==typeof conversationOrId?(conversation=conversationOrId,conversationId=parseInt(conversation.id,10)):(conversation=null,conversationId=parseInt(conversationOrId,10),conversationId=isNaN(conversationId)?null:conversationId),!conversationId&&action&&otherUserId&&(userId=otherUserId,conversationId=Object.keys(stateCache).reduce((function(carry,id){if(!carry){var state=stateCache[id].state;state.type!=CONVERSATION_TYPES.PUBLIC&&userId in state.members&&(carry=state.id)}return carry}),null)),setConversationAttributes(footer,conversationId,otherUserId);var isNewConversation=!viewState||viewState.id!=conversationId||otherUserId&&otherUserId!=getOtherUserId();if(body.attr("data-init")||(render=function(header,body,footer,isNewConversation){var rendererFunc=function(patch){return Renderer.render(header,body,footer,patch)};if(!isNewConversation){var initialState=StateManager.buildInitialState(viewState.midnight,viewState.loggedInUserId,viewState.id);rendererFunc(Patcher.buildPatch(initialState,viewState))}return renderers.push(rendererFunc),function(newState){var patch=Patcher.buildPatch(viewState,newState),deferred=$.Deferred();return Object.keys(patch).length?renderBuffer.push({patch:patch,deferred:deferred}):deferred.resolve(!0),viewState=newState,newState.id&&(stateCache[newState.id]={state:newState,messagesOffset:getMessagesOffset(),loadedAllMessages:hasLoadedAllMessages()}),processRenderBuffer(header,body,footer),deferred.promise()}}(header,body,footer,isNewConversation),registerEventListeners(namespace,header,body,footer),body.attr("data-init",!0)),isNewConversation){var loggedInUserProfile=function(body){return{id:parseInt(body.attr("data-user-id"),10),fullname:null,profileimageurl:null,profileimageurlsmall:null,isonline:null,showonlinestatus:null,isblocked:null,iscontact:null,isdeleted:null,canmessage:null,canmessageevenifblocked:null,requirescontact:null,contactrequests:[],cancreatecontact:null}}(body);return(conversation?resetByConversation(body,conversation,loggedInUserProfile,otherUserId):conversationId?resetById(body,conversationId,loggedInUserProfile):resetNoConversation(body,loggedInUserProfile,otherUserId)).then((function(){isResetting=!1,header.find(Constants.SELECTORS.CAN_RECEIVE_FOCUS).first().focus()})).catch((function(error){isResetting=!1,Notification.exception(error)}))}if(resetMessagePollTimer(conversationId),viewState.type==CONVERSATION_TYPES.PRIVATE&&action){var currentOtherUserId=getOtherUserId();switch(action){case"block":return requestBlockUser(currentOtherUserId);case"unblock":return requestUnblockUser(currentOtherUserId);case"add-contact":return requestAddContact(currentOtherUserId);case"remove-contact":return requestRemoveContact(currentOtherUserId)}}return $.Deferred().resolve().promise()},description:function(){return Str.get_string("messagedrawerviewconversation","core_message",viewState.name)}}})); //# sourceMappingURL=message_drawer_view_conversation.min.js.mapPKB\c_u2u2.build/message_drawer_lazy_load_list.min.js.mapnu[{"version":3,"file":"message_drawer_lazy_load_list.min.js","sources":["../src/message_drawer_lazy_load_list.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Lazy loaded list of items.\n *\n * @module core_message/message_drawer_lazy_load_list\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pending',\n],\nfunction(\n $,\n CustomEvents,\n PendingPromise,\n) {\n\n var SELECTORS = {\n ROOT: '[data-region=\"lazy-load-list\"]',\n LOADING_ICON_CONTAINER: '[data-region=\"loading-icon-container\"]',\n CONTENT_CONTAINER: '[data-region=\"content-container\"]',\n EMPTY_MESSAGE: '[data-region=\"empty-message-container\"]',\n PLACEHOLDER: '[data-region=\"placeholder-container\"]'\n };\n\n /**\n * Flag element as loading.\n *\n * @param {Object} root The section container element.\n */\n var startLoading = function(root) {\n root.attr('data-loading', true);\n };\n\n /**\n * Flag element as not loading.\n *\n * @param {Object} root The section container element.\n */\n var stopLoading = function(root) {\n root.attr('data-loading', false);\n };\n\n /**\n * Check if the element is loading.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var isLoading = function(root) {\n return root.attr('data-loading') === 'true';\n };\n\n /**\n * Get user id\n *\n * @param {Object} root The section container element.\n * @return {Number} Logged in user id.\n */\n var getUserId = function(root) {\n return root.attr('data-user-id');\n };\n\n /**\n * Get the section content container element.\n *\n * @param {Object} root The section container element.\n * @return {Object} The section content container element.\n */\n var getContentContainer = function(root) {\n return root.find(SELECTORS.CONTENT_CONTAINER);\n };\n\n /**\n * Get the root element.\n *\n * @param {Object} containerElement The container element to search in.\n * @return {Object} The list root element.\n */\n var getRoot = function(containerElement) {\n return containerElement.find(SELECTORS.ROOT);\n };\n\n /**\n * Show the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var showLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the loading icon.\n *\n * @param {Object} root The section container element.\n */\n var hideLoadingIcon = function(root) {\n root.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the empty message.\n *\n * @param {Object} root The section container element.\n */\n var showEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).removeClass('hidden');\n };\n\n /**\n * Hide the empty message.\n *\n * @param {Object} root The section container element.\n */\n var hideEmptyMessage = function(root) {\n root.find(SELECTORS.EMPTY_MESSAGE).addClass('hidden');\n };\n\n /**\n * Show the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var showPlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).removeClass('hidden');\n };\n\n /**\n * Hide the placeholder element.\n *\n * @param {Object} root The section container element.\n */\n var hidePlaceholder = function(root) {\n root.find(SELECTORS.PLACEHOLDER).addClass('hidden');\n };\n\n /**\n * Show the section content container.\n *\n * @param {Object} root The section container element.\n */\n var showContent = function(root) {\n getContentContainer(root).removeClass('hidden');\n };\n\n /**\n * Hide the section content container.\n *\n * @param {Object} root The section container element.\n */\n var hideContent = function(root) {\n getContentContainer(root).addClass('hidden');\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var hasLoadedAll = function(root) {\n return root.attr('data-loaded-all') == 'true';\n };\n\n /**\n * If the section has loaded all content.\n *\n * @param {Object} root The section container element.\n * @param {Bool} value If all items have been loaded.\n */\n var setLoadedAll = function(root, value) {\n root.attr('data-loaded-all', value);\n };\n\n /**\n * If the section can load more items.\n *\n * @param {Object} root The section container element.\n * @return {Bool}\n */\n var canLoadMore = function(root) {\n return !hasLoadedAll(root) && !isLoading(root);\n };\n\n /**\n * Load all items in this container from callback and render them.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} jQuery promise\n */\n var loadAndRender = function(root, loadCallback, renderCallback) {\n var userId = getUserId(root);\n startLoading(root);\n\n return loadCallback(root, userId)\n .then(function(items) {\n if (items.length > 0) {\n var contentContainer = getContentContainer(root);\n return renderCallback(contentContainer, items, userId)\n .then(function() {\n return items;\n });\n } else {\n return items;\n }\n })\n .then(function(items) {\n stopLoading(root);\n root.attr('data-seen', true);\n\n if (!items.length) {\n setLoadedAll(root, true);\n }\n\n return items;\n })\n .catch(function() {\n stopLoading(root);\n root.attr('data-seen', true);\n return;\n });\n };\n\n /**\n * First load of this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n * @return {Object} promise\n */\n var initialLoadAndRender = function(root, loadCallback, renderCallback) {\n const pendingPromise = new PendingPromise('initialLoadAndRender');\n getContentContainer(root).empty();\n showPlaceholder(root);\n hideContent(root);\n return loadAndRender(root, loadCallback, renderCallback)\n .then(function(items) {\n hidePlaceholder(root);\n\n if (!items.length) {\n showEmptyMessage(root);\n } else {\n showContent(root);\n }\n\n return;\n })\n .catch(function() {\n hidePlaceholder(root);\n showContent(root);\n return;\n })\n .then(() => {\n pendingPromise.resolve();\n return;\n });\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var registerEventListeners = function(root, loadCallback, renderCallback) {\n CustomEvents.define(root, [\n CustomEvents.events.scrollBottom\n ]);\n\n root.on(CustomEvents.events.scrollBottom, function() {\n if (canLoadMore(root)) {\n showLoadingIcon(root);\n loadAndRender(root, loadCallback, renderCallback)\n .then(function() {\n return hideLoadingIcon(root);\n })\n .catch(function() {\n return hideLoadingIcon(root);\n });\n }\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Function} renderCallback The callback to render the results.\n */\n var show = function(root, loadCallback, renderCallback) {\n root = $(root);\n\n if (!root.attr('data-init')) {\n registerEventListeners(root, loadCallback, renderCallback);\n initialLoadAndRender(root, loadCallback, renderCallback);\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n getContentContainer: getContentContainer,\n getRoot: getRoot,\n setLoadedAll: setLoadedAll,\n showEmptyMessage: showEmptyMessage,\n hideEmptyMessage: hideEmptyMessage,\n showContent: showContent,\n hideContent: hideContent\n };\n});\n"],"names":["define","$","CustomEvents","PendingPromise","SELECTORS","stopLoading","root","attr","getContentContainer","find","hideLoadingIcon","addClass","showEmptyMessage","removeClass","hidePlaceholder","showContent","hideContent","setLoadedAll","value","loadAndRender","loadCallback","renderCallback","userId","getUserId","startLoading","then","items","length","contentContainer","catch","initialLoadAndRender","pendingPromise","empty","showPlaceholder","resolve","registerEventListeners","events","scrollBottom","on","hasLoadedAll","isLoading","canLoadMore","showLoadingIcon","show","getRoot","containerElement","hideEmptyMessage"],"mappings":";;;;;;;AAsBAA,oDACA,CACI,SACA,iCACA,iBAEJ,SACIC,EACAC,aACAC,oBAGIC,eACM,iCADNA,iCAEwB,yCAFxBA,4BAGmB,oCAHnBA,wBAIe,0CAJfA,sBAKa,wCAiBbC,YAAc,SAASC,MACvBA,KAAKC,KAAK,gBAAgB,IA6B1BC,oBAAsB,SAASF,aACxBA,KAAKG,KAAKL,8BA2BjBM,gBAAkB,SAASJ,MAC3BA,KAAKG,KAAKL,kCAAkCO,SAAS,WAQrDC,iBAAmB,SAASN,MAC5BA,KAAKG,KAAKL,yBAAyBS,YAAY,WA0B/CC,gBAAkB,SAASR,MAC3BA,KAAKG,KAAKL,uBAAuBO,SAAS,WAQ1CI,YAAc,SAAST,MACvBE,oBAAoBF,MAAMO,YAAY,WAQtCG,YAAc,SAASV,MACvBE,oBAAoBF,MAAMK,SAAS,WAmBnCM,aAAe,SAASX,KAAMY,OAC9BZ,KAAKC,KAAK,kBAAmBW,QAqB7BC,cAAgB,SAASb,KAAMc,aAAcC,oBACzCC,OAvIQ,SAAShB,aACdA,KAAKC,KAAK,gBAsIJgB,CAAUjB,aApKR,SAASA,MACxBA,KAAKC,KAAK,gBAAgB,GAoK1BiB,CAAalB,MAENc,aAAad,KAAMgB,QACrBG,MAAK,SAASC,UACPA,MAAMC,OAAS,EAAG,KACdC,iBAAmBpB,oBAAoBF,aACpCe,eAAeO,iBAAkBF,MAAOJ,QAC1CG,MAAK,kBACKC,gBAGRA,SAGdD,MAAK,SAASC,cACXrB,YAAYC,MACZA,KAAKC,KAAK,aAAa,GAElBmB,MAAMC,QACPV,aAAaX,MAAM,GAGhBoB,SAEVG,OAAM,WACHxB,YAAYC,MACZA,KAAKC,KAAK,aAAa,OAa/BuB,qBAAuB,SAASxB,KAAMc,aAAcC,sBAC9CU,eAAiB,IAAI5B,eAAe,+BAC1CK,oBAAoBF,MAAM0B,QAhHR,SAAS1B,MAC3BA,KAAKG,KAAKL,uBAAuBS,YAAY,UAgH7CoB,CAAgB3B,MAChBU,YAAYV,MACLa,cAAcb,KAAMc,aAAcC,gBACpCI,MAAK,SAASC,OACXZ,gBAAgBR,MAEXoB,MAAMC,OAGPZ,YAAYT,MAFZM,iBAAiBN,SAOxBuB,OAAM,WACHf,gBAAgBR,MAChBS,YAAYT,SAGfmB,MAAK,KACFM,eAAeG,cAYvBC,uBAAyB,SAAS7B,KAAMc,aAAcC,gBACtDnB,aAAaF,OAAOM,KAAM,CACtBJ,aAAakC,OAAOC,eAGxB/B,KAAKgC,GAAGpC,aAAakC,OAAOC,cAAc,YA7F5B,SAAS/B,aApBR,SAASA,YACe,QAAhCA,KAAKC,KAAK,mBAoBTgC,CAAajC,QArIT,SAASA,YACgB,SAA9BA,KAAKC,KAAK,gBAoIciC,CAAUlC,OA6FjCmC,CAAYnC,SA3LF,SAASA,MAC3BA,KAAKG,KAAKL,kCAAkCS,YAAY,UA2LhD6B,CAAgBpC,MAChBa,cAAcb,KAAMc,aAAcC,gBAC7BI,MAAK,kBACKf,gBAAgBJ,SAE1BuB,OAAM,kBACInB,gBAAgBJ,oBAuBpC,CACHqC,KAXO,SAASrC,KAAMc,aAAcC,iBACpCf,KAAOL,EAAEK,OAECC,KAAK,eACX4B,uBAAuB7B,KAAMc,aAAcC,gBAC3CS,qBAAqBxB,KAAMc,aAAcC,gBACzCf,KAAKC,KAAK,aAAa,KAM3BC,oBAAqBA,oBACrBoC,QArOU,SAASC,yBACZA,iBAAiBpC,KAAKL,iBAqO7Ba,aAAcA,aACdL,iBAAkBA,iBAClBkC,iBApMmB,SAASxC,MAC5BA,KAAKG,KAAKL,yBAAyBO,SAAS,WAoM5CI,YAAaA,YACbC,YAAaA"}PKB\/@  +build/message_drawer_view_group_info.min.jsnu[/** * Controls the group info page of the message drawer. * * @module core_message/message_drawer_view_group_info * @copyright 2018 Ryan Wyllie * @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.mapPKB\W"build/message_drawer_router.min.jsnu[/** * 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 * @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;ipendingPromise.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.\n\n/**\n * Send bulk message to the given user ids.\n *\n * @module core_message/message_send_bulk\n * @copyright 2019 Shamim Rezaie \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"}PKB\Bbuild/message_popover.min.jsnu[/** * Controls the message popover in the nav bar. * * @module core_message/message_popover * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_popover",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_events"],(function($,CustomEvents,PubSub,MessageDrawerEvents){var SELECTORS_COUNT_CONTAINER='[data-region="count-container"]',handleDecrementConversationCount=function(button){return function(){var countContainer=button.find(SELECTORS_COUNT_CONTAINER),count=parseInt(countContainer.text(),10);isNaN(count)||!count||count<2?countContainer.addClass("hidden"):(count-=1,countContainer.text(count))}},registerEventListeners=function(button){CustomEvents.define(button,[CustomEvents.events.activate]),button.on(CustomEvents.events.activate,(function(e,data){var buttonid;buttonid=button.attr("id"),PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY,buttonid),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.mapPKB\{~<<-build/message_drawer_view_settings.min.js.mapnu[{"version":3,"file":"message_drawer_view_settings.min.js","sources":["../src/message_drawer_view_settings.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the settings page in the message drawer.\n *\n * @module core_message/message_drawer_view_settings\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/str',\n 'core/pubsub',\n 'core/templates',\n 'core_message/message_repository',\n 'core/custom_interaction_events',\n 'core_message/message_drawer_events'\n],\nfunction(\n $,\n Notification,\n Str,\n PubSub,\n Templates,\n Repository,\n CustomEvents,\n MessageDrawerEvents\n) {\n\n var SELECTORS = {\n CHECKBOX: 'input[type=\"checkbox\"]',\n SETTINGS: '[data-region=\"settings\"]',\n PRIVACY_PREFERENCE: '[data-preference=\"blocknoncontacts\"] input[type=\"radio\"]',\n NOTIFICATIONS_PREFERENCE: '[data-preference=\"notifications\"] input[type=\"checkbox\"]',\n ENTER_TO_SEND_PREFERENCE: '[data-preference=\"entertosend\"] input[type=\"checkbox\"]',\n NOTIFICATION_PREFERENCES_CONTAINER: '[data-region=\"notification-preference-container\"]',\n CONTENT_CONTAINER: '[data-region=\"content-container\"]',\n PLACEHOLDER_CONTAINER: '[data-region=\"placeholder-container\"]'\n };\n\n var TEMPLATES = {\n NOTIFICATION_PREFERENCES: 'core_message/message_drawer_view_settings_body_content_notification_preferences'\n };\n\n var NOTIFICATION_PREFERENCES_KEY = 'message_provider_moodle_instantmessage';\n\n /**\n * Select the correct radio button in the DOM for the privacy preference.\n *\n * @param {Object} body The settings body element.\n * @param {Number} value Which radio button should be set\n */\n var setPrivacyPreference = function(body, value) {\n var inputs = body.find(SELECTORS.PRIVACY_PREFERENCE);\n inputs.each(function(index, input) {\n input = $(input);\n if (input.val() == value) {\n input.prop('checked', true);\n } else {\n input.prop('checked', false);\n }\n });\n };\n\n /**\n * Set the \"enter to send\" checkbox to the correct value in the DOM.\n *\n * @param {Object} body The settings body element.\n * @param {Bool} value Whether enter to send is enabled or disabled.\n */\n var setEnterToSend = function(body, value) {\n var checkbox = body.find(SELECTORS.ENTER_TO_SEND_PREFERENCE);\n\n if (value) {\n checkbox.prop('checked', true);\n } else {\n checkbox.prop('checked', false);\n }\n };\n\n /**\n * Send a request to the server to save the given preferences. Also publish\n * a preferences updated event for the rest of the message drawer to\n * subscribe to.\n *\n * @param {Number} loggedInUserId The logged in user id.\n * @param {Array} preferences The preferences to set.\n * @return {Object} jQuery promise\n */\n var savePreferences = function(loggedInUserId, preferences) {\n return Repository.savePreferences(loggedInUserId, preferences)\n .then(function() {\n PubSub.publish(MessageDrawerEvents.PREFERENCES_UPDATED, preferences);\n return;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Create all of the event listeners for the message preferences page.\n *\n * @method registerEventListeners\n * @param {Object} body The settings body element.\n * @param {Number} loggedInUserId The logged in user id.\n */\n var registerEventListeners = function(body, loggedInUserId) {\n var settingsContainer = body.find(SELECTORS.SETTINGS);\n\n CustomEvents.define(settingsContainer, [\n CustomEvents.events.activate\n ]);\n\n settingsContainer.on(CustomEvents.events.activate, SELECTORS.NOTIFICATIONS_PREFERENCE, function(e) {\n var container = $(e.target).closest(SELECTORS.NOTIFICATION_PREFERENCES_CONTAINER);\n var checkboxes = container.find(SELECTORS.CHECKBOX);\n if (!checkboxes.length) {\n return;\n }\n // The preference value is all of the enabled processors, comma separated, so let's\n // see which ones are enabled.\n var values = checkboxes.toArray().reduce(function(carry, checkbox) {\n checkbox = $(checkbox);\n if (checkbox.prop('checked')) {\n carry.push(checkbox.attr('data-name'));\n }\n\n return carry;\n }, []);\n var newValue = values.length ? values.join(',') : 'none';\n var preferences = [\n {\n type: 'message_provider_moodle_instantmessage_enabled',\n value: newValue\n }\n ];\n\n savePreferences(loggedInUserId, preferences);\n });\n\n settingsContainer.on('change', SELECTORS.PRIVACY_PREFERENCE, function(e) {\n var newValue = $(e.target).val();\n var preferences = [\n {\n type: 'message_blocknoncontacts',\n value: newValue\n }\n ];\n\n savePreferences(loggedInUserId, preferences);\n });\n\n settingsContainer.on(CustomEvents.events.activate, SELECTORS.ENTER_TO_SEND_PREFERENCE, function(e) {\n var newValue = $(e.target).prop('checked');\n var preferences = [\n {\n type: 'message_entertosend',\n value: newValue\n }\n ];\n\n savePreferences(loggedInUserId, preferences);\n });\n };\n\n /**\n * Initialise the module by loading the user's messaging preferences from the server and\n * rendering them in the settings page.\n *\n * Moodle may have many (or no) message processors enabled to notify the user when they\n * receive messages. We need to dynamically build the settings page based on which processors\n * are configured for the user.\n *\n * @param {Object} body The settings body element.\n * @param {Number} loggedInUserId The logged in user id.\n */\n var init = function(body, loggedInUserId) {\n // Load the message preferences from the server.\n Repository.getUserMessagePreferences(loggedInUserId)\n .then(function(response) {\n // Set the values of the stright forward preferences.\n setPrivacyPreference(body, response.blocknoncontacts);\n setEnterToSend(body, response.entertosend);\n\n // Parse the list of other preferences into a more usable format.\n var notificationProcessors = [];\n if (response.preferences.components.length) {\n response.preferences.components.forEach(function(component) {\n if (component.notifications.length) {\n // Filter down to just the notification processors that work on instant\n // messaging. We don't care about another other ones.\n var notificationPreferences = component.notifications.filter(function(notification) {\n return notification.preferencekey == NOTIFICATION_PREFERENCES_KEY;\n });\n\n if (notificationPreferences.length) {\n // Messaging only has one config at the moment which is for notifications\n // on personal messages.\n var configuration = component.notifications[0];\n notificationProcessors = configuration.processors.map(function(processor) {\n // Consider the the processor enabled if either preference is set. This is\n // for backwards compatibility. Going forward they will be treated as one\n // setting.\n var checked = processor.enabled;\n return {\n displayname: processor.displayname,\n name: processor.name,\n checked: checked,\n // The admin can force processors to be enabled at a site level so\n // we need to check if this processor has been locked by the admin.\n locked: processor.locked,\n lockedmessage: processor.lockedmessage || null,\n };\n });\n }\n }\n });\n }\n\n var container = body.find(SELECTORS.NOTIFICATION_PREFERENCES_CONTAINER);\n if (notificationProcessors.length) {\n // We have processors (i.e. email, mobile, jabber) to show.\n container.removeClass('hidden');\n // Render the processor options.\n return Templates.render(TEMPLATES.NOTIFICATION_PREFERENCES, {processors: notificationProcessors})\n .then(function(html) {\n container.append(html);\n return html;\n });\n } else {\n return true;\n }\n })\n .then(function() {\n // We're done loading so hide the loading placeholder and show the settings.\n body.find(SELECTORS.CONTENT_CONTAINER).removeClass('hidden');\n body.find(SELECTORS.PLACEHOLDER_CONTAINER).addClass('hidden');\n // Register the event listers for if the user wants to change the preferences.\n registerEventListeners(body, loggedInUserId);\n return;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Initialise the settings page by adding event listeners to\n * the checkboxes.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header The settings header element.\n * @param {Object} body The settings body element.\n * @param {Object} footer The footer body element.\n * @param {Number} loggedInUserId The logged in user id.\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, loggedInUserId) {\n if (!body.attr('data-init')) {\n init(body, loggedInUserId);\n body.attr('data-init', true);\n }\n\n return $.Deferred().resolve().promise();\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @return {Object} jQuery promise\n */\n var description = function() {\n return Str.get_string('messagedrawerviewsettings', 'core_message');\n };\n\n return {\n show: show,\n description: description,\n };\n});\n"],"names":["define","$","Notification","Str","PubSub","Templates","Repository","CustomEvents","MessageDrawerEvents","SELECTORS","TEMPLATES","savePreferences","loggedInUserId","preferences","then","publish","PREFERENCES_UPDATED","catch","exception","init","body","getUserMessagePreferences","response","value","find","each","index","input","val","prop","setPrivacyPreference","blocknoncontacts","checkbox","setEnterToSend","entertosend","notificationProcessors","components","length","forEach","component","notifications","filter","notification","preferencekey","configuration","processors","map","processor","checked","enabled","displayname","name","locked","lockedmessage","container","removeClass","render","html","append","addClass","settingsContainer","events","activate","on","e","checkboxes","target","closest","values","toArray","reduce","carry","push","attr","newValue","join","type","registerEventListeners","show","namespace","header","footer","Deferred","resolve","promise","description","get_string"],"mappings":"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"}PKB\+A*build/message_drawer_lazy_load_list.min.jsnu[/** * Lazy loaded list of items. * * @module core_message/message_drawer_lazy_load_list * @copyright 2018 Ryan Wyllie * @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.mapPKB\:build/message_drawer_view_contacts_section_requests.min.jsnu[/** * Controls the requests section of the contacts page. * * @module core_message/message_drawer_view_contacts_section_requests * @copyright 2018 Ryan Wyllie * @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.mapPKB\xrV..#build/message_repository.min.js.mapnu[{"version":3,"file":"message_repository.min.js","sources":["../src/message_repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Retrieves messages from the server.\n *\n * @module core_message/message_repository\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/ajax',\n 'core/notification',\n 'core_message/message_drawer_view_conversation_constants'\n], function(\n $,\n Ajax,\n Notification,\n Constants) {\n\n var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;\n\n /**\n * Count the number of unread conversations (one or more messages from a user)\n * for a given user.\n *\n * @param {object} args The request arguments:\n * @return {object} jQuery promise\n */\n var countUnreadConversations = function(args) {\n var request = {\n methodname: 'core_message_get_unread_conversations_count',\n args: args\n };\n\n var promise = Ajax.call([request])[0];\n\n promise.fail(Notification.exception);\n\n return promise;\n };\n\n /**\n * Get contacts for given user.\n *\n * @param {int} userId The user id\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @return {object} jQuery promise\n */\n var getContacts = function(userId, limit, offset) {\n var args = {\n userid: userId\n };\n\n if (typeof limit !== 'undefined') {\n args.limitnum = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.limitfrom = offset;\n }\n\n var request = {\n methodname: 'core_message_get_user_contacts',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Block a user.\n *\n * @param {int} userId The requesting user\n * @param {int} blockedUserId Id of user to block\n * @return {object} jQuery promise\n */\n var blockUser = function(userId, blockedUserId) {\n var requests = [\n {\n methodname: 'core_message_block_user',\n args: {\n userid: userId,\n blockeduserid: blockedUserId\n }\n },\n {\n methodname: 'core_message_get_member_info',\n args: {\n referenceuserid: userId,\n userids: [blockedUserId],\n includecontactrequests: true,\n includeprivacyinfo: true\n }\n }\n ];\n\n // Wrap both requests in a single promise so that we can catch an error\n // from either request.\n return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) {\n // Only return the profile.\n return profiles.length ? profiles[0] : {};\n });\n };\n\n /**\n * Unblock a user.\n *\n * @param {int} userId The requesting user\n * @param {int} unblockedUserId Id of user to unblock\n * @return {object} jQuery promise\n */\n var unblockUser = function(userId, unblockedUserId) {\n var requests = [\n {\n methodname: 'core_message_unblock_user',\n args: {\n userid: userId,\n unblockeduserid: unblockedUserId\n }\n },\n {\n methodname: 'core_message_get_member_info',\n args: {\n referenceuserid: userId,\n userids: [unblockedUserId],\n includecontactrequests: true,\n includeprivacyinfo: true\n }\n }\n ];\n\n // Wrap both requests in a single promise so that we can catch an error\n // from either request.\n return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) {\n // Only return the profile.\n return profiles.length ? profiles[0] : {};\n });\n };\n\n /**\n * Create a request to add a user as a contact.\n *\n * @param {int} userId The requesting user\n * @param {int[]} requestUserIds List of user ids to add\n * @return {object} jQuery promise\n */\n var createContactRequest = function(userId, requestUserIds) {\n var request = {\n methodname: 'core_message_create_contact_request',\n args: {\n userid: userId,\n requesteduserid: requestUserIds\n }\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Remove a list of users as contacts.\n *\n * @param {int} userId The requesting user\n * @param {int[]} contactUserIds List of user ids to add\n * @return {object} jQuery promise\n */\n var deleteContacts = function(userId, contactUserIds) {\n var requests = [\n {\n methodname: 'core_message_delete_contacts',\n args: {\n userid: userId,\n userids: contactUserIds\n }\n },\n {\n methodname: 'core_message_get_member_info',\n args: {\n referenceuserid: userId,\n userids: contactUserIds,\n includecontactrequests: true,\n includeprivacyinfo: true\n }\n }\n ];\n\n return $.when.apply(null, Ajax.call(requests)).then(function(response1, profiles) {\n // Return all of the profiles as an array.\n return profiles;\n });\n };\n\n /**\n * Get messages between two users.\n *\n * @param {int} currentUserId The requesting user\n * @param {int} conversationId Other user in the conversation\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @param {bool} newestFirst Order results by newest first\n * @param {int} timeFrom Only return messages after this timestamp\n * @return {object} jQuery promise\n */\n var getMessages = function(currentUserId, conversationId, limit, offset, newestFirst, timeFrom) {\n var args = {\n currentuserid: currentUserId,\n convid: conversationId,\n newest: newestFirst ? true : false\n };\n\n if (typeof limit !== 'undefined') {\n args.limitnum = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.limitfrom = offset;\n }\n\n if (typeof timeFrom !== 'undefined') {\n args.timefrom = timeFrom;\n }\n\n var request = {\n methodname: 'core_message_get_conversation_messages',\n args: args\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Search for users.\n *\n * @param {int} userId The requesting user\n * @param {string} searchString Search string\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @return {object} jQuery promise\n */\n var searchUsers = function(userId, searchString, limit, offset) {\n var args = {\n userid: userId,\n search: searchString\n };\n\n if (typeof limit !== 'undefined') {\n args.limitnum = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.limitfrom = offset;\n }\n\n var request = {\n methodname: 'core_message_message_search_users',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Search for messages.\n *\n * @param {int} userId The requesting user\n * @param {string} searchString Search string\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @return {object} jQuery promise\n */\n var searchMessages = function(userId, searchString, limit, offset) {\n var args = {\n userid: userId,\n search: searchString\n };\n\n if (typeof limit !== 'undefined') {\n args.limitnum = limit;\n }\n\n if (typeof offset !== 'undefined') {\n args.limitfrom = offset;\n }\n\n var request = {\n methodname: 'core_message_data_for_messagearea_search_messages',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Send a list of messages to a user.\n *\n * @param {int} toUserId The recipient user id\n * @param {string[]} messages List of messages to send\n * @return {object} jQuery promise\n */\n var sendMessagesToUser = function(toUserId, messages) {\n var formattedMessages = messages.map(function(message) {\n return {\n touserid: toUserId,\n text: message\n };\n });\n var request = {\n methodname: 'core_message_send_instant_messages',\n args: {\n messages: formattedMessages\n }\n };\n\n return Ajax.call([request])[0]\n .then(function(results) {\n // Error handling for the weird way the old function works.\n var errors = results.reduce(function(carry, result) {\n if (result.errormessage) {\n carry.push(result.errormessage);\n }\n\n return carry;\n }, []);\n if (errors.length) {\n throw new Error(errors.join(\"\\n\"));\n }\n\n return results;\n })\n .then(function(results) {\n // Format the results to match the other send message function.\n return results.map(function(result) {\n return {\n id: result.msgid,\n text: result.text,\n timecreated: result.timecreated,\n useridfrom: result.useridfrom,\n conversationid: result.conversationid,\n candeletemessagesforallusers: result.candeletemessagesforallusers\n };\n });\n });\n };\n\n /**\n * Send a single message to a user.\n *\n * @param {int} toUserId The recipient user id\n * @param {string} text The message text\n * @return {object} jQuery promise\n */\n var sendMessageToUser = function(toUserId, text) {\n return sendMessagesToUser(toUserId, [text])\n .then(function(results) {\n return results[0];\n });\n };\n\n /**\n * Send messages to a conversation.\n *\n * @param {int} conversationId The conversation id\n * @param {string[]} messages List of messages to send\n * @return {object} jQuery promise\n */\n var sendMessagesToConversation = function(conversationId, messages) {\n var formattedMessages = messages.map(function(message) {\n return {\n text: message\n };\n });\n var request = {\n methodname: 'core_message_send_messages_to_conversation',\n args: {\n conversationid: conversationId,\n messages: formattedMessages\n }\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Send a message to a conversation.\n *\n * @param {int} conversationId The conversation id\n * @param {string} text The message text\n * @return {object} jQuery promise\n */\n var sendMessageToConversation = function(conversationId, text) {\n return sendMessagesToConversation(conversationId, [text])\n .then(function(result) {\n return result[0];\n });\n };\n\n /**\n * Save message preferences.\n *\n * @param {int} userId The owner of the preferences\n * @param {object[]} preferences New preferences values\n * @return {object} jQuery promise\n */\n var savePreferences = function(userId, preferences) {\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n userid: userId,\n preferences: preferences\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the user's preferences.\n *\n * @param {int} userId The target user\n * @return {object} jQuery promise\n */\n var getPreferences = function(userId) {\n var request = {\n methodname: 'core_user_get_user_preferences',\n args: {\n userid: userId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Delete a list of messages.\n *\n * @param {int} userId The user to delete messages for\n * @param {int[]} messageIds List of message ids to delete\n * @return {object} jQuery promise\n */\n var deleteMessages = function(userId, messageIds) {\n return $.when.apply(null, Ajax.call(messageIds.map(function(messageId) {\n return {\n methodname: 'core_message_delete_message',\n args: {\n messageid: messageId,\n userid: userId\n }\n };\n })));\n };\n\n /**\n * Delete a list of messages for all users.\n *\n * @param {int} userId The user to delete messages for\n * @param {int[]} messageIds List of message ids to delete\n * @return {object} jQuery promise\n */\n var deleteMessagesForAllUsers = function(userId, messageIds) {\n return $.when.apply(null, Ajax.call(messageIds.map(function(messageId) {\n return {\n methodname: 'core_message_delete_message_for_all_users',\n args: {\n messageid: messageId,\n userid: userId\n }\n };\n })));\n };\n\n /**\n * Delete a conversation between two users.\n *\n * @param {int} userId The user to delete messages for\n * @param {int} conversationId The id of the conversation\n * @return {object} jQuery promise\n */\n var deleteConversation = function(userId, conversationId) {\n var request = {\n methodname: 'core_message_delete_conversations_by_id',\n args: {\n userid: userId,\n conversationids: [conversationId]\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the list of contact requests for a user.\n *\n * @param {int} userId The user id\n * @return {object} jQuery promise\n */\n var getContactRequests = function(userId) {\n var request = {\n methodname: 'core_message_get_contact_requests',\n args: {\n userid: userId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Accept a contact request.\n *\n * @param {int} sendingUserId The user that sent the request\n * @param {int} recipientUserId The user that received the request\n * @return {object} jQuery promise\n */\n var acceptContactRequest = function(sendingUserId, recipientUserId) {\n var requests = [\n {\n methodname: 'core_message_confirm_contact_request',\n args: {\n userid: sendingUserId,\n requesteduserid: recipientUserId\n }\n },\n {\n methodname: 'core_message_get_member_info',\n args: {\n referenceuserid: recipientUserId,\n userids: [sendingUserId],\n includecontactrequests: true,\n includeprivacyinfo: true\n }\n }\n ];\n\n // Wrap both requests in a single promise so that we can catch an error\n // from either request.\n return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) {\n // Only return the profile.\n return profiles.length ? profiles[0] : {};\n });\n };\n\n /**\n * Decline a contact request.\n *\n * @param {int} sendingUserId The user that sent the request\n * @param {int} recipientUserId The user that received the request\n * @return {object} jQuery promise\n */\n var declineContactRequest = function(sendingUserId, recipientUserId) {\n var requests = [\n {\n methodname: 'core_message_decline_contact_request',\n args: {\n userid: sendingUserId,\n requesteduserid: recipientUserId\n }\n },\n {\n methodname: 'core_message_get_member_info',\n args: {\n referenceuserid: recipientUserId,\n userids: [sendingUserId],\n includecontactrequests: true,\n includeprivacyinfo: true\n }\n }\n ];\n\n // Wrap both requests in a single promise so that we can catch an error\n // from either request.\n return $.when.apply(null, Ajax.call(requests)).then(function(reponse1, profiles) {\n // Only return the profile.\n return profiles.length ? profiles[0] : {};\n });\n };\n\n /**\n * Get a conversation.\n *\n * @param {int} loggedInUserId The logged in user\n * @param {int} conversationId The conversation id\n * @param {bool} includeContactRequests Incldue contact requests between members\n * @param {bool} includePrivacyInfo Include privacy info for members\n * @param {int} memberLimit Limit for members\n * @param {int} memberOffset Offset for members\n * @param {int} messageLimit Limit for messages\n * @param {int} messageOffset Offset for messages\n * @param {bool} newestMessagesFirst Order the messages by newest first\n * @return {object} jQuery promise\n */\n var getConversation = function(\n loggedInUserId,\n conversationId,\n includeContactRequests,\n includePrivacyInfo,\n memberLimit,\n memberOffset,\n messageLimit,\n messageOffset,\n newestMessagesFirst\n ) {\n var args = {\n userid: loggedInUserId,\n conversationid: conversationId\n };\n\n if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) {\n args.includecontactrequests = includeContactRequests;\n }\n\n if (typeof includePrivacyInfo != 'undefined' && includePrivacyInfo !== null) {\n args.includeprivacyinfo = includePrivacyInfo;\n }\n\n if (typeof memberLimit != 'undefined' && memberLimit !== null) {\n args.memberlimit = memberLimit;\n }\n\n if (typeof memberOffset != 'undefined' && memberOffset !== null) {\n args.memberoffset = memberOffset;\n }\n\n if (typeof messageLimit != 'undefined' && messageLimit !== null) {\n args.messagelimit = messageLimit;\n }\n\n if (typeof messageOffset != 'undefined' && messageOffset !== null) {\n args.messageoffset = messageOffset;\n }\n\n if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) {\n args.newestmessagesfirst = newestMessagesFirst;\n }\n\n var request = {\n methodname: 'core_message_get_conversation',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Get a conversation between users.\n *\n * @param {int} loggedInUserId The logged in user\n * @param {int} otherUserId The other user id\n * @param {bool} includeContactRequests Incldue contact requests between members\n * @param {bool} includePrivacyInfo Include privacy info for members\n * @param {int} memberLimit Limit for members\n * @param {int} memberOffset Offset for members\n * @param {int} messageLimit Limit for messages\n * @param {int} messageOffset Offset for messages\n * @param {bool} newestMessagesFirst Order the messages by newest first\n * @return {object} jQuery promise\n */\n var getConversationBetweenUsers = function(\n loggedInUserId,\n otherUserId,\n includeContactRequests,\n includePrivacyInfo,\n memberLimit,\n memberOffset,\n messageLimit,\n messageOffset,\n newestMessagesFirst\n ) {\n var args = {\n userid: loggedInUserId,\n otheruserid: otherUserId\n };\n\n if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) {\n args.includecontactrequests = includeContactRequests;\n }\n\n if (typeof includePrivacyInfo != 'undefined' && includePrivacyInfo !== null) {\n args.includeprivacyinfo = includePrivacyInfo;\n }\n\n if (typeof memberLimit != 'undefined' && memberLimit !== null) {\n args.memberlimit = memberLimit;\n }\n\n if (typeof memberOffset != 'undefined' && memberOffset !== null) {\n args.memberoffset = memberOffset;\n }\n\n if (typeof messageLimit != 'undefined' && messageLimit !== null) {\n args.messagelimit = messageLimit;\n }\n\n if (typeof messageOffset != 'undefined' && messageOffset !== null) {\n args.messageoffset = messageOffset;\n }\n\n if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) {\n args.newestmessagesfirst = newestMessagesFirst;\n }\n\n var request = {\n methodname: 'core_message_get_conversation_between_users',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Get a self-conversation.\n *\n * @param {int} loggedInUserId The logged in user\n * @param {int} messageLimit Limit for messages\n * @param {int} messageOffset Offset for messages\n * @param {bool} newestMessagesFirst Order the messages by newest first\n * @return {object} jQuery promise\n */\n var getSelfConversation = function(\n loggedInUserId,\n messageLimit,\n messageOffset,\n newestMessagesFirst\n ) {\n var args = {\n userid: loggedInUserId\n };\n\n if (typeof messageLimit != 'undefined' && messageLimit !== null) {\n args.messagelimit = messageLimit;\n }\n\n if (typeof messageOffset != 'undefined' && messageOffset !== null) {\n args.messageoffset = messageOffset;\n }\n\n if (typeof newestMessagesFirst != 'undefined' && newestMessagesFirst !== null) {\n args.newestmessagesfirst = newestMessagesFirst;\n }\n\n var request = {\n methodname: 'core_message_get_self_conversation',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the conversations for a user.\n *\n * @param {int} userId The logged in user\n * @param {int|null} type The type of conversation to get\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @param {bool|null} favourites If favourites should be included or not\n * @param {bool} mergeself\n * @return {object} jQuery promise\n */\n var getConversations = function(\n userId,\n type,\n limit,\n offset,\n favourites,\n mergeself\n ) {\n var args = {\n userid: userId,\n type: type\n };\n\n if (typeof limit != 'undefined' && limit !== null) {\n args.limitnum = limit;\n }\n\n if (typeof offset != 'undefined' && offset !== null) {\n args.limitfrom = offset;\n }\n\n if (typeof favourites != 'undefined' && favourites !== null) {\n args.favourites = favourites;\n }\n\n if (typeof mergeself != 'undefined' && mergeself !== null) {\n args.mergeself = mergeself;\n }\n\n var request = {\n methodname: 'core_message_get_conversations',\n args: args\n };\n\n return Ajax.call([request])[0]\n .then(function(result) {\n if (result.conversations.length) {\n result.conversations = result.conversations.map(function(conversation) {\n if (conversation.type == CONVERSATION_TYPES.PRIVATE || conversation.type == CONVERSATION_TYPES.SELF) {\n var otherUser = conversation.members.length ? conversation.members[0] : null;\n\n if (otherUser) {\n conversation.name = conversation.name ? conversation.name : otherUser.fullname;\n conversation.imageurl = conversation.imageurl ? conversation.imageurl : otherUser.profileimageurl;\n }\n }\n\n return conversation;\n });\n }\n\n return result;\n });\n };\n\n /**\n * Get the conversations for a user.\n *\n * @param {int} conversationId The conversation id\n * @param {int} loggedInUserId The logged in user\n * @param {int} limit Limit for results\n * @param {int} offset Offset for results\n * @param {bool} includeContactRequests If contact requests should be included in result\n * @return {object} jQuery promise\n */\n var getConversationMembers = function(conversationId, loggedInUserId, limit, offset, includeContactRequests) {\n var args = {\n userid: loggedInUserId,\n conversationid: conversationId\n };\n\n if (typeof limit != 'undefined' && limit !== null) {\n args.limitnum = limit;\n }\n\n if (typeof offset != 'undefined' && offset !== null) {\n args.limitfrom = offset;\n }\n\n if (typeof includeContactRequests != 'undefined' && includeContactRequests !== null) {\n args.includecontactrequests = includeContactRequests;\n }\n\n var request = {\n methodname: 'core_message_get_conversation_members',\n args: args\n };\n\n return Ajax.call([request])[0];\n };\n\n /**\n * Set a list of conversations to set as favourites for the given user.\n *\n * @param {int} userId The user id\n * @param {array} conversationIds List of conversation ids to set as favourite\n * @return {object} jQuery promise\n */\n var setFavouriteConversations = function(userId, conversationIds) {\n\n var request = {\n methodname: 'core_message_set_favourite_conversations',\n args: {\n userid: userId,\n conversations: conversationIds\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set a list of conversations to unset as favourites for the given user.\n *\n * @param {int} userId The user id\n * @param {array} conversationIds List of conversation ids to unset as favourite\n * @return {object} jQuery promise\n */\n var unsetFavouriteConversations = function(userId, conversationIds) {\n\n var request = {\n methodname: 'core_message_unset_favourite_conversations',\n args: {\n userid: userId,\n conversations: conversationIds\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set a list of conversations to set as muted for the given user.\n *\n * @param {int} userId The user id\n * @param {array} conversationIds List of conversation ids to set as favourite\n * @return {object} jQuery promise\n */\n var setMutedConversations = function(userId, conversationIds) {\n var request = {\n methodname: 'core_message_mute_conversations',\n args: {\n userid: userId,\n conversationids: conversationIds\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Set a list of conversations to unset as muted for the given user.\n *\n * @param {int} userId The user id\n * @param {array} conversationIds List of conversation ids to unset as favourite\n * @return {object} jQuery promise\n */\n var unsetMutedConversations = function(userId, conversationIds) {\n var request = {\n methodname: 'core_message_unmute_conversations',\n args: {\n userid: userId,\n conversationids: conversationIds\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get a list of user's member info.\n *\n * @param {int} referenceUserId The user id\n * @param {array} userIds List of user ids to get\n * @param {bool} includeContactRequests Include contact requests between users in response\n * @param {bool} includePrivacyInfo Include privacy info for reference user in response\n * @return {object} jQuery promise\n */\n var getMemberInfo = function(referenceUserId, userIds, includeContactRequests, includePrivacyInfo) {\n var args = {\n referenceuserid: referenceUserId,\n userids: userIds\n };\n\n if (typeof includeContactRequests != 'undefined') {\n args.includecontactrequests = includeContactRequests;\n }\n\n if (typeof includePrivacyInfo != 'undefined') {\n args.includeprivacyinfo = includePrivacyInfo;\n }\n\n var request = {\n methodname: 'core_message_get_member_info',\n args: args\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get a list of user's member info.\n *\n * @param {int} userId The user id to mark as read for\n * @param {int} conversationId The conversation to mark as read\n * @return {object} jQuery promise\n */\n var markAllConversationMessagesAsRead = function(userId, conversationId) {\n\n var request = {\n methodname: 'core_message_mark_all_conversation_messages_as_read',\n args: {\n userid: userId,\n conversationid: conversationId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get the user's message preferences.\n *\n * @param {int} userId The user id to load preferences for\n * @return {object} jQuery promise\n */\n var getUserMessagePreferences = function(userId) {\n var request = {\n methodname: 'core_message_get_user_message_preferences',\n args: {\n userid: userId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * The the count of the user's conversations grouped by type.\n *\n * @param {Number} userId The user's id.\n * @return {Object} jQuery promise.\n */\n var getTotalConversationCounts = function(userId) {\n var request = {\n methodname: 'core_message_get_conversation_counts',\n args: {\n userid: userId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * The the count of the user's unread conversations grouped by type.\n *\n * @param {Number} userId The user's id.\n * @return {Object} jQuery promise.\n */\n var getUnreadConversationCounts = function(userId) {\n var request = {\n methodname: 'core_message_get_unread_conversation_counts',\n args: {\n userid: userId\n }\n };\n return Ajax.call([request])[0];\n };\n\n /**\n * Get both the unread and total conversation counts in a single request.\n *\n * @param {Number} userId The user's id.\n * @return {Object} jQuery promise.\n */\n var getAllConversationCounts = function(userId) {\n var requests = [\n {\n methodname: 'core_message_get_conversation_counts',\n args: {\n userid: userId\n }\n },\n {\n methodname: 'core_message_get_unread_conversation_counts',\n args: {\n userid: userId\n }\n },\n ];\n return $.when.apply(null, Ajax.call(requests)).then(function(total, unread) {\n return {\n total: total,\n unread: unread\n };\n });\n };\n\n return {\n countUnreadConversations: countUnreadConversations,\n getContacts: getContacts,\n blockUser: blockUser,\n unblockUser: unblockUser,\n createContactRequest: createContactRequest,\n deleteContacts: deleteContacts,\n getMessages: getMessages,\n searchUsers: searchUsers,\n searchMessages: searchMessages,\n sendMessagesToUser: sendMessagesToUser,\n sendMessageToUser: sendMessageToUser,\n sendMessagesToConversation: sendMessagesToConversation,\n sendMessageToConversation: sendMessageToConversation,\n savePreferences: savePreferences,\n getPreferences: getPreferences,\n deleteMessages: deleteMessages,\n deleteMessagesForAllUsers: deleteMessagesForAllUsers,\n deleteConversation: deleteConversation,\n getContactRequests: getContactRequests,\n acceptContactRequest: acceptContactRequest,\n declineContactRequest: declineContactRequest,\n getConversation: getConversation,\n getConversationBetweenUsers: getConversationBetweenUsers,\n getSelfConversation: getSelfConversation,\n getConversations: getConversations,\n getConversationMembers: getConversationMembers,\n setFavouriteConversations: setFavouriteConversations,\n setMutedConversations: setMutedConversations,\n unsetFavouriteConversations: unsetFavouriteConversations,\n unsetMutedConversations: unsetMutedConversations,\n getMemberInfo: getMemberInfo,\n markAllConversationMessagesAsRead: markAllConversationMessagesAsRead,\n getUserMessagePreferences: getUserMessagePreferences,\n getTotalConversationCounts: getTotalConversationCounts,\n getUnreadConversationCounts: getUnreadConversationCounts,\n getAllConversationCounts: getAllConversationCounts\n };\n});\n"],"names":["define","$","Ajax","Notification","Constants","CONVERSATION_TYPES","sendMessagesToUser","toUserId","messages","request","methodname","args","map","message","touserid","text","call","then","results","errors","reduce","carry","result","errormessage","push","length","Error","join","id","msgid","timecreated","useridfrom","conversationid","candeletemessagesforallusers","sendMessagesToConversation","conversationId","countUnreadConversations","promise","fail","exception","getContacts","userId","limit","offset","userid","limitnum","limitfrom","blockUser","blockedUserId","requests","blockeduserid","referenceuserid","userids","includecontactrequests","includeprivacyinfo","when","apply","reponse1","profiles","unblockUser","unblockedUserId","unblockeduserid","createContactRequest","requestUserIds","requesteduserid","deleteContacts","contactUserIds","response1","getMessages","currentUserId","newestFirst","timeFrom","currentuserid","convid","newest","timefrom","searchUsers","searchString","search","searchMessages","sendMessageToUser","sendMessageToConversation","savePreferences","preferences","getPreferences","deleteMessages","messageIds","messageId","messageid","deleteMessagesForAllUsers","deleteConversation","conversationids","getContactRequests","acceptContactRequest","sendingUserId","recipientUserId","declineContactRequest","getConversation","loggedInUserId","includeContactRequests","includePrivacyInfo","memberLimit","memberOffset","messageLimit","messageOffset","newestMessagesFirst","memberlimit","memberoffset","messagelimit","messageoffset","newestmessagesfirst","getConversationBetweenUsers","otherUserId","otheruserid","getSelfConversation","getConversations","type","favourites","mergeself","conversations","conversation","PRIVATE","SELF","otherUser","members","name","fullname","imageurl","profileimageurl","getConversationMembers","setFavouriteConversations","conversationIds","setMutedConversations","unsetFavouriteConversations","unsetMutedConversations","getMemberInfo","referenceUserId","userIds","markAllConversationMessagesAsRead","getUserMessagePreferences","getTotalConversationCounts","getUnreadConversationCounts","getAllConversationCounts","total","unread"],"mappings":"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"}PKB\##:build/preferences_notifications_list_controller.min.js.mapnu[{"version":3,"file":"preferences_notifications_list_controller.min.js","sources":["../src/preferences_notifications_list_controller.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the preferences for the list of notification types on the\n * message preference page\n *\n * @module core_message/preferences_notifications_list_controller\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery',\n 'core/ajax',\n 'core/notification',\n 'core/custom_interaction_events',\n 'core_message/notification_preference',\n 'core_message/notification_processor_settings',\n ],\n function(\n $,\n Ajax,\n Notification,\n CustomEvents,\n NotificationPreference,\n NotificationProcessorSettings,\n ) {\n\n var SELECTORS = {\n DISABLE_NOTIFICATIONS: '[data-region=\"disable-notification-container\"] [data-disable-notifications]',\n DISABLE_NOTIFICATIONS_CONTAINER: '[data-region=\"disable-notification-container\"]',\n PREFERENCE: '.preference-state',\n PREFERENCE_ROW: '[data-region=\"preference-row\"]',\n PREFERENCE_INPUT: '.preference-state input',\n PROCESSOR_SETTING: '[data-processor-setting]',\n };\n\n /**\n * Constructor for the PreferencesController.\n *\n * @class\n * @param {object} element jQuery object root element of the preference\n */\n var PreferencesController = function(element) {\n this.root = $(element);\n this.userId = this.root.attr('data-user-id');\n\n this.registerEventListeners();\n };\n\n /**\n * Check if the preferences are all disabled.\n *\n * @method isDisabled\n * @return {bool}\n */\n PreferencesController.prototype.isDisabled = function() {\n return this.root.hasClass('disabled');\n };\n\n /**\n * Disable all of the preferences.\n *\n * @method setDisabled\n */\n PreferencesController.prototype.setDisabled = function() {\n this.root.addClass('disabled');\n this.root.find(SELECTORS.PREFERENCE_INPUT).prop('disabled', true);\n };\n\n /**\n * Enable all of the preferences.\n *\n * @method setEnabled\n */\n PreferencesController.prototype.setEnabled = function() {\n this.root.removeClass('disabled');\n this.root.find(SELECTORS.PREFERENCE_INPUT).prop('disabled', false);\n };\n\n /**\n * Update the disable all notifications user property in the DOM and\n * send a request to update on the server.\n *\n * @method toggleDisableAllStatus\n * @return {Promise}\n */\n PreferencesController.prototype.toggleDisableAllStatus = function() {\n var checkbox = $(SELECTORS.DISABLE_NOTIFICATIONS);\n var container = $(SELECTORS.DISABLE_NOTIFICATIONS_CONTAINER);\n var ischecked = checkbox.prop('checked');\n\n if (container.hasClass('loading')) {\n return $.Deferred().resolve();\n }\n\n container.addClass('loading');\n\n var request = {\n methodname: 'core_user_update_user_preferences',\n args: {\n userid: this.userId,\n emailstop: ischecked ? 1 : 0,\n }\n };\n\n return Ajax.call([request])[0]\n .done(function() {\n if (ischecked) {\n this.setDisabled();\n } else {\n this.setEnabled();\n }\n }.bind(this))\n .always(function() {\n container.removeClass('loading');\n })\n .fail(Notification.exception);\n };\n\n /**\n * Set up all of the event listeners for the PreferencesController.\n *\n * @method registerEventListeners\n */\n PreferencesController.prototype.registerEventListeners = function() {\n var disabledNotificationsElement = $(SELECTORS.DISABLE_NOTIFICATIONS);\n\n CustomEvents.define(this.root, [\n CustomEvents.events.activate,\n ]);\n\n this.root.on('change', function(e) {\n if (!this.isDisabled()) {\n var preferenceElement = $(e.target).closest(SELECTORS.PREFERENCE);\n var preferenceRow = $(e.target).closest(SELECTORS.PREFERENCE_ROW);\n var preference = new NotificationPreference(preferenceRow, this.userId);\n\n preferenceElement.addClass('loading');\n preference.save().always(function() {\n preferenceElement.removeClass('loading');\n });\n }\n }.bind(this));\n\n var eventFormPromise = NotificationProcessorSettings.create({});\n\n this.root.on(CustomEvents.events.activate, SELECTORS.PROCESSOR_SETTING, function(e, data) {\n var element = $(e.target).closest(SELECTORS.PROCESSOR_SETTING);\n\n data.originalEvent.preventDefault();\n\n eventFormPromise.then(function(modal) {\n // Configure modal with element settings.\n modal.setUserId($(element).attr('data-user-id'));\n modal.setName($(element).attr('data-name'));\n modal.setContextId($(element).attr('data-context-id'));\n modal.setElement(element);\n modal.show();\n\n e.stopImmediatePropagation();\n return;\n }).catch(Notification.exception);\n });\n\n CustomEvents.define(disabledNotificationsElement, [\n CustomEvents.events.activate\n ]);\n\n disabledNotificationsElement.on(CustomEvents.events.activate, function() {\n this.toggleDisableAllStatus();\n }.bind(this));\n };\n\n return PreferencesController;\n});\n"],"names":["define","$","Ajax","Notification","CustomEvents","NotificationPreference","NotificationProcessorSettings","SELECTORS","PreferencesController","element","root","userId","this","attr","registerEventListeners","prototype","isDisabled","hasClass","setDisabled","addClass","find","prop","setEnabled","removeClass","toggleDisableAllStatus","checkbox","container","ischecked","Deferred","resolve","request","methodname","args","userid","emailstop","call","done","bind","always","fail","exception","disabledNotificationsElement","events","activate","on","e","preferenceElement","target","closest","preferenceRow","preference","save","eventFormPromise","create","data","originalEvent","preventDefault","then","modal","setUserId","setName","setContextId","setElement","show","stopImmediatePropagation","catch"],"mappings":";;;;;;;;AAuBAA,gEAAO,CAAC,SACA,YACA,oBACA,iCACA,uCACA,iDAEA,SACEC,EACAC,KACAC,aACAC,aACAC,uBACAC,mCAGFC,gCACuB,8EADvBA,0CAEiC,iDAFjCA,qBAGY,oBAHZA,yBAIgB,iCAJhBA,2BAKkB,0BALlBA,4BAMmB,2BASnBC,sBAAwB,SAASC,cAC5BC,KAAOT,EAAEQ,cACTE,OAASC,KAAKF,KAAKG,KAAK,qBAExBC,iCASTN,sBAAsBO,UAAUC,WAAa,kBAClCJ,KAAKF,KAAKO,SAAS,aAQ9BT,sBAAsBO,UAAUG,YAAc,gBACrCR,KAAKS,SAAS,iBACdT,KAAKU,KAAKb,4BAA4Bc,KAAK,YAAY,IAQhEb,sBAAsBO,UAAUO,WAAa,gBACpCZ,KAAKa,YAAY,iBACjBb,KAAKU,KAAKb,4BAA4Bc,KAAK,YAAY,IAUhEb,sBAAsBO,UAAUS,uBAAyB,eACjDC,SAAWxB,EAAEM,iCACbmB,UAAYzB,EAAEM,2CACdoB,UAAYF,SAASJ,KAAK,cAE1BK,UAAUT,SAAS,kBACZhB,EAAE2B,WAAWC,UAGxBH,UAAUP,SAAS,eAEfW,QAAU,CACVC,WAAY,oCACZC,KAAM,CACFC,OAAQrB,KAAKD,OACbuB,UAAWP,UAAY,EAAI,WAI5BzB,KAAKiC,KAAK,CAACL,UAAU,GACvBM,KAAK,WACET,eACKT,mBAEAI,cAEXe,KAAKzB,OACN0B,QAAO,WACJZ,UAAUH,YAAY,cAEzBgB,KAAKpC,aAAaqC,YAQ3BhC,sBAAsBO,UAAUD,uBAAyB,eACjD2B,6BAA+BxC,EAAEM,iCAErCH,aAAaJ,OAAOY,KAAKF,KAAM,CAC3BN,aAAasC,OAAOC,gBAGnBjC,KAAKkC,GAAG,SAAU,SAASC,OACvBjC,KAAKI,aAAc,KAChB8B,kBAAoB7C,EAAE4C,EAAEE,QAAQC,QAAQzC,sBACxC0C,cAAgBhD,EAAE4C,EAAEE,QAAQC,QAAQzC,0BACpC2C,WAAa,IAAI7C,uBAAuB4C,cAAerC,KAAKD,QAEhEmC,kBAAkB3B,SAAS,WAC3B+B,WAAWC,OAAOb,QAAO,WACrBQ,kBAAkBvB,YAAY,gBAGxCc,KAAKzB,WAEHwC,iBAAmB9C,8BAA8B+C,OAAO,SAEvD3C,KAAKkC,GAAGxC,aAAasC,OAAOC,SAAUpC,6BAA6B,SAASsC,EAAGS,UAC5E7C,QAAUR,EAAE4C,EAAEE,QAAQC,QAAQzC,6BAElC+C,KAAKC,cAAcC,iBAEnBJ,iBAAiBK,MAAK,SAASC,OAE3BA,MAAMC,UAAU1D,EAAEQ,SAASI,KAAK,iBAChC6C,MAAME,QAAQ3D,EAAEQ,SAASI,KAAK,cAC9B6C,MAAMG,aAAa5D,EAAEQ,SAASI,KAAK,oBACnC6C,MAAMI,WAAWrD,SACjBiD,MAAMK,OAENlB,EAAEmB,8BAEHC,MAAM9D,aAAaqC,cAG1BpC,aAAaJ,OAAOyC,6BAA8B,CAC9CrC,aAAasC,OAAOC,WAGxBF,6BAA6BG,GAAGxC,aAAasC,OAAOC,SAAU,gBACrDnB,0BACPa,KAAKzB,QAGJJ"}PKB\4"hh'build/preferences_processor_form.min.jsnu[/** * Manages the processor form on the message preferences page. * * @module core_message/preferences_processor_form * @copyright 2016 Ryan Wyllie * @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.mapPKB\ZE?build/message_drawer_view_conversation_state_manager.min.js.mapnu[{"version":3,"file":"message_drawer_view_conversation_state_manager.min.js","sources":["../src/message_drawer_view_conversation_state_manager.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module operates on the view states from the message_drawer_view_conversation module.\n * It exposes functions that can be used to generate new version of the state.\n *\n * Important notes for this module:\n * 1.) The existing state is always immutable. It should never be modified.\n * 2.) All functions that operate on the state should always clone the state and\n * modify the cloned state before returning it.\n *\n * It's important that the states remain immutable because they are diff'd in\n * the message_drawer_view_conversation_patcher module in order to work out what\n * has changed.\n *\n * @module core_message/message_drawer_view_conversation_state_manager\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n\n /**\n * Clone a state, a state is a collection of information about the variables required to build\n * the conversation user interface.\n *\n * @param {Object} state State to clone\n * @return {Object} newstate A copy of the state to clone.\n */\n var cloneState = function(state) {\n // Do a deep extend to make sure we recursively copy objects and\n // arrays so that the new state doesn't contain any references to\n // the old state, e.g. adding a value to an array in the new state\n // shouldn't also add it to the old state.\n return $.extend(true, {}, state);\n };\n\n /**\n * Format messages to be used in a state.\n *\n * @param {Array} messages The messages to format.\n * @param {Number} loggedInUserId The logged in user id.\n * @param {Array} members The converstation members.\n * @return {Array} Formatted messages.\n */\n var formatMessages = function(messages, loggedInUserId, members) {\n return messages.map(function(message) {\n var fromLoggedInUser = message.useridfrom == loggedInUserId;\n return {\n // Stringify the id.\n id: \"\" + message.id,\n fromLoggedInUser: fromLoggedInUser,\n userFrom: members[message.useridfrom],\n text: message.text,\n timeCreated: message.timecreated ? parseInt(message.timecreated, 10) : null\n };\n });\n };\n\n /**\n * Format members to be used in a state.\n *\n * @param {Array} members The messages to format.\n * @return {Array} Formatted members.\n */\n var formatMembers = function(members) {\n return members.map(function(member) {\n return {\n id: member.id,\n fullname: member.fullname,\n profileurl: member.profileurl,\n profileimageurl: member.profileimageurl,\n profileimageurlsmall: member.profileimageurlsmall,\n isonline: member.isonline,\n showonlinestatus: member.showonlinestatus,\n isblocked: member.isblocked,\n iscontact: member.iscontact,\n isdeleted: member.isdeleted,\n canmessage: member.canmessage,\n canmessageevenifblocked: member.canmessageevenifblocked,\n requirescontact: member.requirescontact,\n contactrequests: member.contactrequests || [],\n cancreatecontact: member.cancreatecontact,\n };\n });\n };\n\n /**\n * Create an initial (blank) state.\n *\n * @param {Number} midnight Midnight time.\n * @param {Number} loggedInUserId The logged in user id.\n * @param {Number} id The conversation id.\n * @param {Number} messagePollMin The message poll start timeout in seconds.\n * @param {Number} messagePollMax The message poll max timeout limit in seconds.\n * @param {Number} messagePollAfterMax The message poll frequency in seconds to reset to after max limit is reached.\n * @return {Object} Initial state.\n */\n var buildInitialState = function(\n midnight,\n loggedInUserId,\n id,\n messagePollMin,\n messagePollMax,\n messagePollAfterMax\n ) {\n return {\n midnight: midnight,\n loggedInUserId: loggedInUserId,\n id: id,\n messagePollMin: messagePollMin,\n messagePollMax: messagePollMax,\n messagePollAfterMax: messagePollAfterMax,\n name: null,\n subname: null,\n type: null,\n totalMemberCount: null,\n imageUrl: null,\n isFavourite: null,\n isMuted: null,\n canDeleteMessagesForAllUsers: false,\n deleteMessagesForAllUsers: false,\n members: {},\n messages: [],\n hasTriedToLoadMessages: false,\n loadingMessages: true,\n loadingMembers: true,\n loadingConfirmAction: false,\n pendingBlockUserIds: [],\n pendingUnblockUserIds: [],\n pendingRemoveContactIds: [],\n pendingAddContactIds: [],\n pendingDeleteMessageIds: [],\n pendingSendMessageIds: [],\n pendingDeleteConversation: false,\n selectedMessageIds: [],\n showEmojiAutoComplete: false,\n showEmojiPicker: false\n };\n };\n\n /**\n * Add messages to a state and sort them by timecreated.\n *\n * @param {Object} state Current state.\n * @param {Array} messages Messages to add to state.\n * @return {Object} state New state with added messages.\n */\n var addMessages = function(state, messages) {\n var newState = cloneState(state);\n var formattedMessages = formatMessages(messages, state.loggedInUserId, state.members);\n formattedMessages = formattedMessages.map(function(message) {\n message.sendState = null;\n message.timeAdded = Date.now();\n message.errorMessage = null;\n return message;\n });\n var allMessages = state.messages.concat(formattedMessages);\n // Sort the messages. Oldest to newest.\n allMessages.sort(function(a, b) {\n if (a.timeCreated === null && b.timeCreated === null) {\n if (a.timeAdded < b.timeAdded) {\n return -1;\n } else if (a.timeAdded > b.timeAdded) {\n return 1;\n }\n }\n\n if (a.timeCreated === null && b.timeCreated !== null) {\n // A comes after b.\n return 1;\n } else if (a.timeCreated !== null && b.timeCreated === null) {\n // A comes before b.\n return -1;\n } else if (a.timeCreated < b.timeCreated) {\n // A comes before b.\n return -1;\n } else if (a.timeCreated > b.timeCreated) {\n // A comes after b.\n return 1;\n } else if (a.id < b.id) {\n return -1;\n } else if (a.id > b.id) {\n return 1;\n } else {\n return 0;\n }\n });\n\n // Filter out any duplicate messages.\n newState.messages = allMessages.filter(function(message, index, sortedMessages) {\n return !index || message.id != sortedMessages[index - 1].id;\n });\n\n return newState;\n };\n\n /**\n * Update existing messages.\n *\n * @param {Object} state Current state.\n * @param {Array} data 2D array of old and new messages\n * @return {Object} state.\n */\n var updateMessages = function(state, data) {\n var newState = cloneState(state);\n var updatesById = data.reduce(function(carry, messageData) {\n var oldMessage = messageData[0];\n var newMessage = messageData[1];\n var formattedMessages = formatMessages([newMessage], state.loggedInUserId, state.members);\n var formattedMessage = formattedMessages[0];\n\n carry[oldMessage.id] = formattedMessage;\n return carry;\n }, {});\n\n newState.messages = newState.messages.map(function(message) {\n if (message.id in updatesById) {\n return $.extend(message, updatesById[message.id]);\n } else {\n return message;\n }\n });\n\n return newState;\n };\n\n /**\n * Remove messages from state.\n *\n * @param {Object} state Current state.\n * @param {Array} messages Messages to remove from state.\n * @return {Object} state New state with removed messages.\n */\n var removeMessages = function(state, messages) {\n var newState = cloneState(state);\n var removeMessageIds = messages.map(function(message) {\n return \"\" + message.id;\n });\n newState.messages = newState.messages.filter(function(message) {\n return removeMessageIds.indexOf(message.id) < 0;\n });\n\n return newState;\n };\n\n /**\n * Remove messages from state by message id.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Message ids to remove from state.\n * @return {Object} state New state with removed messages.\n */\n var removeMessagesById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.messages = newState.messages.filter(function(message) {\n return messageIds.indexOf(message.id) < 0;\n });\n\n return newState;\n };\n\n /**\n * Add conversation member to state.\n *\n * @param {Object} state Current state.\n * @param {Array} members Conversation members to be added to state.\n * @return {Object} New state with added members.\n */\n var addMembers = function(state, members) {\n var newState = cloneState(state);\n var formattedMembers = formatMembers(members);\n formattedMembers.forEach(function(member) {\n newState.members[member.id] = member;\n });\n return newState;\n };\n\n /**\n * Remove members from state.\n *\n * @param {Object} state Current state.\n * @param {Array} members Members to be removed from state.\n * @return {Object} New state with removed members.\n */\n var removeMembers = function(state, members) {\n var newState = cloneState(state);\n members.forEach(function(member) {\n delete newState.members[member.id];\n });\n return newState;\n };\n\n /**\n * Set the state loading messages attribute.\n *\n * @param {Object} state Current state.\n * @param {Bool} value New loading messages value.\n * @return {Object} New state with loading messages attribute.\n */\n var setLoadingMessages = function(state, value) {\n var newState = cloneState(state);\n newState.loadingMessages = value;\n if (state.loadingMessages && !value) {\n // If we're going from loading to not loading then\n // it means we've tried to load.\n newState.hasTriedToLoadMessages = true;\n }\n return newState;\n };\n\n /**\n * Set the state loading members attribute.\n *\n * @param {Object} state Current state.\n * @param {Bool} value New loading members value.\n * @return {Object} New state with loading members attribute.\n */\n var setLoadingMembers = function(state, value) {\n var newState = cloneState(state);\n newState.loadingMembers = value;\n return newState;\n };\n\n /**\n * Set the conversation id.\n *\n * @param {Object} state Current state.\n * @param {String} value The ID.\n * @return {Object} New state.\n */\n var setId = function(state, value) {\n var newState = cloneState(state);\n newState.id = value;\n return newState;\n };\n\n /**\n * Set the state name attribute.\n *\n * @param {Object} state Current state.\n * @param {String} value New name value.\n * @return {Object} New state with name attribute.\n */\n var setName = function(state, value) {\n var newState = cloneState(state);\n newState.name = value;\n return newState;\n };\n\n /**\n * Set the state subname attribute.\n *\n * @param {Object} state Current state.\n * @param {String} value New subname value.\n * @return {Object} New state.\n */\n var setSubname = function(state, value) {\n var newState = cloneState(state);\n newState.subname = value;\n return newState;\n };\n\n /**\n * Set the conversation type.\n *\n * @param {Object} state Current state.\n * @param {Int} type Conversation type.\n * @return {Object} New state.\n */\n var setType = function(state, type) {\n var newState = cloneState(state);\n newState.type = type;\n return newState;\n };\n\n /**\n * Set whether the conversation is a favourite conversation.\n *\n * @param {Object} state Current state.\n * @param {Bool} isFavourite If it's a favourite.\n * @return {Object} New state.\n */\n var setIsFavourite = function(state, isFavourite) {\n var newState = cloneState(state);\n newState.isFavourite = isFavourite;\n return newState;\n };\n\n /**\n * Set whether the conversation is a muted conversation.\n *\n * @param {Object} state Current state.\n * @param {bool} isMuted If it's muted.\n * @return {Object} New state.\n */\n var setIsMuted = function(state, isMuted) {\n var newState = cloneState(state);\n newState.isMuted = isMuted;\n return newState;\n };\n\n /**\n * Set the total member count.\n *\n * @param {Object} state Current state.\n * @param {String} count The count.\n * @return {Object} New state.\n */\n var setTotalMemberCount = function(state, count) {\n var newState = cloneState(state);\n newState.totalMemberCount = count;\n return newState;\n };\n\n /**\n * Set the conversation image url.\n *\n * @param {Object} state Current state.\n * @param {String} url The url to the image.\n * @return {Object} New state.\n */\n var setImageUrl = function(state, url) {\n var newState = cloneState(state);\n newState.imageUrl = url;\n return newState;\n };\n\n /**\n * Set the state loading confirm action attribute.\n *\n * @param {Object} state Current state.\n * @param {Bool} value New loading confirm action value.\n * @return {Object} New state with loading confirm action attribute.\n */\n var setLoadingConfirmAction = function(state, value) {\n var newState = cloneState(state);\n newState.loadingConfirmAction = value;\n return newState;\n };\n\n /**\n * Set the state pending delete conversation attribute.\n *\n * @param {Object} state Current state.\n * @param {Bool} value New pending delete conversation value.\n * @return {Object} New state with pending delete conversation attribute.\n */\n var setPendingDeleteConversation = function(state, value) {\n var newState = cloneState(state);\n newState.pendingDeleteConversation = value;\n return newState;\n };\n\n /**\n * Set the state of message to pending.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages to delete.\n * @return {Object} New state with array of pending delete message ids.\n */\n var setMessagesSendPendingById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.messages.forEach(function(message) {\n if (messageIds.indexOf(message.id) >= 0) {\n message.sendState = 'pending';\n message.errorMessage = null;\n }\n });\n return newState;\n };\n\n /**\n * Set the state of message to sent.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages to delete.\n * @return {Object} New state with array of pending delete message ids.\n */\n var setMessagesSendSuccessById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.messages.forEach(function(message) {\n if (messageIds.indexOf(message.id) >= 0) {\n message.sendState = 'sent';\n message.errorMessage = null;\n }\n });\n return newState;\n };\n\n /**\n * Set the state of messages to error.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages to delete.\n * @param {string} errorMessage\n * @return {Object} New state with array of pending delete message ids.\n */\n var setMessagesSendFailById = function(state, messageIds, errorMessage) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.messages.forEach(function(message) {\n if (messageIds.indexOf(message.id) >= 0) {\n message.sendState = 'error';\n message.errorMessage = errorMessage;\n }\n });\n return newState;\n };\n\n /**\n * Set the visibility of the emoji picker.\n *\n * @param {Object} state Current state.\n * @param {Bool} show Should the emoji picker be shown.\n * @return {Object} New state with array of pending delete message ids.\n */\n var setShowEmojiPicker = function(state, show) {\n var newState = cloneState(state);\n newState.showEmojiPicker = show;\n return newState;\n };\n\n /**\n * Set whether emojis auto complete suggestions should be shown.\n *\n * @param {Object} state Current state.\n * @param {Bool} show Show the autocomplete\n * @return {Object} New state with array of pending delete message ids.\n */\n var setShowEmojiAutoComplete = function(state, show) {\n var newState = cloneState(state);\n newState.showEmojiAutoComplete = show;\n return newState;\n };\n\n /**\n * Set the state pending block userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to block.\n * @return {Object} New state with array of pending block userids.\n */\n var addPendingBlockUsersById = function(state, userIds) {\n var newState = cloneState(state);\n userIds.forEach(function(id) {\n newState.pendingBlockUserIds.push(id);\n });\n return newState;\n };\n\n /**\n * Set the state pending remove userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to remove.\n * @return {Object} New state with array of pending remove userids.\n */\n var addPendingRemoveContactsById = function(state, userIds) {\n var newState = cloneState(state);\n userIds.forEach(function(id) {\n newState.pendingRemoveContactIds.push(id);\n });\n return newState;\n };\n\n /**\n * Set the state pending unblock userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to unblock.\n * @return {Object} New state with array of pending unblock userids.\n */\n var addPendingUnblockUsersById = function(state, userIds) {\n var newState = cloneState(state);\n userIds.forEach(function(id) {\n newState.pendingUnblockUserIds.push(id);\n });\n return newState;\n };\n\n /**\n * Set the state pending add users to contacts userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to add users to contacts.\n * @return {Object} New state with array of pending add users to contacts userids.\n */\n var addPendingAddContactsById = function(state, userIds) {\n var newState = cloneState(state);\n userIds.forEach(function(id) {\n newState.pendingAddContactIds.push(id);\n });\n return newState;\n };\n\n /**\n * Set the state pending delete messages.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages to delete.\n * @return {Object} New state with array of pending delete message ids.\n */\n var addPendingDeleteMessagesById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds.forEach(function(id) {\n newState.pendingDeleteMessageIds.push(id);\n });\n return newState;\n };\n\n /**\n * Update the state pending block userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to remove from the list of user ids to block.\n * @return {Object} New state with array of pending block userids.\n */\n var removePendingBlockUsersById = function(state, userIds) {\n var newState = cloneState(state);\n newState.pendingBlockUserIds = newState.pendingBlockUserIds.filter(function(id) {\n return userIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Update the state pending remove userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to remove from the list of user ids to remove.\n * @return {Object} New state with array of pending remove userids.\n */\n var removePendingRemoveContactsById = function(state, userIds) {\n var newState = cloneState(state);\n newState.pendingRemoveContactIds = newState.pendingRemoveContactIds.filter(function(id) {\n return userIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Update the state pending unblock userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to remove from the list of user ids to unblock.\n * @return {Object} New state with array of pending unblock userids.\n */\n var removePendingUnblockUsersById = function(state, userIds) {\n var newState = cloneState(state);\n newState.pendingUnblockUserIds = newState.pendingUnblockUserIds.filter(function(id) {\n return userIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Update the state pending add to contacts userids.\n *\n * @param {Object} state Current state.\n * @param {Array} userIds User ids to remove from the list of user ids to add to contacts.\n * @return {Object} New state with array of pending add to contacts userids.\n */\n var removePendingAddContactsById = function(state, userIds) {\n var newState = cloneState(state);\n newState.pendingAddContactIds = newState.pendingAddContactIds.filter(function(id) {\n return userIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Update the state pending delete messages userids.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Message ids to remove from the list of messages to delete.\n * @return {Object} New state with array of messages to delete.\n */\n var removePendingDeleteMessagesById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.pendingDeleteMessageIds = newState.pendingDeleteMessageIds.filter(function(id) {\n return messageIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Add messages to state selected messages.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages that are selected.\n * @return {Object} New state with array of not blocked members.\n */\n var addSelectedMessagesById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.selectedMessageIds = newState.selectedMessageIds.concat(messageIds);\n return newState;\n };\n\n /**\n * Remove messages from the state selected messages.\n *\n * @param {Object} state Current state.\n * @param {Array} messageIds Messages to remove from selected messages.\n * @return {Object} New state with array of selected messages.\n */\n var removeSelectedMessagesById = function(state, messageIds) {\n var newState = cloneState(state);\n messageIds = messageIds.map(function(id) {\n return \"\" + id;\n });\n newState.selectedMessageIds = newState.selectedMessageIds.filter(function(id) {\n return messageIds.indexOf(id) < 0;\n });\n return newState;\n };\n\n /**\n * Mark messages as read.\n *\n * @param {Object} state Current state.\n * @param {Array} readMessages Messages that are read.\n * @return {Object} New state with array of messages that have the isread attribute set.\n */\n var markMessagesAsRead = function(state, readMessages) {\n var newState = cloneState(state);\n var readMessageIds = readMessages.map(function(message) {\n return message.id;\n });\n newState.messages = newState.messages.map(function(message) {\n if (readMessageIds.indexOf(message.id) >= 0) {\n message.isRead = true;\n }\n\n return message;\n });\n return newState;\n };\n\n /**\n * Add a contact request to each of the members that the request is for.\n *\n * @param {Object} state Current state.\n * @param {Array} requests The contact requests\n * @return {Object} New state\n */\n var addContactRequests = function(state, requests) {\n var newState = cloneState(state);\n\n requests.forEach(function(request) {\n var fromUserId = request.userid;\n var toUserId = request.requesteduserid;\n newState.members[fromUserId].contactrequests.push(request);\n newState.members[toUserId].contactrequests.push(request);\n });\n\n return newState;\n };\n\n /**\n * Remove a contact request from the members of that request.\n *\n * @param {Object} state Current state.\n * @param {Array} requests The contact requests\n * @return {Object} New state\n */\n var removeContactRequests = function(state, requests) {\n var newState = cloneState(state);\n requests.forEach(function(request) {\n var fromUserId = request.userid;\n var toUserId = request.requesteduserid;\n\n newState.members[fromUserId].contactrequests = newState.members[fromUserId].contactrequests.filter(function(existing) {\n return existing.userid != fromUserId;\n });\n newState.members[toUserId].contactrequests = newState.members[toUserId].contactrequests.filter(function(existing) {\n return existing.requesteduserid != toUserId;\n });\n });\n\n return newState;\n };\n\n /**\n * Set wheter the message of the conversation can delete for all users.\n *\n * @param {Object} state Current state.\n * @param {Bool} value If it can delete for all users.\n * @return {Object} New state.\n */\n var setCanDeleteMessagesForAllUsers = function(state, value) {\n var newState = cloneState(state);\n newState.canDeleteMessagesForAllUsers = value;\n return newState;\n };\n\n /**\n * Set wheter the messages of the conversation delete for all users.\n *\n * @param {Object} state Current state.\n * @param {Bool} value Delete messages for all users.\n * @return {Object} New state.\n */\n var setDeleteMessagesForAllUsers = function(state, value) {\n var newState = cloneState(state);\n newState.deleteMessagesForAllUsers = value;\n return newState;\n };\n\n return {\n buildInitialState: buildInitialState,\n addMessages: addMessages,\n updateMessages: updateMessages,\n removeMessages: removeMessages,\n removeMessagesById: removeMessagesById,\n addMembers: addMembers,\n removeMembers: removeMembers,\n setLoadingMessages: setLoadingMessages,\n setLoadingMembers: setLoadingMembers,\n setId: setId,\n setName: setName,\n setSubname: setSubname,\n setType: setType,\n setIsFavourite: setIsFavourite,\n setIsMuted: setIsMuted,\n setCanDeleteMessagesForAllUsers: setCanDeleteMessagesForAllUsers,\n setDeleteMessagesForAllUsers: setDeleteMessagesForAllUsers,\n setTotalMemberCount: setTotalMemberCount,\n setImageUrl: setImageUrl,\n setLoadingConfirmAction: setLoadingConfirmAction,\n setPendingDeleteConversation: setPendingDeleteConversation,\n setMessagesSendPendingById: setMessagesSendPendingById,\n setMessagesSendSuccessById: setMessagesSendSuccessById,\n setMessagesSendFailById: setMessagesSendFailById,\n setShowEmojiAutoComplete: setShowEmojiAutoComplete,\n setShowEmojiPicker: setShowEmojiPicker,\n addPendingBlockUsersById: addPendingBlockUsersById,\n addPendingRemoveContactsById: addPendingRemoveContactsById,\n addPendingUnblockUsersById: addPendingUnblockUsersById,\n addPendingAddContactsById: addPendingAddContactsById,\n addPendingDeleteMessagesById: addPendingDeleteMessagesById,\n removePendingBlockUsersById: removePendingBlockUsersById,\n removePendingRemoveContactsById: removePendingRemoveContactsById,\n removePendingUnblockUsersById: removePendingUnblockUsersById,\n removePendingAddContactsById: removePendingAddContactsById,\n removePendingDeleteMessagesById: removePendingDeleteMessagesById,\n addSelectedMessagesById: addSelectedMessagesById,\n removeSelectedMessagesById: removeSelectedMessagesById,\n markMessagesAsRead: markMessagesAsRead,\n addContactRequests: addContactRequests,\n removeContactRequests: removeContactRequests\n };\n});\n"],"names":["define","$","cloneState","state","extend","formatMessages","messages","loggedInUserId","members","map","message","fromLoggedInUser","useridfrom","id","userFrom","text","timeCreated","timecreated","parseInt","buildInitialState","midnight","messagePollMin","messagePollMax","messagePollAfterMax","name","subname","type","totalMemberCount","imageUrl","isFavourite","isMuted","canDeleteMessagesForAllUsers","deleteMessagesForAllUsers","hasTriedToLoadMessages","loadingMessages","loadingMembers","loadingConfirmAction","pendingBlockUserIds","pendingUnblockUserIds","pendingRemoveContactIds","pendingAddContactIds","pendingDeleteMessageIds","pendingSendMessageIds","pendingDeleteConversation","selectedMessageIds","showEmojiAutoComplete","showEmojiPicker","addMessages","newState","formattedMessages","sendState","timeAdded","Date","now","errorMessage","allMessages","concat","sort","a","b","filter","index","sortedMessages","updateMessages","data","updatesById","reduce","carry","messageData","oldMessage","newMessage","formattedMessage","removeMessages","removeMessageIds","indexOf","removeMessagesById","messageIds","addMembers","formattedMembers","member","fullname","profileurl","profileimageurl","profileimageurlsmall","isonline","showonlinestatus","isblocked","iscontact","isdeleted","canmessage","canmessageevenifblocked","requirescontact","contactrequests","cancreatecontact","formatMembers","forEach","removeMembers","setLoadingMessages","value","setLoadingMembers","setId","setName","setSubname","setType","setIsFavourite","setIsMuted","setCanDeleteMessagesForAllUsers","setDeleteMessagesForAllUsers","setTotalMemberCount","count","setImageUrl","url","setLoadingConfirmAction","setPendingDeleteConversation","setMessagesSendPendingById","setMessagesSendSuccessById","setMessagesSendFailById","setShowEmojiAutoComplete","show","setShowEmojiPicker","addPendingBlockUsersById","userIds","push","addPendingRemoveContactsById","addPendingUnblockUsersById","addPendingAddContactsById","addPendingDeleteMessagesById","removePendingBlockUsersById","removePendingRemoveContactsById","removePendingUnblockUsersById","removePendingAddContactsById","removePendingDeleteMessagesById","addSelectedMessagesById","removeSelectedMessagesById","markMessagesAsRead","readMessages","readMessageIds","isRead","addContactRequests","requests","request","fromUserId","userid","toUserId","requesteduserid","removeContactRequests","existing"],"mappings":"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"}PKB\ 0build/message_notification_preference.min.js.mapnu[{"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 .\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 \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"}PKB\Y,,0build/notification_processor_settings.min.js.mapnu[{"version":3,"file":"notification_processor_settings.min.js","sources":["../src/notification_processor_settings.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Load the settings for a message processor.\n *\n * @module core_message/notification_processor_settings\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\nimport $ from 'jquery';\nimport * as Ajax from 'core/ajax';\nimport * as Str from 'core/str';\nimport * as Notification from 'core/notification';\nimport * as CustomEvents from 'core/custom_interaction_events';\nimport Modal from 'core/modal';\nimport * as Fragment from 'core/fragment';\n\nconst SELECTORS = {\n SAVE_BUTTON: '[data-action=\"save\"]',\n CANCEL_BUTTON: '[data-action=\"cancel\"]',\n PROCESSOR: '[data-processor-name]',\n PREFERENCE_ROW: '[data-region=\"preference-row\"]',\n};\n\nexport default class NotificationProcessorSettings extends Modal {\n static TYPE = 'core_message-notification_processor_settings';\n static TEMPLATE = 'core/modal_save_cancel';\n\n /**\n * Constructor for the Modal.\n *\n * @class\n * @param {object} root The root jQuery element for the modal.\n */\n constructor(root) {\n super(root);\n this.name = null;\n this.userId = null;\n this.contextId = null;\n this.element = null;\n this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);\n this.cancelButton = this.getFooter().find(SELECTORS.CANCEL_BUTTON);\n }\n\n /**\n * Set the userid to the given value.\n *\n * @method setUserId\n * @param {int} id The notification userid\n */\n setUserId(id) {\n this.userId = id;\n }\n\n /**\n * Retrieve the current userid, if any.\n *\n * @method getUserId\n * @return {int|null} The notification userid\n */\n getUserId() {\n return this.userId;\n }\n\n /**\n * Set the object to the given value.\n *\n * @method setElement\n * @param {object} element The notification node element.\n */\n setElement(element) {\n this.element = element;\n }\n\n /**\n * Retrieve the current element, if any.\n *\n * @method getElement\n * @return {object|null} The notification node element.\n */\n getElement() {\n return this.element;\n }\n\n /**\n * Set the name to the given value.\n *\n * @method setName\n * @param {string} name The notification name.\n */\n setName(name) {\n this.name = name;\n }\n\n /**\n * Retrieve the current name, if any.\n *\n * @method getName\n * @return {string|null} The notification name.\n */\n getName() {\n return this.name;\n }\n /**\n * Set the context id to the given value.\n *\n * @method setContextId\n * @param {Number} id The notification context id\n */\n setContextId(id) {\n this.contextId = id;\n }\n\n /**\n * Retrieve the current context id, if any.\n *\n * @method getContextId\n * @return {Number|null} The notification context id\n */\n getContextId() {\n return this.contextId;\n }\n\n /**\n * Get the form element from the modal.\n *\n * @method getForm\n * @return {object}\n */\n getForm() {\n return this.getBody().find('form');\n }\n\n /**\n * Disable the buttons in the footer.\n *\n * @method disableButtons\n */\n disableButtons() {\n this.saveButton.prop('disabled', true);\n this.cancelButton.prop('disabled', true);\n }\n\n /**\n * Enable the buttons in the footer.\n *\n * @method enableButtons\n */\n enableButtons() {\n this.saveButton.prop('disabled', false);\n this.cancelButton.prop('disabled', false);\n }\n\n /**\n * Load the title for the modal to the appropriate value\n * depending on message outputs.\n *\n * @method loadTitleContent\n * @return {object} A promise resolved with the new title text.\n */\n loadTitleContent() {\n this.titlePromise = Str.get_string('processorsettings', 'message');\n this.setTitle(this.titlePromise);\n\n return this.titlePromise;\n }\n\n /**\n * Load the body for the modal to the appropriate value\n * depending on message outputs.\n *\n * @method loadBodyContent\n * @return {object} A promise resolved with the fragment html and js from\n */\n loadBodyContent() {\n this.disableButtons();\n\n const args = {\n userid: this.getUserId(),\n type: this.getName(),\n };\n\n this.bodyPromise = Fragment.loadFragment('message', 'processor_settings', this.getContextId(), args);\n this.setBody(this.bodyPromise);\n\n this.bodyPromise.then(() => {\n this.enableButtons();\n return;\n })\n .catch(Notification.exception);\n\n return this.bodyPromise;\n }\n\n /**\n * Load both the title and body content.\n *\n * @method loadAllContent\n * @return {object} promise\n */\n loadAllContent() {\n return $.when(this.loadTitleContent(), this.loadBodyContent());\n }\n\n /**\n * Load the modal content before showing it. This\n * is to allow us to re-use the same modal for creating and\n * editing different message outputs within the page.\n *\n * @method show\n */\n show() {\n this.loadAllContent();\n super.show(this);\n }\n\n /**\n * Clear the notification from the modal when it's closed so\n * that it is loaded fresh next time it's displayed.\n *\n * @method hide\n */\n hide() {\n super.hide(this);\n this.setContextId(null);\n this.setName(null);\n this.setUserId(null);\n }\n\n /**\n * Checks if the processor has been configured. If so then remove the unconfigured\n * status from the interface.\n *\n * @method updateConfiguredStatus\n * @return {Promise|boolean}\n */\n updateConfiguredStatus() {\n const processorHeader = $(this.getElement()).closest(SELECTORS.PROCESSOR);\n\n if (!processorHeader.hasClass('unconfigured')) {\n return false;\n }\n\n const processorName = processorHeader.attr('data-processor-name');\n const request = {\n methodname: 'core_message_get_message_processor',\n args: {\n name: processorName,\n userid: this.userId,\n },\n };\n\n return Ajax.call([request])[0]\n .then((result) => {\n // Check if the user has figured configuring the processor.\n if (result.userconfigured) {\n // If they have then we can enable the settings.\n const notifications = $(SELECTORS.PREFERENCE_ROW + ' [data-processor-name=\"' + processorName + '\"]');\n processorHeader.removeClass('unconfigured');\n notifications.removeClass('disabled');\n }\n return result;\n });\n }\n\n /**\n * Set up all of the event handling for the modal.\n *\n * @method registerEventListeners\n */\n registerEventListeners() {\n // Apply parent event listeners.\n super.registerEventListeners(this);\n\n // When the user clicks the save button we trigger the form submission.\n this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, (e, data) => {\n this.getForm().submit();\n data.originalEvent.preventDefault();\n });\n\n this.getModal().on('mpp:formsubmitted', (e) => {\n this.hide();\n this.updateConfiguredStatus();\n e.stopPropagation();\n });\n\n this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {\n this.hide();\n data.originalEvent.preventDefault();\n e.stopPropagation();\n });\n }\n}\n\nNotificationProcessorSettings.registerModalType();\n"],"names":["SELECTORS","NotificationProcessorSettings","Modal","constructor","root","name","userId","contextId","element","saveButton","this","getFooter","find","cancelButton","setUserId","id","getUserId","setElement","getElement","setName","getName","setContextId","getContextId","getForm","getBody","disableButtons","prop","enableButtons","loadTitleContent","titlePromise","Str","get_string","setTitle","loadBodyContent","args","userid","type","bodyPromise","Fragment","loadFragment","setBody","then","catch","Notification","exception","loadAllContent","$","when","show","hide","updateConfiguredStatus","processorHeader","closest","hasClass","processorName","attr","request","methodname","Ajax","call","result","userconfigured","notifications","removeClass","registerEventListeners","getModal","on","CustomEvents","events","activate","e","data","submit","originalEvent","preventDefault","stopPropagation","registerModalType"],"mappings":"yyDAgCMA,sBACW,uBADXA,wBAEa,yBAFbA,oBAGS,wBAHTA,yBAIc,uCAGCC,sCAAsCC,eAUvDC,YAAYC,YACFA,WACDC,KAAO,UACPC,OAAS,UACTC,UAAY,UACZC,QAAU,UACVC,WAAaC,KAAKC,YAAYC,KAAKZ,4BACnCa,aAAeH,KAAKC,YAAYC,KAAKZ,yBAS9Cc,UAAUC,SACDT,OAASS,GASlBC,mBACWN,KAAKJ,OAShBW,WAAWT,cACFA,QAAUA,QASnBU,oBACWR,KAAKF,QAShBW,QAAQd,WACCA,KAAOA,KAShBe,iBACWV,KAAKL,KAQhBgB,aAAaN,SACJR,UAAYQ,GASrBO,sBACWZ,KAAKH,UAShBgB,iBACWb,KAAKc,UAAUZ,KAAK,QAQ/Ba,sBACShB,WAAWiB,KAAK,YAAY,QAC5Bb,aAAaa,KAAK,YAAY,GAQvCC,qBACSlB,WAAWiB,KAAK,YAAY,QAC5Bb,aAAaa,KAAK,YAAY,GAUvCE,+BACSC,aAAeC,IAAIC,WAAW,oBAAqB,gBACnDC,SAAStB,KAAKmB,cAEZnB,KAAKmB,aAUhBI,uBACSR,uBAECS,KAAO,CACTC,OAAQzB,KAAKM,YACboB,KAAM1B,KAAKU,uBAGViB,YAAcC,SAASC,aAAa,UAAW,qBAAsB7B,KAAKY,eAAgBY,WAC1FM,QAAQ9B,KAAK2B,kBAEbA,YAAYI,MAAK,UACbd,mBAGRe,MAAMC,aAAaC,WAEblC,KAAK2B,YAShBQ,wBACWC,gBAAEC,KAAKrC,KAAKkB,mBAAoBlB,KAAKuB,mBAUhDe,YACSH,uBACCG,KAAKtC,MASfuC,aACUA,KAAKvC,WACNW,aAAa,WACbF,QAAQ,WACRL,UAAU,MAUnBoC,+BACUC,iBAAkB,mBAAEzC,KAAKQ,cAAckC,QAAQpD,yBAEhDmD,gBAAgBE,SAAS,uBACnB,QAGLC,cAAgBH,gBAAgBI,KAAK,uBACrCC,QAAU,CACZC,WAAY,qCACZvB,KAAM,CACF7B,KAAMiD,cACNnB,OAAQzB,KAAKJ,gBAIdoD,KAAKC,KAAK,CAACH,UAAU,GACvBf,MAAMmB,YAECA,OAAOC,eAAgB,OAEjBC,eAAgB,mBAAE9D,yBAA2B,0BAA4BsD,cAAgB,MAC/FH,gBAAgBY,YAAY,gBAC5BD,cAAcC,YAAY,mBAEvBH,UASnBI,+BAEUA,uBAAuBtD,WAGxBuD,WAAWC,GAAGC,aAAaC,OAAOC,SAAUrE,uBAAuB,CAACsE,EAAGC,aACnEhD,UAAUiD,SACfD,KAAKE,cAAcC,yBAGlBT,WAAWC,GAAG,qBAAsBI,SAChCrB,YACAC,yBACLoB,EAAEK,0BAGDV,WAAWC,GAAGC,aAAaC,OAAOC,SAAUrE,yBAAyB,CAACsE,EAAGC,aACrEtB,OACLsB,KAAKE,cAAcC,iBACnBJ,EAAEK,4FAzQO1E,qCACH,gEADGA,yCAEC,0BA4QtBA,8BAA8B2E"}PKB\dkڗ 'build/notification_processor.min.js.mapnu[{"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 .\n\n/**\n * Represents the notification processor (e.g. email, popup, jabber)\n *\n * @module core_message/notification_processor\n * @copyright 2016 Ryan Wyllie \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"}PKB\.C C "build/toggle_contact_button.min.jsnu[/** * Module to add/remove contact using ajax. * * @module core_message/toggle_contact_button * @copyright 2016 Ryan Wyllie * @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 border-0");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.mapPKB\4 *!%!%-build/message_drawer_view_contacts.min.js.mapnu[{"version":3,"file":"message_drawer_view_contacts.min.js","sources":["../src/message_drawer_view_contacts.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the contacts page of the message drawer.\n *\n * @module core_message/message_drawer_view_contacts\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_view_contacts_section_contacts',\n 'core_message/message_drawer_view_contacts_section_requests'\n],\nfunction(\n $,\n PubSub,\n Str,\n MessageDrawerEvents,\n ContactsSection,\n RequestsSection\n) {\n\n var SELECTORS = {\n ACTION_SHOW_CONTACTS_SECTION: '[data-action=\"show-contacts-section\"]',\n ACTION_SHOW_REQUESTS_SECTION: '[data-action=\"show-requests-section\"]',\n CONTACT_REQUEST_COUNT: '[data-region=\"contact-request-count\"]',\n CONTACTS_SECTION_CONTAINER: '[data-section=\"contacts\"]',\n REQUESTS_SECTION_CONTAINER: '[data-section=\"requests\"]',\n };\n\n /**\n * Get the container element for the contacts section.\n *\n * @param {Object} body Contacts page body element.\n * @return {Object}\n */\n var getContactsSectionContainer = function(body) {\n return body.find(SELECTORS.CONTACTS_SECTION_CONTAINER);\n };\n\n /**\n * Get the container element for the requests section.\n *\n * @param {Object} body Contacts page body element.\n * @return {Object}\n */\n var getRequestsSectionContainer = function(body) {\n return body.find(SELECTORS.REQUESTS_SECTION_CONTAINER);\n };\n\n /**\n * Get the element that triggers showing the contacts section.\n *\n * @param {Object} body Contacts page body element.\n * @return {Object}\n */\n var getShowContactsAction = function(body) {\n return body.find(SELECTORS.ACTION_SHOW_CONTACTS_SECTION);\n };\n\n /**\n * Get the element that triggers showing the requests section.\n *\n * @param {Object} body Contacts page body element.\n * @return {Object}\n */\n var getShowRequestsAction = function(body) {\n return body.find(SELECTORS.ACTION_SHOW_REQUESTS_SECTION);\n };\n\n /**\n * Check if the given section is visible.\n *\n * @param {Object} sectionRoot The root element for the section\n * @return {Bool}\n */\n var isSectionVisible = function(sectionRoot) {\n return sectionRoot.hasClass('active');\n };\n\n /**\n * Decrement the contact request count. If the count is zero or below then\n * hide the count.\n *\n * @param {Object} body Conversation body container element.\n * @return {Function} A function to handle decrementing the count.\n */\n var decrementContactRequestCount = function(body) {\n return function() {\n var countContainer = body.find(SELECTORS.CONTACT_REQUEST_COUNT);\n var count = parseInt(countContainer.text(), 10);\n count = isNaN(count) ? 0 : count - 1;\n\n if (count <= 0) {\n countContainer.addClass('hidden');\n } else {\n countContainer.text(count);\n }\n };\n };\n\n /**\n * Listen to and handle events for contacts.\n *\n * @param {Object} body Contacts body container element.\n */\n var registerEventListeners = function(body) {\n var contactsSection = getContactsSectionContainer(body);\n var requestsSection = getRequestsSectionContainer(body);\n var showContactsAction = getShowContactsAction(body);\n var showRequestsAction = getShowRequestsAction(body);\n\n showContactsAction[0].addEventListener('show.bs.tab', function() {\n ContactsSection.show(contactsSection);\n });\n\n showRequestsAction[0].addEventListener('show.bs.tab', function() {\n RequestsSection.show(requestsSection);\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, decrementContactRequestCount(body));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, decrementContactRequestCount(body));\n };\n\n /**\n * Setup the contact page.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Contacts header container element.\n * @param {Object} body Contacts body container element.\n * @param {Object} footer Contacts footer container element.\n * @param {String|null} tab Tab to show, either 'requests' or 'contacts', if any.\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, tab) {\n body = $(body);\n\n if (!body.attr('data-contacts-init')) {\n registerEventListeners(body);\n body.attr('data-contacts-init', true);\n }\n\n var contactsSection = getContactsSectionContainer(body);\n var requestsSection = getRequestsSectionContainer(body);\n\n if (tab) {\n var showContactsAction = getShowContactsAction(body);\n var showRequestsAction = getShowRequestsAction(body);\n\n // Unfortunately we need to hardcode the class changes here rather than trigger\n // the bootstrap tab functionality because the bootstrap JS doesn't appear to be\n // loaded by this point which means the tab plugin isn't added and the event listeners\n // haven't been set up so we can't just trigger a click either.\n if (tab == 'requests') {\n showContactsAction.removeClass('active');\n contactsSection.removeClass('show active');\n showRequestsAction.addClass('active');\n requestsSection.addClass('show active');\n } else {\n showRequestsAction.removeClass('active');\n requestsSection.removeClass('show active');\n showContactsAction.addClass('active');\n contactsSection.addClass('show active');\n }\n }\n\n if (isSectionVisible(contactsSection)) {\n ContactsSection.show(contactsSection);\n } else {\n RequestsSection.show(requestsSection);\n }\n\n return $.Deferred().resolve().promise();\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @return {Object} jQuery promise\n */\n var description = function() {\n return Str.get_string('messagedrawerviewcontacts', 'core_message');\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","PubSub","Str","MessageDrawerEvents","ContactsSection","RequestsSection","SELECTORS","getContactsSectionContainer","body","find","getRequestsSectionContainer","getShowContactsAction","getShowRequestsAction","decrementContactRequestCount","countContainer","count","parseInt","text","isNaN","addClass","show","namespace","header","footer","tab","attr","contactsSection","requestsSection","showContactsAction","showRequestsAction","addEventListener","subscribe","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","registerEventListeners","removeClass","hasClass","Deferred","resolve","promise","description","get_string"],"mappings":";;;;;;;AAsBAA,mDACA,CACI,SACA,cACA,WACA,qCACA,6DACA,+DAEJ,SACIC,EACAC,OACAC,IACAC,oBACAC,gBACAC,qBAGIC,uCAC8B,wCAD9BA,uCAE8B,wCAF9BA,gCAGuB,wCAHvBA,qCAI4B,4BAJ5BA,qCAK4B,4BAS5BC,4BAA8B,SAASC,aAChCA,KAAKC,KAAKH,uCASjBI,4BAA8B,SAASF,aAChCA,KAAKC,KAAKH,uCASjBK,sBAAwB,SAASH,aAC1BA,KAAKC,KAAKH,yCASjBM,sBAAwB,SAASJ,aAC1BA,KAAKC,KAAKH,yCAoBjBO,6BAA+B,SAASL,aACjC,eACCM,eAAiBN,KAAKC,KAAKH,iCAC3BS,MAAQC,SAASF,eAAeG,OAAQ,KAC5CF,MAAQG,MAAMH,OAAS,EAAIA,MAAQ,IAEtB,EACTD,eAAeK,SAAS,UAExBL,eAAeG,KAAKF,eAwFzB,CACHK,KAnDO,SAASC,UAAWC,OAAQd,KAAMe,OAAQC,MACjDhB,KAAOR,EAAEQ,OAECiB,KAAK,yBA/BU,SAASjB,UAC9BkB,gBAAkBnB,4BAA4BC,MAC9CmB,gBAAkBjB,4BAA4BF,MAC9CoB,mBAAqBjB,sBAAsBH,MAC3CqB,mBAAqBjB,sBAAsBJ,MAE/CoB,mBAAmB,GAAGE,iBAAiB,eAAe,WAClD1B,gBAAgBgB,KAAKM,oBAGzBG,mBAAmB,GAAGC,iBAAiB,eAAe,WAClDzB,gBAAgBe,KAAKO,oBAGzB1B,OAAO8B,UAAU5B,oBAAoB6B,yBAA0BnB,6BAA6BL,OAC5FP,OAAO8B,UAAU5B,oBAAoB8B,yBAA0BpB,6BAA6BL,OAiBxF0B,CAAuB1B,MACvBA,KAAKiB,KAAK,sBAAsB,QAGhCC,gBAAkBnB,4BAA4BC,MAC9CmB,gBAAkBjB,4BAA4BF,SAE9CgB,IAAK,KACDI,mBAAqBjB,sBAAsBH,MAC3CqB,mBAAqBjB,sBAAsBJ,MAMpC,YAAPgB,KACAI,mBAAmBO,YAAY,UAC/BT,gBAAgBS,YAAY,eAC5BN,mBAAmBV,SAAS,UAC5BQ,gBAAgBR,SAAS,iBAEzBU,mBAAmBM,YAAY,UAC/BR,gBAAgBQ,YAAY,eAC5BP,mBAAmBT,SAAS,UAC5BO,gBAAgBP,SAAS,uBAIZO,gBAzFFU,SAAS,UA0FxBhC,gBAAgBgB,KAAKM,iBAErBrB,gBAAgBe,KAAKO,iBAGlB3B,EAAEqC,WAAWC,UAAUC,WAc9BC,YANc,kBACPtC,IAAIuC,WAAW,4BAA6B"}PKB\qQQQQ5build/message_drawer_view_conversation_patcher.min.jsnu[/** * This module will take 2 view states from the message_drawer_view_conversation * module and generate a patch that can be given to the * message_drawer_view_conversation_renderer module to update the UI. * * This module should never modify either state. It's purely a read only * module. * * @module core_message/message_drawer_view_conversation_patcher * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_conversation_patcher",["jquery","core/user_date","core_message/message_drawer_view_conversation_constants"],(function($,UserDate,Constants){var sortMessagesByDay=function(messages,midnight){var messagesByDay=messages.reduce((function(carry,message){var timeCreated=message.timeCreated?message.timeCreated:midnight,dayTimestamp=UserDate.getUserMidnightForTimestamp(timeCreated,midnight);return carry.hasOwnProperty(dayTimestamp)?carry[dayTimestamp].push(message):carry[dayTimestamp]=[message],carry}),{});return Object.keys(messagesByDay).map((function(dayTimestamp){return{timestamp:dayTimestamp,messages:messagesByDay[dayTimestamp]}}))},diffArrays=function(a,b,matchFunction){b=b.slice();var missingFromB=[],matches=[];return a.forEach((function(current){for(var found=!1,index=0;index0,newHasContactRequests=newContactRequests.length>0;return oldOtherUser||newOtherUser?oldHasContactRequests&&newHasContactRequests?null:oldHasContactRequests||!newHasContactRequests||newOtherUser.iscontact?!oldOtherUser&&newOtherUser?newOtherUser.iscontact?"contact":null:!newOtherUser&&oldOtherUser?oldOtherUser.iscontact?"non-contact":null:oldOtherUser.iscontact&&!newOtherUser.iscontact?newHasContactRequests?"pending-contact":"non-contact":!oldOtherUser.iscontact&&newOtherUser.iscontact?"contact":null:"pending-contact":null},buildLoadingConfirmationAction=function(state,newState){return!(state.loadingConfirmAction||!newState.loadingConfirmAction)||!(state.loadingConfirmAction&&!newState.loadingConfirmAction)&&null},buildInEditMode=function(state,newState){var oldHasSelectedMessages=state.selectedMessageIds.length>0,newHasSelectedMessages=newState.selectedMessageIds.length>0,numberOfMessagesHasChanged=state.messages.length!=newState.messages.length;return!(oldHasSelectedMessages||!newHasSelectedMessages)||!(oldHasSelectedMessages&&!newHasSelectedMessages)&&(!(!oldHasSelectedMessages||!numberOfMessagesHasChanged)||null)},buildSelectedMessages=function(state,newState){var oldSelectedMessages=state.selectedMessageIds,newSelectedMessages=newState.selectedMessageIds;if(isArrayEqual(oldSelectedMessages,newSelectedMessages))return null;var diff=diffArrays(oldSelectedMessages,newSelectedMessages,(function(a,b){return a==b}));return{count:newSelectedMessages.length,add:diff.missingFromA,remove:diff.missingFromB}},getOtherUserFromState=function(state){return Object.keys(state.members).reduce((function(carry,userId){return userId==state.loggedInUserId||carry||(carry=state.members[userId]),carry}),null)},requiresContactRequest=function(loggedInUserId,user){if(user.canmessage)return!1;var hasSentContactRequest=user.contactrequests.filter((function(request){return request.userid==loggedInUserId||request.requesteduserid})).length>0;return user.requirescontact&&!user.iscontact&&!hasSentContactRequest},buildRequireAddContact=function(state,newState){var oldOtherUser=getOtherUserFromState(state),newOtherUser=getOtherUserFromState(newState),hadMessages=state.messages.length>0,hasMessages=newState.messages.length>0,loggedInUserId=newState.loggedInUserId,prevRequiresContactRequest=oldOtherUser&&requiresContactRequest(loggedInUserId,oldOtherUser),nextRequiresContactRequest=newOtherUser&&requiresContactRequest(loggedInUserId,newOtherUser),finishedAddContact=!1===buildConfirmAddContact(state,newState);if(!state.hasTriedToLoadMessages&&!newState.hasTriedToLoadMessages)return null;if(!oldOtherUser&&!newOtherUser)return null;if(!oldOtherUser&&nextRequiresContactRequest)return{show:!0,hasMessages:hasMessages,user:newOtherUser};if(finishedAddContact&&nextRequiresContactRequest)return{show:!0,hasMessages:hasMessages,user:newOtherUser};if(state.hasTriedToLoadMessages&&newState.hasTriedToLoadMessages){if(!prevRequiresContactRequest&&nextRequiresContactRequest)return{show:!0,hasMessages:hasMessages,user:newOtherUser};if(prevRequiresContactRequest&&!nextRequiresContactRequest)return{show:!1,hasMessages:hasMessages}}return!state.hasTriedToLoadMessages&&newState.hasTriedToLoadMessages&&nextRequiresContactRequest?{show:!0,hasMessages:hasMessages,user:newOtherUser}:state.hasTriedToLoadMessages&&!newState.hasTriedToLoadMessages&&prevRequiresContactRequest?{show:!1,hasMessages:hadMessages}:null},buildFooterPatchTypePrivate=function(state,newState){var loadingFirstMessages=buildLoadingFirstMessages(state,newState),inEditMode=buildInEditMode(state,newState),requireAddContact=buildRequireAddContact(state,newState),requireUnblock=function(state,newState){var oldOtherUser=getOtherUserFromState(state),newOtherUser=getOtherUserFromState(newState);return oldOtherUser||newOtherUser?oldOtherUser&&!newOtherUser?!oldOtherUser.isblocked&&null:!oldOtherUser&&newOtherUser?!!newOtherUser.isblocked||null:!(oldOtherUser.isblocked||!newOtherUser.isblocked)||!(oldOtherUser.isblocked&&!newOtherUser.isblocked)&&null:null}(state,newState),unableToMessage=function(state,newState){var oldOtherUser=getOtherUserFromState(state),newOtherUser=getOtherUserFromState(newState);return newState.type==Constants.CONVERSATION_TYPES.SELF?null:oldOtherUser||newOtherUser?oldOtherUser&&!newOtherUser?!oldOtherUser.canmessage||null:!oldOtherUser&&newOtherUser?!newOtherUser.canmessage||null:!(!oldOtherUser.canmessage&&newOtherUser.canmessage)&&(!(!oldOtherUser.canmessage||newOtherUser.canmessage)||null):null}(state,newState),showRequireAddContact=null!==requireAddContact?requireAddContact.show&&requireAddContact.hasMessages:null,otherUser=getOtherUserFromState(newState),generateReturnValue=function(checkValue,successReturn){if(checkValue)return successReturn;if(null!==checkValue&&!checkValue){if(!otherUser)return{type:"content"};if(otherUser.isblocked)return{type:"unblock"};if(newState.messages.length&&requiresContactRequest(newState.loggedInUserId,otherUser))return{type:"add-contact",user:otherUser};if(!otherUser.canmessage&&otherUser.requirescontact&&!otherUser.iscontact)return{type:"unable-to-message"}}return null};if(null===loadingFirstMessages&&null===inEditMode&&null===requireAddContact&&null===requireUnblock)return null;for(var checks=[[loadingFirstMessages,{type:"placeholder"}],[inEditMode,{type:"edit-mode"}],[unableToMessage,{type:"unable-to-message"}],[requireUnblock,{type:"unblock"}],[showRequireAddContact,{type:"add-contact",user:otherUser}]],i=0;i0,newRequest=newSentRequests.length>0;return oldRequest||!newRequest||newOtherUser.iscontact?!(oldOtherUser&&!oldOtherUser.iscontact&&newRequest&&newOtherUser.iscontact)&&(!(oldRequest&&!newRequest)&&null):newOtherUser.fullname};return{buildPatch:function(state,newState){var config={all:{reset:buildReset,conversation:buildConversationPatch,scrollToMessage:buildScrollToMessagePatch,loadingMembers:buildLoadingMembersPatch,loadingFirstMessages:buildLoadingFirstMessages,loadingMessages:buildLoadingMessages,confirmDeleteSelectedMessages:buildConfirmDeleteSelectedMessages,inEditMode:buildInEditMode,selectedMessages:buildSelectedMessages,isFavourite:buildIsFavourite,isMuted:buildIsMuted,showEmojiPicker:buildShowEmojiPicker,showEmojiAutoComplete:buildShowEmojiAutoComplete}};config[Constants.CONVERSATION_TYPES.PRIVATE]={header:buildHeaderPatchTypePrivate,footer:buildFooterPatchTypePrivate,confirmBlockUser:buildConfirmBlockUser,confirmUnblockUser:buildConfirmUnblockUser,confirmAddContact:buildConfirmAddContact,confirmRemoveContact:buildConfirmRemoveContact,confirmContactRequest:buildConfirmContactRequest,confirmDeleteConversation:buildConfirmDeleteConversation,isBlocked:buildIsBlocked,isContact:buildIsContact,loadingConfirmAction:buildLoadingConfirmationAction,requireAddContact:buildRequireAddContact,contactRequestSent:buildContactRequestSent},config[Constants.CONVERSATION_TYPES.PUBLIC]={header:buildHeaderPatchTypePublic,footer:buildFooterPatchTypePublic},config[Constants.CONVERSATION_TYPES.SELF]={header:buildHeaderPatchTypeSelf,footer:buildFooterPatchTypePublic,confirmDeleteConversation:buildConfirmDeleteConversation,selfConversationMessage:buildSelfConversationMessage};var patchConfig=$.extend({},config.all);return newState.type&&newState.type in config&&(patchConfig=$.extend(patchConfig,config[newState.type])),Object.keys(patchConfig).reduce((function(patch,key){var value=(0,patchConfig[key])(state,newState);return null!==value&&(patch[key]=value),patch}),{})}}})); //# sourceMappingURL=message_drawer_view_conversation_patcher.min.js.mapPKB\ build/message_preferences.min.jsnu[/** * Controls the message preference page. * * @module core_message/message_preferences * @copyright 2016 Ryan Wyllie * @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.mapPKB\@s\$build/message_user_button.min.js.mapnu[{"version":3,"file":"message_user_button.min.js","sources":["../src/message_user_button.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to message a user from their profile page.\n *\n * @module core_message/message_user_button\n * @copyright 2019 Mark Nelson \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/custom_interaction_events', 'core_message/message_drawer_helper', 'core/templates'],\n function($, CustomEvents, MessageDrawerHelper, Templates) {\n\n\n var SELECTORS = {\n MESSAGE_TEXTAREA: '[data-region=\"send-message-txt\"]',\n MESSAGE_USER_BUTTON: '#message-user-button',\n MESSAGE_JUMP: '[data-region=\"jumpto\"]'\n };\n\n var TEMPLATES = {\n CONTENT: 'core_message/message_jumpto'\n };\n\n /**\n * Get the id for the user being messaged.\n *\n * @param {object} element jQuery object for the button\n * @return {int}\n */\n var getUserId = function(element) {\n return parseInt(element.attr('data-userid'));\n };\n\n /**\n * Returns the conversation id, 0 if none.\n *\n * @param {object} element jQuery object for the button\n * @return {int}\n */\n var getConversationId = function(element) {\n return parseInt(element.attr('data-conversationid'));\n };\n\n /**\n * Handles opening the messaging drawer to send a\n * message to a given user.\n *\n * @method enhance\n * @param {object} element jQuery object for the button\n */\n var send = function(element) {\n element = $(element);\n\n var args = {\n conversationid: getConversationId(element),\n buttonid: $(element).attr('id'),\n userid: getUserId(element)\n };\n\n Templates.render(TEMPLATES.CONTENT, {})\n .then(function(html) {\n element.after(html);\n })\n .then(function() {\n $(SELECTORS.MESSAGE_USER_BUTTON).next().focus(function() {\n $(SELECTORS.MESSAGE_TEXTAREA).focus();\n });\n });\n\n CustomEvents.define(element, [CustomEvents.events.activate]);\n\n element.on(CustomEvents.events.activate, function(e, data) {\n if ($(e.target).hasClass('active')) {\n MessageDrawerHelper.hide();\n $(SELECTORS.MESSAGE_USER_BUTTON).next().attr('tabindex', -1);\n } else {\n $(SELECTORS.MESSAGE_USER_BUTTON).next().attr('tabindex', 0);\n if (args.conversationid) {\n MessageDrawerHelper.showConversation(args);\n } else {\n MessageDrawerHelper.createConversationWithUser(args);\n }\n }\n $(e.target).focus();\n $(e.target).toggleClass('active');\n e.preventDefault();\n data.originalEvent.preventDefault();\n });\n };\n\n return /** @alias module:core_message/message_user_button */ {\n send: send\n };\n });\n"],"names":["define","$","CustomEvents","MessageDrawerHelper","Templates","SELECTORS","TEMPLATES","getUserId","element","parseInt","attr","getConversationId","send","args","conversationid","buttonid","userid","render","then","html","after","next","focus","events","activate","on","e","data","target","hasClass","hide","showConversation","createConversationWithUser","toggleClass","preventDefault","originalEvent"],"mappings":";;;;;;;AAsBAA,0CAAO,CAAC,SAAU,iCAAkC,qCAAsC,mBACtF,SAASC,EAAGC,aAAcC,oBAAqBC,eAGvCC,2BACkB,mCADlBA,8BAEqB,uBAIrBC,kBACS,8BASTC,UAAY,SAASC,gBACdC,SAASD,QAAQE,KAAK,iBAS7BC,kBAAoB,SAASH,gBACtBC,SAASD,QAAQE,KAAK,+BAkD4B,CACzDE,KAzCO,SAASJ,SAChBA,QAAUP,EAAEO,aAERK,KAAO,CACPC,eAAgBH,kBAAkBH,SAClCO,SAAUd,EAAEO,SAASE,KAAK,MAC1BM,OAAQT,UAAUC,UAGtBJ,UAAUa,OAAOX,kBAAmB,IAC/BY,MAAK,SAASC,MACXX,QAAQY,MAAMD,SAEjBD,MAAK,WACFjB,EAAEI,+BAA+BgB,OAAOC,OAAM,WAC1CrB,EAAEI,4BAA4BiB,cAI1CpB,aAAaF,OAAOQ,QAAS,CAACN,aAAaqB,OAAOC,WAElDhB,QAAQiB,GAAGvB,aAAaqB,OAAOC,UAAU,SAASE,EAAGC,MAC7C1B,EAAEyB,EAAEE,QAAQC,SAAS,WACrB1B,oBAAoB2B,OACpB7B,EAAEI,+BAA+BgB,OAAOX,KAAK,YAAa,KAE1DT,EAAEI,+BAA+BgB,OAAOX,KAAK,WAAY,GACrDG,KAAKC,eACLX,oBAAoB4B,iBAAiBlB,MAErCV,oBAAoB6B,2BAA2BnB,OAGvDZ,EAAEyB,EAAEE,QAAQN,QACZrB,EAAEyB,EAAEE,QAAQK,YAAY,UACxBP,EAAEQ,iBACFP,KAAKQ,cAAcD"}PKB\|(Ia#d#d6build/message_drawer_view_conversation_renderer.min.jsnu[/** * This module updates the UI for the conversation page in the message * drawer. * * The module will take a patch from the message_drawer_view_conversation_patcher * module and update the UI to reflect the changes. * * This is the only module that ever modifies the UI of the conversation page. * * @module core_message/message_drawer_view_conversation_renderer * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_conversation_renderer",["jquery","core/notification","core/str","core/templates","core/user_date","core_message/message_drawer_view_conversation_constants","core/aria","core/truncate"],(function($,Notification,Str,Templates,UserDate,Constants,Aria,Truncate){var SELECTORS=Constants.SELECTORS,TEMPLATES=Constants.TEMPLATES,CONVERSATION_TYPES=Constants.CONVERSATION_TYPES,getMessagesContainer=function(body){return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER)},getSelfConversationMessageContainer=function(body){return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER)},getContactRequestSentContainer=function(body){return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER)},getFooterContentContainer=function(footer){return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER)},getFooterEditModeContainer=function(footer){return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER)},getFooterPlaceholderContainer=function(footer){return footer.find(SELECTORS.PLACEHOLDER_CONTAINER)},showFooterPlaceholder=function(footer){getFooterPlaceholderContainer(footer).removeClass("hidden")},getFooterRequireContactContainer=function(footer){return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER)},getFooterRequireUnblockContainer=function(footer){return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER)},getFooterUnableToMessageContainer=function(footer){return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER)},hideAllFooterElements=function(footer){!function(footer){getFooterContentContainer(footer).addClass("hidden")}(footer),function(footer){getFooterEditModeContainer(footer).addClass("hidden")}(footer),function(footer){getFooterPlaceholderContainer(footer).addClass("hidden")}(footer),function(footer){getFooterRequireContactContainer(footer).addClass("hidden")}(footer),function(footer){getFooterRequireUnblockContainer(footer).addClass("hidden")}(footer),function(footer){getFooterUnableToMessageContainer(footer).addClass("hidden")}(footer)},getContentPlaceholderContainer=function(body){return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER)},getHeaderContent=function(header){return header.find(SELECTORS.HEADER)},showHeaderContent=function(header){getHeaderContent(header).removeClass("hidden")},hideHeaderContent=function(header){getHeaderContent(header).addClass("hidden")},getHeaderEditMode=function(header){return header.find(SELECTORS.HEADER_EDIT_MODE)},hideHeaderEditMode=function(header){getHeaderEditMode(header).addClass("hidden")},getHeaderPlaceholderContainer=function(header){return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER)},showHeaderPlaceholder=function(header){getHeaderPlaceholderContainer(header).removeClass("hidden")},hideHeaderPlaceholder=function(header){getHeaderPlaceholderContainer(header).addClass("hidden")},getMessageElement=function(body,messageId){return getMessagesContainer(body).find('[data-message-id="'+messageId+'"]')},getDayElement=function(body,dayTimeCreated){return getMessagesContainer(body).find('[data-day-id="'+dayTimeCreated+'"]')},getMoreMessagesLoadingIconContainer=function(body){return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER)},getConfirmDialogueContainer=function(root){return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER)},showConfirmDialogueContainer=function(root){var container=getConfirmDialogueContainer(root),siblings=container.siblings(":not(.hidden)");Aria.hide(siblings.get()),siblings.attr("data-confirm-dialogue-hidden",!0),container.removeClass("hidden")},hideConfirmDialogueContainer=function(root){var container=getConfirmDialogueContainer(root),siblings=container.siblings('[data-confirm-dialogue-hidden="true"]');Aria.unhide(siblings.get()),siblings.removeAttr("data-confirm-dialogue-hidden"),container.addClass("hidden")},formatMessagesForTemplate=function(messages,datesCache){return messages.map((function(message){return{id:message.id,isread:message.isRead,fromloggedinuser:message.fromLoggedInUser,userfrom:message.userFrom,text:message.text,formattedtime:message.timeCreated?datesCache[message.timeCreated]:null}}))},renderConversation=function(header,body,footer,data){var renderingPromises=[],hasAddDays=data.days.add.length>0,hasAddMessages=data.messages.add.length>0,hasUpdateMessages=data.messages.update.length>0,timestampsToFormat=[],datesCachePromise=$.Deferred().resolve({}).promise();return hasAddDays&&(timestampsToFormat=timestampsToFormat.concat(data.days.add.reduce((function(carry,day){return carry.concat(day.value.messages.reduce((function(timestamps,message){return message.timeCreated&×tamps.push(message.timeCreated),timestamps}),[]))}),[]))),hasAddMessages&&(timestampsToFormat=timestampsToFormat.concat(data.messages.add.reduce((function(timestamps,message){return message.value.timeCreated&×tamps.push(message.value.timeCreated),timestamps}),[]))),hasUpdateMessages&&(timestampsToFormat=timestampsToFormat.concat(data.messages.update.reduce((function(timestamps,message){return message.before.timeCreated!=message.after.timeCreated&×tamps.push(message.after.timeCreated),timestamps}),[]))),timestampsToFormat.length&&(datesCachePromise=Str.get_string("strftimetime24","core_langconfig").then((function(format){var requests=timestampsToFormat.map((function(timestamp){return{timestamp:timestamp,format:format}}));return UserDate.get(requests)})).then((function(formattedTimes){return timestampsToFormat.reduce((function(carry,timestamp,index){return carry[timestamp]=formattedTimes[index],carry}),{})}))),hasAddDays&&renderingPromises.push(datesCachePromise.then((function(datesCache){return function(header,body,footer,days,datesCache){var messagesContainer=getMessagesContainer(body),daysRenderPromises=days.map((function(data){var timestampDate=new Date(1e3*data.value.timestamp);return Templates.render(TEMPLATES.DAY,{timestamp:data.value.timestamp,currentyear:timestampDate.getFullYear()===(new Date).getFullYear(),messages:formatMessagesForTemplate(data.value.messages,datesCache)})}));return $.when.apply($,daysRenderPromises).then((function(){days.forEach((function(data,index){daysRenderPromises[index].then((function(html){if(data.before){var element=getDayElement(body,data.before.timestamp);return $(html).insertBefore(element)}return messagesContainer.append(html)})).catch((function(){}))}))}))}(0,body,0,data.days.add,datesCache)}))),hasAddMessages&&renderingPromises.push(datesCachePromise.then((function(datesCache){return function(header,body,footer,messages,datesCache){var messagesData=messages.map((function(data){return data.value})),formattedMessages=formatMessagesForTemplate(messagesData,datesCache);return Templates.render(TEMPLATES.MESSAGES,{messages:formattedMessages}).then((function(html){var messageList=$(html);messages.forEach((function(data){var messageHtml=messageList.find('[data-message-id="'+data.value.id+'"]');if(data.before){var element=getMessageElement(body,data.before.id);return messageHtml.insertBefore(element)}return getDayElement(body,data.day.timestamp).find(SELECTORS.DAY_MESSAGES_CONTAINER).append(messageHtml)}))}))}(0,body,0,data.messages.add,datesCache)}))),hasUpdateMessages&&renderingPromises.push(datesCachePromise.then((function(datesCache){return function(header,body,footer,messages,datesCache){messages.forEach((function(message){var before=message.before,after=message.after,element=getMessageElement(body,before.id);if(before.id!=after.id&&element.attr("data-message-id",after.id),before.timeCreated!=after.timeCreated){var formattedTime=datesCache[after.timeCreated];element.find(SELECTORS.LOADING_ICON_CONTAINER).addClass("hidden"),element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass("hidden")}if(before.sendState!=after.sendState){var loading=element.find(SELECTORS.LOADING_ICON_CONTAINER),time=element.find(SELECTORS.TIME_CREATED),retry=element.find(SELECTORS.RETRY_SEND);switch(loading.addClass("hidden"),Aria.hide(loading.get()),time.addClass("hidden"),Aria.hide(time.get()),retry.addClass("hidden"),Aria.hide(retry.get()),element.removeClass("border border-danger"),after.sendState){case"pending":loading.removeClass("hidden"),Aria.unhide(loading.get());break;case"error":retry.removeClass("hidden"),Aria.unhide(retry.get()),element.addClass("border border-danger");break;case"sent":time.removeClass("hidden"),Aria.unhide(time.get())}}if(before.text!=after.text&&element.find(SELECTORS.TEXT_CONTAINER).html(after.text),before.errorMessage!=after.errorMessage){var messageContainer=element.find(SELECTORS.ERROR_MESSAGE_CONTAINER);message=messageContainer.find(SELECTORS.ERROR_MESSAGE),after.errorMessage?(messageContainer.removeClass("hidden"),Aria.unhide(messageContainer.get()),message.text(after.errorMessage)):(messageContainer.addClass("hidden"),Aria.unhide(messageContainer.get()),message.text(""))}}))}(0,body,0,data.messages.update,datesCache)}))),data.days.remove.length>0&&function(body,days){days.forEach((function(data){getDayElement(body,data.timestamp).remove()}))}(body,data.days.remove),data.messages.remove.length>0&&function(body,messages){messages.forEach((function(data){getMessageElement(body,data.id).remove()}))}(body,data.messages.remove),$.when.apply($,renderingPromises)},renderHeader=function(header,body,footer,data){var headerContainer=getHeaderContent(header),template=TEMPLATES.HEADER_PUBLIC;return data.context.showrouteback="false"===header.attr("data-from-panel"),data.type==CONVERSATION_TYPES.PRIVATE?template=data.showControls?TEMPLATES.HEADER_PRIVATE:TEMPLATES.HEADER_PRIVATE_NO_CONTROLS:data.type==CONVERSATION_TYPES.SELF&&(template=TEMPLATES.HEADER_SELF),Templates.render(template,data.context).then((function(html,js){Templates.replaceNodeContents(headerContainer,html,js)}))},renderFooter=function(header,body,footer,data){switch(hideAllFooterElements(footer),data.type){case"placeholder":return showFooterPlaceholder(footer);case"add-contact":return Str.get_strings([{key:"requirecontacttomessage",component:"core_message",param:data.user.fullname},{key:"isnotinyourcontacts",component:"core_message",param:data.user.fullname}]).then((function(strings){var title=strings[1],text=strings[0],footerContainer=getFooterRequireContactContainer(footer);return footerContainer.find(SELECTORS.TITLE).text(title),footerContainer.find(SELECTORS.TEXT).text(text),function(footer){getFooterRequireContactContainer(footer).removeClass("hidden")}(footer),strings}));case"edit-mode":return function(footer){getFooterEditModeContainer(footer).removeClass("hidden")}(footer);case"content":return function(footer){getFooterContentContainer(footer).removeClass("hidden")}(footer);case"unblock":return function(footer){getFooterRequireUnblockContainer(footer).removeClass("hidden")}(footer);case"unable-to-message":return function(footer){getFooterUnableToMessageContainer(footer).removeClass("hidden")}(footer)}return!0},renderScrollToMessage=function(header,body,footer,messageId){var messagesContainer=getMessagesContainer(body),position=getMessageElement(body,messageId).position();if(position){var scrollTop=messagesContainer.scrollTop()+position.top;messagesContainer.scrollTop(scrollTop)}},renderLoadingMembers=function(header,body,footer,isLoadingMembers){isLoadingMembers?(hideHeaderContent(header),showHeaderPlaceholder(header)):(showHeaderContent(header),hideHeaderPlaceholder(header))},renderLoadingFirstMessages=function(header,body,footer,isLoadingFirstMessages){isLoadingFirstMessages?(function(body){getMessagesContainer(body).addClass("hidden")}(body),function(body){getContentPlaceholderContainer(body).removeClass("hidden")}(body)):(function(body){getMessagesContainer(body).removeClass("hidden")}(body),function(body){getContentPlaceholderContainer(body).addClass("hidden")}(body))},renderLoadingMessages=function(header,body,footer,isLoading){isLoading?function(body){getMoreMessagesLoadingIconContainer(body).removeClass("hidden")}(body):function(body){getMoreMessagesLoadingIconContainer(body).addClass("hidden")}(body)},renderShowEmojiPicker=function(header,body,footer,show){var container=function(footer){return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER)}(footer);show?(container.removeClass("hidden"),Aria.unhide(container.get()),container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus()):(container.addClass("hidden"),Aria.hide(container.get()))},renderShowEmojiAutoComplete=function(header,body,footer,show){var container=function(footer){return footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER)}(footer);show?(container.removeClass("hidden"),Aria.unhide(container.get())):(container.addClass("hidden"),Aria.hide(container.get()))},showConfirmDialogue=function(header,body,footer,buttonSelectors,bodyText,headerText,canCancel,skipHeader,showOk){var dialogue=getConfirmDialogueContainer(body),buttons=buttonSelectors.map((function(selector){return dialogue.find(selector)})),cancelButton=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON),okayButton=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON),text=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT),dialogueHeader=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);(dialogue.find("button").addClass("hidden"),canCancel?cancelButton.removeClass("hidden"):cancelButton.addClass("hidden"),showOk?okayButton.removeClass("hidden"):okayButton.addClass("hidden"),headerText)?((dialogueHeader=$('

    ')).text(headerText),dialogue.find(SELECTORS.CONFIRM_DIALOGUE).prepend(dialogueHeader)):dialogueHeader.length&&dialogueHeader.remove();buttons.forEach((function(button){button.removeClass("hidden")})),text.text(bodyText),showConfirmDialogueContainer(footer),showConfirmDialogueContainer(body),skipHeader||showConfirmDialogueContainer(header),dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(":visible").first().focus()},hideConfirmDialogue=function(header,body,footer){var dialogue=getConfirmDialogueContainer(body),cancelButton=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON),okayButton=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON),text=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT),dialogueHeader=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);return hideCheckDeleteDialogue(body),hideConfirmDialogueContainer(body),hideConfirmDialogueContainer(footer),hideConfirmDialogueContainer(header),dialogue.find("button").addClass("hidden"),cancelButton.removeClass("hidden"),okayButton.removeClass("hidden"),text.text(""),dialogueHeader.length&&dialogueHeader.remove(),header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus(),!0},renderConfirmBlockUser=function(header,body,footer,user){if(user){const username=truncateUsername(user.fullname);return user.canmessageevenifblocked?Str.get_string("cantblockuser","core_message",username).then((function(string){return showConfirmDialogue(header,body,footer,[],string,"",!1,!1,!0)})):Str.get_string("blockuserconfirm","core_message",username).then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_BLOCK],string,"",!0,!1)}))}return hideConfirmDialogue(header,body,footer)},renderConfirmUnblockUser=function(header,body,footer,user){return user?Str.get_string("unblockuserconfirm","core_message",truncateUsername(user.fullname)).then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_UNBLOCK],string,"",!0,!1)})):hideConfirmDialogue(header,body,footer)},renderConfirmAddContact=function(header,body,footer,user){if(user){const userFullName=Truncate.truncate(user.fullname,{length:30,words:!0,ellipsis:"..."});return Str.get_string("addcontactconfirm","core_message",userFullName).then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_ADD_CONTACT],string,"",!0,!1)}))}return hideConfirmDialogue(header,body,footer)},renderConfirmRemoveContact=function(header,body,footer,user){return user?Str.get_string("removecontactconfirm","core_message",truncateUsername(user.fullname)).then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT],string,"",!0,!1)})):hideConfirmDialogue(header,body,footer)},renderConfirmDeleteSelectedMessages=function(header,body,footer,data){var showmessage=null;return data.type==CONVERSATION_TYPES.SELF?showmessage="deleteselectedmessagesconfirmselfconversation":data.canDeleteMessagesForAllUsers?(showCheckDeleteDialogue(body),showmessage="deleteforeveryoneselectedmessagesconfirm"):showmessage="deleteselectedmessagesconfirm",data.show?Str.get_string(showmessage,"core_message").then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES],string,"",!0,!1)})):hideConfirmDialogue(header,body,footer)},renderConfirmDeleteConversation=function(header,body,footer,type){var showmessage=null;return type==CONVERSATION_TYPES.SELF?showmessage="deleteallselfconfirm":type&&(showmessage="deleteallconfirm"),showmessage?Str.get_string(showmessage,"core_message").then((function(string){return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION],string,"",!0,!1)})):hideConfirmDialogue(header,body,footer)},renderConfirmContactRequest=function(header,body,footer,user){return user?Str.get_string("userwouldliketocontactyou","core_message",truncateUsername(user.fullname)).then((function(string){var buttonSelectors=[SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,SELECTORS.ACTION_DECLINE_CONTACT_REQUEST];return showConfirmDialogue(header,body,footer,buttonSelectors,string,"",!1,!0)})):hideConfirmDialogue(header,body,footer)},showCheckDeleteDialogue=function(body){getConfirmDialogueContainer(body).find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER).removeClass("hidden")},hideCheckDeleteDialogue=function(body){var dialogue=getConfirmDialogueContainer(body),checkboxRegion=dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE).prop("checked",!1),checkboxRegion.addClass("hidden")},renderIsBlocked=function(header,body,footer,isBlocked){isBlocked?(header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass("hidden"),header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass("hidden")):(header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass("hidden"),header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass("hidden"))},renderIsFavourite=function(header,body,footer,state){var favouriteIcon=header.find(SELECTORS.FAVOURITE_ICON_CONTAINER),addFavourite=header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE),removeFavourite=header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE);switch(state){case"hide":favouriteIcon.addClass("hidden"),addFavourite.addClass("hidden"),removeFavourite.addClass("hidden");break;case"show-add":favouriteIcon.addClass("hidden"),addFavourite.removeClass("hidden"),removeFavourite.addClass("hidden");break;case"show-remove":favouriteIcon.removeClass("hidden"),addFavourite.addClass("hidden"),removeFavourite.removeClass("hidden")}},renderIsMuted=function(header,body,footer,state){var muteIcon=header.find(SELECTORS.MUTED_ICON_CONTAINER),setMuted=header.find(SELECTORS.ACTION_CONFIRM_MUTE),unsetMuted=header.find(SELECTORS.ACTION_CONFIRM_UNMUTE);switch(state){case"hide":muteIcon.addClass("hidden"),setMuted.addClass("hidden"),unsetMuted.addClass("hidden");break;case"show-mute":muteIcon.addClass("hidden"),setMuted.removeClass("hidden"),unsetMuted.addClass("hidden");break;case"show-unmute":muteIcon.removeClass("hidden"),setMuted.addClass("hidden"),unsetMuted.removeClass("hidden")}},renderIsContact=function(header,body,footer,state){var addContact=header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT),removeContact=header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT);switch(state){case"pending-contact":addContact.addClass("hidden"),removeContact.addClass("hidden");break;case"contact":addContact.addClass("hidden"),removeContact.removeClass("hidden");break;case"non-contact":addContact.removeClass("hidden"),removeContact.addClass("hidden")}},renderLoadingConfirmAction=function(header,body,footer,isLoading){var dialogue=getConfirmDialogueContainer(body),buttons=dialogue.find("button"),buttonText=dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT),loadingIcon=dialogue.find(SELECTORS.LOADING_ICON_CONTAINER);isLoading?(buttons.prop("disabled",!0),buttonText.addClass("hidden"),loadingIcon.removeClass("hidden")):(buttons.prop("disabled",!1),buttonText.removeClass("hidden"),loadingIcon.addClass("hidden"))},renderInEditMode=function(header,body,footer,inEditMode){var messages=null;inEditMode?((messages=body.find(SELECTORS.MESSAGE_NOT_SELECTED)).find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass("hidden"),hideHeaderContent(header),function(header){getHeaderEditMode(header).removeClass("hidden")}(header)):((messages=getMessagesContainer(body)).find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass("hidden"),messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass("hidden"),showHeaderContent(header),hideHeaderEditMode(header))},renderSelectedMessages=function(header,body,footer,data){var hasSelectedMessages=data.count>0;data.add.length&&data.add.forEach((function(messageId){var message=getMessageElement(body,messageId);message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass("hidden"),message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass("hidden"),message.attr("aria-checked",!0)})),data.remove.length&&data.remove.forEach((function(messageId){var message=getMessageElement(body,messageId);hasSelectedMessages&&message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass("hidden"),message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass("hidden"),message.attr("aria-checked",!1)})),function(header,value){getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value)}(header,data.count)},renderRequireAddContact=function(header,body,footer,data){return data.show&&!data.hasMessages?Str.get_strings([{key:"requirecontacttomessage",component:"core_message",param:data.user.fullname},{key:"isnotinyourcontacts",component:"core_message",param:data.user.fullname}]).then((function(strings){var title=strings[1],text=strings[0];return showConfirmDialogue(header,body,footer,[SELECTORS.ACTION_REQUEST_ADD_CONTACT],text,title,!1,!0)})):hideConfirmDialogue(header,body,footer)},renderSelfConversationMessage=function(header,body,footer,displayMessage){var container=getSelfConversationMessageContainer(body);return displayMessage?container.removeClass("hidden"):container.addClass("hidden"),!0},renderContactRequestSent=function(header,body,footer,userFullName){var container=getContactRequestSentContainer(body);return userFullName?Str.get_string("yourcontactrequestpending","core_message",truncateUsername(userFullName)).then((function(string){return container.find(SELECTORS.TEXT).text(string),container.removeClass("hidden"),string})):(container.addClass("hidden"),!0)},renderReset=function(header,body,footer){return hideConfirmDialogue(header,body,footer),function(body){getContactRequestSentContainer(body).addClass("hidden")}(body),function(body){getSelfConversationMessageContainer(body).addClass("hidden")}(body),function(header){hideHeaderContent(header),hideHeaderEditMode(header),hideHeaderPlaceholder(header)}(header),showHeaderPlaceholder(header),hideAllFooterElements(footer),showFooterPlaceholder(footer),!0},truncateUsername=function(username){return Truncate.truncate(username,{length:30,words:!0,ellipsis:"..."})};return{render:function(header,body,footer,patch){var configs=[{reset:renderReset},{conversation:renderConversation,header:renderHeader,footer:renderFooter,confirmBlockUser:renderConfirmBlockUser,confirmUnblockUser:renderConfirmUnblockUser,confirmAddContact:renderConfirmAddContact,confirmRemoveContact:renderConfirmRemoveContact,confirmDeleteSelectedMessages:renderConfirmDeleteSelectedMessages,confirmDeleteConversation:renderConfirmDeleteConversation,confirmContactRequest:renderConfirmContactRequest,requireAddContact:renderRequireAddContact,selfConversationMessage:renderSelfConversationMessage,contactRequestSent:renderContactRequestSent},{loadingMembers:renderLoadingMembers,loadingFirstMessages:renderLoadingFirstMessages,loadingMessages:renderLoadingMessages,isBlocked:renderIsBlocked,isContact:renderIsContact,isFavourite:renderIsFavourite,isMuted:renderIsMuted,loadingConfirmAction:renderLoadingConfirmAction,inEditMode:renderInEditMode,showEmojiPicker:renderShowEmojiPicker,showEmojiAutoComplete:renderShowEmojiAutoComplete},{scrollToMessage:renderScrollToMessage,selectedMessages:renderSelectedMessages}],processConfig=function(config){var results=[];for(var key in patch)if(config.hasOwnProperty(key)){var renderFunc=config[key],patchValue=patch[key];results.push(renderFunc(header,body,footer,patchValue))}return results},renderingPromises=processConfig(configs[0]);return renderingPromises=renderingPromises.concat(processConfig(configs[1])),$.when.apply($,renderingPromises).then((function(){for(var i=2;ibuild/message_drawer_view_contacts_section_requests.min.js.mapnu[{"version":3,"file":"message_drawer_view_contacts_section_requests.min.js","sources":["../src/message_drawer_view_contacts_section_requests.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the requests section of the contacts page.\n *\n * @module core_message/message_drawer_view_contacts_section_requests\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/pubsub',\n 'core/templates',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_lazy_load_list'\n],\nfunction(\n $,\n Notification,\n PubSub,\n Templates,\n MessageRepository,\n MessageDrawerEvents,\n LazyLoadList\n) {\n\n var SELECTORS = {\n CONTACT_REQUEST: '[data-region=\"contact-request\"]'\n };\n\n var TEMPLATES = {\n REQUESTS_LIST: 'core_message/message_drawer_view_contacts_body_section_requests_list'\n };\n\n /**\n * Render the requests in the content container.\n *\n * @param {Object} contentContainer List container element.\n * @param {Array} requests List of requests.\n * @return {Object} jQuery promise\n */\n var render = function(contentContainer, requests) {\n var formattedRequests = requests.map(function(request) {\n return {\n // This is actually the user id.\n id: request.id,\n profileimageurl: request.profileimageurl,\n fullname: request.fullname\n };\n });\n return Templates.render(TEMPLATES.REQUESTS_LIST, {requests: formattedRequests})\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Load the user contacts and call the renderer.\n *\n * @param {Object} listRoot The lazy loaded list root element\n * @param {Integer} userId The logged in user id.\n * @return {Object} jQuery promise\n */\n var load = function(listRoot, userId) {\n return MessageRepository.getContactRequests(userId)\n .then(function(requests) {\n LazyLoadList.setLoadedAll(listRoot, true);\n return requests;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Handle when a contact request is accepted or declined by removing the contact\n * list from the page.\n *\n * @param {Object} root The section root element\n * @return {Function} The event handler function\n */\n var handleContactRequestProcessed = function(root) {\n return function(request) {\n root.find('[data-request-id=\"' + request.userid + '\"]').remove();\n var contactRequests = root.find(SELECTORS.CONTACT_REQUEST);\n\n if (!contactRequests.length) {\n LazyLoadList.showEmptyMessage(root);\n LazyLoadList.hideContent(root);\n }\n };\n };\n\n /**\n * Listen for any events that might affect the requests section.\n *\n * @param {Object} root The section root element\n */\n var registerEventListeners = function(root) {\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, handleContactRequestProcessed(root));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, handleContactRequestProcessed(root));\n };\n\n /**\n * Setup the requests section.\n *\n * @param {Object} root Requests section container.\n */\n var show = function(root) {\n if (!root.attr('data-contacts-init')) {\n registerEventListeners(root);\n root.attr('data-contacts-init', true);\n }\n\n // The root element is already the lazy loaded list root.\n LazyLoadList.show(root, load, render);\n };\n\n return {\n show: show,\n };\n});\n"],"names":["define","$","Notification","PubSub","Templates","MessageRepository","MessageDrawerEvents","LazyLoadList","SELECTORS","TEMPLATES","render","contentContainer","requests","formattedRequests","map","request","id","profileimageurl","fullname","then","html","append","catch","exception","load","listRoot","userId","getContactRequests","setLoadedAll","handleContactRequestProcessed","root","find","userid","remove","length","showEmptyMessage","hideContent","show","attr","subscribe","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","registerEventListeners"],"mappings":";;;;;;;AAsBAA,oEACA,CACI,SACA,oBACA,cACA,iBACA,kCACA,qCACA,+CAEJ,SACIC,EACAC,aACAC,OACAC,UACAC,kBACAC,oBACAC,kBAGIC,0BACiB,kCAGjBC,wBACe,uEAUfC,OAAS,SAASC,iBAAkBC,cAChCC,kBAAoBD,SAASE,KAAI,SAASC,eACnC,CAEHC,GAAID,QAAQC,GACZC,gBAAiBF,QAAQE,gBACzBC,SAAUH,QAAQG,oBAGnBd,UAAUM,OAAOD,wBAAyB,CAACG,SAAUC,oBACvDM,MAAK,SAASC,aACXT,iBAAiBU,OAAOD,MACjBA,QAEVE,MAAMpB,aAAaqB,YAUxBC,KAAO,SAASC,SAAUC,eACnBrB,kBAAkBsB,mBAAmBD,QACvCP,MAAK,SAASP,iBACXL,aAAaqB,aAAaH,UAAU,GAC7Bb,YAEVU,MAAMpB,aAAaqB,YAUxBM,8BAAgC,SAASC,aAClC,SAASf,SACZe,KAAKC,KAAK,qBAAuBhB,QAAQiB,OAAS,MAAMC,SAClCH,KAAKC,KAAKvB,2BAEX0B,SACjB3B,aAAa4B,iBAAiBL,MAC9BvB,aAAa6B,YAAYN,eA8B9B,CACHO,KAXO,SAASP,MACXA,KAAKQ,KAAK,yBAXU,SAASR,MAClC3B,OAAOoC,UAAUjC,oBAAoBkC,yBAA0BX,8BAA8BC,OAC7F3B,OAAOoC,UAAUjC,oBAAoBmC,yBAA0BZ,8BAA8BC,OAUzFY,CAAuBZ,MACvBA,KAAKQ,KAAK,sBAAsB,IAIpC/B,aAAa8B,KAAKP,KAAMN,KAAMd"}PKB\)c.c.;build/message_drawer_view_conversation_constants.min.js.mapnu[{"version":3,"file":"message_drawer_view_conversation_constants.min.js","sources":["../src/message_drawer_view_conversation_constants.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Constant values for the conversation page in the message drawer.\n *\n * @module core_message/message_drawer_view_conversation_constants\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([], function() {\n\n var SELECTORS = {\n ACTION_ACCEPT_CONTACT_REQUEST: '[data-action=\"accept-contact-request\"]',\n ACTION_CANCEL_CONFIRM: '[data-action=\"cancel-confirm\"]',\n ACTION_OKAY_CONFIRM: '[data-action=\"okay-confirm\"]',\n ACTION_CANCEL_EDIT_MODE: '[data-action=\"cancel-edit-mode\"]',\n ACTION_CONFIRM_ADD_CONTACT: '[data-action=\"confirm-add-contact\"]',\n ACTION_CONFIRM_BLOCK: '[data-action=\"confirm-block\"]',\n ACTION_CONFIRM_DELETE_SELECTED_MESSAGES: '[data-action=\"confirm-delete-selected-messages\"]',\n ACTION_CONFIRM_DELETE_CONVERSATION: '[data-action=\"confirm-delete-conversation\"]',\n ACTION_CONFIRM_FAVOURITE: '[data-action=\"confirm-favourite\"]',\n ACTION_CONFIRM_MUTE: '[data-action=\"confirm-mute\"]',\n ACTION_CONFIRM_UNFAVOURITE: '[data-action=\"confirm-unfavourite\"]',\n ACTION_CONFIRM_REMOVE_CONTACT: '[data-action=\"confirm-remove-contact\"]',\n ACTION_CONFIRM_UNBLOCK: '[data-action=\"confirm-unblock\"]',\n ACTION_CONFIRM_UNMUTE: '[data-action=\"confirm-unmute\"]',\n ACTION_DECLINE_CONTACT_REQUEST: '[data-action=\"decline-contact-request\"]',\n ACTION_REQUEST_ADD_CONTACT: '[data-action=\"request-add-contact\"]',\n ACTION_REQUEST_BLOCK: '[data-action=\"request-block\"]',\n ACTION_REQUEST_DELETE_CONVERSATION: '[data-action=\"request-delete-conversation\"]',\n ACTION_REQUEST_DELETE_SELECTED_MESSAGES: '[data-action=\"delete-selected-messages\"]',\n ACTION_REQUEST_REMOVE_CONTACT: '[data-action=\"request-remove-contact\"]',\n ACTION_REQUEST_UNBLOCK: '[data-action=\"request-unblock\"]',\n ACTION_VIEW_CONTACT: '[data-action=\"view-contact\"]',\n ACTION_VIEW_GROUP_INFO: '[data-action=\"view-group-info\"]',\n CAN_RECEIVE_FOCUS: 'input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]',\n CONFIRM_DIALOGUE: '[data-region=\"confirm-dialogue\"]',\n CONFIRM_DIALOGUE_BUTTON_TEXT: '[data-region=\"dialogue-button-text\"]',\n CONFIRM_DIALOGUE_CANCEL_BUTTON: '[data-action=\"cancel-confirm\"]',\n CONFIRM_DIALOGUE_CONTAINER: '[data-region=\"confirm-dialogue-container\"]',\n CONFIRM_DIALOGUE_HEADER: '[data-region=\"dialogue-header\"]',\n CONFIRM_DIALOGUE_OKAY_BUTTON: '[data-action=\"okay-confirm\"]',\n CONFIRM_DIALOGUE_TEXT: '[data-region=\"dialogue-text\"]',\n CONTACT_REQUEST_SENT_MESSAGE_CONTAINER: '[data-region=\"contact-request-sent-message-container\"]',\n CONTENT_PLACEHOLDER_CONTAINER: '[data-region=\"content-placeholder\"]',\n CONTENT_CONTAINER: '[data-region=\"content-container\"]',\n CONTENT_MESSAGES_CONTAINER: '[data-region=\"content-message-container\"]',\n CONTENT_MESSAGES_FOOTER_CONTAINER: '[data-region=\"content-messages-footer-container\"]',\n CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER: '[data-region=\"content-messages-footer-edit-mode-container\"]',\n CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER: '[data-region=\"content-messages-footer-require-contact-container\"]',\n CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER: '[data-region=\"content-messages-footer-require-unblock-container\"]',\n CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER: '[data-region=\"content-messages-footer-unable-to-message\"]',\n DAY_MESSAGES_CONTAINER: '[data-region=\"day-messages-container\"]',\n DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE: '[data-region=\"delete-messages-for-all-users-toggle\"]',\n DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER: '[data-region=\"delete-messages-for-all-users-toggle-container\"]',\n EMOJI_AUTO_COMPLETE_CONTAINER: '[data-region=\"emoji-auto-complete-container\"]',\n EMOJI_PICKER_CONTAINER: '[data-region=\"emoji-picker-container\"]',\n EMOJI_PICKER: '[data-region=\"emoji-picker\"]',\n EMOJI_PICKER_SEARCH_INPUT: '[data-region=\"search-input\"]',\n ERROR_MESSAGE_CONTAINER: '[data-region=\"error-message-container\"]',\n ERROR_MESSAGE: '[data-region=\"error-message\"]',\n FAVOURITE_ICON_CONTAINER: '[data-region=\"favourite-icon-container\"]',\n FOOTER_CONTAINER: '[data-region=\"content-messages-footer-container\"]',\n HEADER: '[data-region=\"header-content\"]',\n HEADER_EDIT_MODE: '[data-region=\"header-edit-mode\"]',\n HEADER_PLACEHOLDER_CONTAINER: '[data-region=\"header-placeholder\"]',\n LOADING_ICON_CONTAINER: '[data-region=\"loading-icon-container\"]',\n MESSAGE: '[data-region=\"message\"]',\n MESSAGE_NOT_SELECTED: '[data-region=\"message\"][aria-checked=\"false\"]',\n MESSAGE_NOT_SELECTED_ICON: '[data-region=\"not-selected-icon\"]',\n MESSAGE_SELECTED_ICON: '[data-region=\"selected-icon\"]',\n MESSAGES: '[data-region=\"content-message-container\"]',\n MESSAGES_CONTAINER: '[data-region=\"content-message-container\"]',\n MESSAGES_SELECTED_COUNT: '[data-region=\"message-selected-court\"]',\n MESSAGE_TEXT_AREA: '[data-region=\"send-message-txt\"]',\n MORE_MESSAGES_LOADING_ICON_CONTAINER: '[data-region=\"more-messages-loading-icon-container\"]',\n MUTED_ICON_CONTAINER: '[data-region=\"muted-icon-container\"]',\n PLACEHOLDER_CONTAINER: '[data-region=\"placeholder-container\"]',\n RETRY_SEND: '[data-region=\"retry-send\"]',\n SELF_CONVERSATION_MESSAGE_CONTAINER: '[data-region=\"self-conversation-message-container\"]',\n SEND_MESSAGE_BUTTON: '[data-action=\"send-message\"]',\n SEND_MESSAGE_ICON_CONTAINER: '[data-region=\"send-icon-container\"]',\n TEXT: '[data-region=\"text\"]',\n TEXT_CONTAINER: '[data-region=\"text-container\"]',\n TIME_CREATED: '[data-region=\"time-created\"]',\n TITLE: '[data-region=\"title\"]',\n TOGGLE_EMOJI_PICKER_BUTTON: '[data-action=\"toggle-emoji-picker\"]'\n };\n\n var TEMPLATES = {\n HEADER_PRIVATE: 'core_message/message_drawer_view_conversation_header_content_type_private',\n HEADER_PRIVATE_NO_CONTROLS: 'core_message/message_drawer_view_conversation_header_content_type_private_no_controls',\n HEADER_PUBLIC: 'core_message/message_drawer_view_conversation_header_content_type_public',\n HEADER_SELF: 'core_message/message_drawer_view_conversation_header_content_type_self',\n DAY: 'core_message/message_drawer_view_conversation_body_day',\n MESSAGE: 'core_message/message_drawer_view_conversation_body_message',\n MESSAGES: 'core_message/message_drawer_view_conversation_body_messages'\n };\n\n // Conversation types. They must have the same values defined in \\core_message\\api.\n var CONVERSATION_TYPES = {\n PRIVATE: 1,\n PUBLIC: 2,\n SELF: 3\n };\n\n return {\n SELECTORS: SELECTORS,\n TEMPLATES: TEMPLATES,\n CONVERSATION_TYPES: CONVERSATION_TYPES,\n NEWEST_MESSAGES_FIRST: true,\n LOAD_MESSAGE_LIMIT: 100,\n MILLISECONDS_IN_SEC: 1000\n };\n});\n"],"names":["define","SELECTORS","ACTION_ACCEPT_CONTACT_REQUEST","ACTION_CANCEL_CONFIRM","ACTION_OKAY_CONFIRM","ACTION_CANCEL_EDIT_MODE","ACTION_CONFIRM_ADD_CONTACT","ACTION_CONFIRM_BLOCK","ACTION_CONFIRM_DELETE_SELECTED_MESSAGES","ACTION_CONFIRM_DELETE_CONVERSATION","ACTION_CONFIRM_FAVOURITE","ACTION_CONFIRM_MUTE","ACTION_CONFIRM_UNFAVOURITE","ACTION_CONFIRM_REMOVE_CONTACT","ACTION_CONFIRM_UNBLOCK","ACTION_CONFIRM_UNMUTE","ACTION_DECLINE_CONTACT_REQUEST","ACTION_REQUEST_ADD_CONTACT","ACTION_REQUEST_BLOCK","ACTION_REQUEST_DELETE_CONVERSATION","ACTION_REQUEST_DELETE_SELECTED_MESSAGES","ACTION_REQUEST_REMOVE_CONTACT","ACTION_REQUEST_UNBLOCK","ACTION_VIEW_CONTACT","ACTION_VIEW_GROUP_INFO","CAN_RECEIVE_FOCUS","CONFIRM_DIALOGUE","CONFIRM_DIALOGUE_BUTTON_TEXT","CONFIRM_DIALOGUE_CANCEL_BUTTON","CONFIRM_DIALOGUE_CONTAINER","CONFIRM_DIALOGUE_HEADER","CONFIRM_DIALOGUE_OKAY_BUTTON","CONFIRM_DIALOGUE_TEXT","CONTACT_REQUEST_SENT_MESSAGE_CONTAINER","CONTENT_PLACEHOLDER_CONTAINER","CONTENT_CONTAINER","CONTENT_MESSAGES_CONTAINER","CONTENT_MESSAGES_FOOTER_CONTAINER","CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER","CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER","CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER","CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER","DAY_MESSAGES_CONTAINER","DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE","DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER","EMOJI_AUTO_COMPLETE_CONTAINER","EMOJI_PICKER_CONTAINER","EMOJI_PICKER","EMOJI_PICKER_SEARCH_INPUT","ERROR_MESSAGE_CONTAINER","ERROR_MESSAGE","FAVOURITE_ICON_CONTAINER","FOOTER_CONTAINER","HEADER","HEADER_EDIT_MODE","HEADER_PLACEHOLDER_CONTAINER","LOADING_ICON_CONTAINER","MESSAGE","MESSAGE_NOT_SELECTED","MESSAGE_NOT_SELECTED_ICON","MESSAGE_SELECTED_ICON","MESSAGES","MESSAGES_CONTAINER","MESSAGES_SELECTED_COUNT","MESSAGE_TEXT_AREA","MORE_MESSAGES_LOADING_ICON_CONTAINER","MUTED_ICON_CONTAINER","PLACEHOLDER_CONTAINER","RETRY_SEND","SELF_CONVERSATION_MESSAGE_CONTAINER","SEND_MESSAGE_BUTTON","SEND_MESSAGE_ICON_CONTAINER","TEXT","TEXT_CONTAINER","TIME_CREATED","TITLE","TOGGLE_EMOJI_PICKER_BUTTON","TEMPLATES","HEADER_PRIVATE","HEADER_PRIVATE_NO_CONTROLS","HEADER_PUBLIC","HEADER_SELF","DAY","CONVERSATION_TYPES","PRIVATE","PUBLIC","SELF","NEWEST_MESSAGES_FIRST","LOAD_MESSAGE_LIMIT","MILLISECONDS_IN_SEC"],"mappings":";;;;;;;AAsBAA,iEAAO,IAAI,iBAiGA,CACHC,UAhGY,CACZC,8BAA+B,yCAC/BC,sBAAuB,iCACvBC,oBAAqB,+BACrBC,wBAAyB,mCACzBC,2BAA4B,sCAC5BC,qBAAsB,gCACtBC,wCAAyC,mDACzCC,mCAAoC,8CACpCC,yBAA0B,oCAC1BC,oBAAqB,+BACrBC,2BAA4B,sCAC5BC,8BAA+B,yCAC/BC,uBAAwB,kCACxBC,sBAAuB,iCACvBC,+BAAgC,0CAChCC,2BAA4B,sCAC5BC,qBAAsB,gCACtBC,mCAAoC,8CACpCC,wCAAyC,2CACzCC,8BAA+B,yCAC/BC,uBAAwB,kCACxBC,oBAAqB,+BACrBC,uBAAwB,kCACxBC,kBAAmB,4EACnBC,iBAAkB,mCAClBC,6BAA8B,uCAC9BC,+BAAgC,iCAChCC,2BAA4B,6CAC5BC,wBAAyB,kCACzBC,6BAA8B,+BAC9BC,sBAAuB,gCACvBC,uCAAwC,yDACxCC,8BAA+B,sCAC/BC,kBAAmB,oCACnBC,2BAA4B,4CAC5BC,kCAAmC,oDACnCC,4CAA6C,8DAC7CC,kDAAmD,oEACnDC,kDAAmD,oEACnDC,oDAAqD,4DACrDC,uBAAwB,yCACxBC,qCAAsC,uDACtCC,+CAAgD,iEAChDC,8BAA+B,gDAC/BC,uBAAwB,yCACxBC,aAAc,+BACdC,0BAA2B,+BAC3BC,wBAAyB,0CACzBC,cAAe,gCACfC,yBAA0B,2CAC1BC,iBAAkB,oDAClBC,OAAQ,iCACRC,iBAAkB,mCAClBC,6BAA8B,qCAC9BC,uBAAwB,yCACxBC,QAAS,0BACTC,qBAAsB,gDACtBC,0BAA2B,oCAC3BC,sBAAuB,gCACvBC,SAAU,4CACVC,mBAAoB,4CACpBC,wBAAyB,yCACzBC,kBAAmB,mCACnBC,qCAAsC,uDACtCC,qBAAsB,uCACtBC,sBAAuB,wCACvBC,WAAY,6BACZC,oCAAqC,sDACrCC,oBAAqB,+BACrBC,4BAA6B,sCAC7BC,KAAM,uBACNC,eAAgB,iCAChBC,aAAc,+BACdC,MAAO,wBACPC,2BAA4B,uCAsB5BC,UAnBY,CACZC,eAAgB,4EAChBC,2BAA4B,wFAC5BC,cAAe,2EACfC,YAAa,yEACbC,IAAK,yDACLzB,QAAS,6DACTI,SAAU,+DAaVsB,mBATqB,CACrBC,QAAS,EACTC,OAAQ,EACRC,KAAM,GAONC,uBAAuB,EACvBC,mBAAoB,IACpBC,oBAAqB"}PKB\_# &build/message_drawer_events.min.js.mapnu[{"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 .\n\n/**\n * Events for the message drawer.\n *\n * @module core_message/message_drawer_events\n * @copyright 2018 Ryan Wyllie \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"}PKB\3~+build/message_drawer_view_search.min.js.mapnu[{"version":3,"file":"message_drawer_view_search.min.js","sources":["../src/message_drawer_view_search.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the search page of the message drawer.\n *\n * @module core_message/message_drawer_view_search\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pubsub',\n 'core/str',\n 'core/templates',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n],\nfunction(\n $,\n CustomEvents,\n Notification,\n PubSub,\n Str,\n Templates,\n Repository,\n Events\n) {\n\n var MESSAGE_SEARCH_LIMIT = 50;\n var USERS_SEARCH_LIMIT = 50;\n var USERS_INITIAL_SEARCH_LIMIT = 3;\n\n var SELECTORS = {\n BLOCK_ICON_CONTAINER: '[data-region=\"block-icon-container\"]',\n CANCEL_SEARCH_BUTTON: '[data-action=\"cancel-search\"]',\n CONTACTS_CONTAINER: '[data-region=\"contacts-container\"]',\n CONTACTS_LIST: '[data-region=\"contacts-container\"] [data-region=\"list\"]',\n EMPTY_MESSAGE_CONTAINER: '[data-region=\"empty-message-container\"]',\n LIST: '[data-region=\"list\"]',\n LOADING_ICON_CONTAINER: '[data-region=\"loading-icon-container\"]',\n LOADING_PLACEHOLDER: '[data-region=\"loading-placeholder\"]',\n MESSAGES_LIST: '[data-region=\"messages-container\"] [data-region=\"list\"]',\n MESSAGES_CONTAINER: '[data-region=\"messages-container\"]',\n NON_CONTACTS_CONTAINER: '[data-region=\"non-contacts-container\"]',\n NON_CONTACTS_LIST: '[data-region=\"non-contacts-container\"] [data-region=\"list\"]',\n SEARCH_ICON_CONTAINER: '[data-region=\"search-icon-container\"]',\n SEARCH_ACTION: '[data-action=\"search\"]',\n SEARCH_INPUT: '[data-region=\"search-input\"]',\n SEARCH_RESULTS_CONTAINER: '[data-region=\"search-results-container\"]',\n LOAD_MORE_USERS: '[data-action=\"load-more-users\"]',\n LOAD_MORE_MESSAGES: '[data-action=\"load-more-messages\"]',\n BUTTON_TEXT: '[data-region=\"button-text\"]',\n NO_RESULTS_CONTAINTER: '[data-region=\"no-results-container\"]',\n ALL_CONTACTS_CONTAINER: '[data-region=\"all-contacts-container\"]'\n };\n\n var TEMPLATES = {\n CONTACTS_LIST: 'core_message/message_drawer_contacts_list',\n NON_CONTACTS_LIST: 'core_message/message_drawer_non_contacts_list',\n MESSAGES_LIST: 'core_message/message_drawer_messages_list'\n };\n\n /**\n * Get the logged in user id.\n *\n * @param {Object} body Search body container element.\n * @return {Number} User id.\n */\n var getLoggedInUserId = function(body) {\n return body.attr('data-user-id');\n };\n\n /**\n * Show the no messages container element.\n *\n * @param {Object} body Search body container element.\n * @return {Object} No messages container element.\n */\n var getEmptyMessageContainer = function(body) {\n return body.find(SELECTORS.EMPTY_MESSAGE_CONTAINER);\n };\n\n /**\n * Get the search loading icon.\n *\n * @param {Object} header Search header container element.\n * @return {Object} Loading icon element.\n */\n var getLoadingIconContainer = function(header) {\n return header.find(SELECTORS.LOADING_ICON_CONTAINER);\n };\n\n /**\n * Get the loading container element.\n *\n * @param {Object} body Search body container element.\n * @return {Object} Loading container element.\n */\n var getLoadingPlaceholder = function(body) {\n return body.find(SELECTORS.LOADING_PLACEHOLDER);\n };\n\n /**\n * Get the search icon container.\n *\n * @param {Object} header Search header container element.\n * @return {Object} Search icon container.\n */\n var getSearchIconContainer = function(header) {\n return header.find(SELECTORS.SEARCH_ICON_CONTAINER);\n };\n\n /**\n * Get the search input container.\n *\n * @param {Object} header Search header container element.\n * @return {Object} Search input container.\n */\n var getSearchInput = function(header) {\n return header.find(SELECTORS.SEARCH_INPUT);\n };\n\n /**\n * Get the search results container.\n *\n * @param {Object} body Search body container element.\n * @return {Object} Search results container.\n */\n var getSearchResultsContainer = function(body) {\n return body.find(SELECTORS.SEARCH_RESULTS_CONTAINER);\n };\n\n /**\n * Get the search contacts container.\n *\n * @param {Object} body Search body container element.\n * @return {Object} Search contacts container.\n */\n var getContactsContainer = function(body) {\n return body.find(SELECTORS.CONTACTS_CONTAINER);\n };\n\n /**\n * Get the search non contacts container.\n *\n * @param {Object} body Search body container element.\n * @return {Object} Search non contacts container.\n */\n var getNonContactsContainer = function(body) {\n return body.find(SELECTORS.NON_CONTACTS_CONTAINER);\n };\n\n /**\n * Get the search messages container.\n *\n * @param {Object} body Search body container element.\n * @return {Object} Search messages container.\n */\n var getMessagesContainer = function(body) {\n return body.find(SELECTORS.MESSAGES_CONTAINER);\n };\n\n\n /**\n * Show the messages empty container.\n *\n * @param {Object} body Search body container element.\n */\n var showEmptyMessage = function(body) {\n getEmptyMessageContainer(body).removeClass('hidden');\n };\n\n /**\n * Hide the messages empty container.\n *\n * @param {Object} body Search body container element.\n */\n var hideEmptyMessage = function(body) {\n getEmptyMessageContainer(body).addClass('hidden');\n };\n\n\n /**\n * Show the loading icon.\n *\n * @param {Object} header Search header container element.\n */\n var showLoadingIcon = function(header) {\n getLoadingIconContainer(header).removeClass('hidden');\n };\n\n /**\n * Hide the loading icon.\n *\n * @param {Object} header Search header container element.\n */\n var hideLoadingIcon = function(header) {\n getLoadingIconContainer(header).addClass('hidden');\n };\n\n /**\n * Show loading placeholder.\n *\n * @param {Object} body Search body container element.\n */\n var showLoadingPlaceholder = function(body) {\n getLoadingPlaceholder(body).removeClass('hidden');\n };\n\n /**\n * Hide loading placeholder.\n *\n * @param {Object} body Search body container element.\n */\n var hideLoadingPlaceholder = function(body) {\n getLoadingPlaceholder(body).addClass('hidden');\n };\n\n /**\n * Show search icon.\n *\n * @param {Object} header Search header container element.\n */\n var showSearchIcon = function(header) {\n getSearchIconContainer(header).removeClass('hidden');\n };\n\n /**\n * Hide search icon.\n *\n * @param {Object} header Search header container element.\n */\n var hideSearchIcon = function(header) {\n getSearchIconContainer(header).addClass('hidden');\n };\n\n /**\n * Show search results.\n *\n * @param {Object} body Search body container element.\n */\n var showSearchResults = function(body) {\n getSearchResultsContainer(body).removeClass('hidden');\n };\n\n /**\n * Hide search results.\n *\n * @param {Object} body Search body container element.\n */\n var hideSearchResults = function(body) {\n getSearchResultsContainer(body).addClass('hidden');\n };\n\n /**\n * Show the no search results message.\n *\n * @param {Object} body Search body container element.\n */\n var showNoSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden');\n container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden');\n container.find(SELECTORS.NO_RESULTS_CONTAINTER).removeClass('hidden');\n };\n\n /**\n * Hide the no search results message.\n *\n * @param {Object} body Search body container element.\n */\n var hideNoSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden');\n container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden');\n container.find(SELECTORS.NO_RESULTS_CONTAINTER).addClass('hidden');\n };\n\n /**\n * Show the whole contacts results area.\n *\n * @param {Object} body Search body container element.\n */\n var showAllContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.ALL_CONTACTS_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the whole contacts results area.\n *\n * @param {Object} body Search body container element.\n */\n var hideAllContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.ALL_CONTACTS_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the contacts results.\n *\n * @param {Object} body Search body container element.\n */\n var showContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.CONTACTS_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the contacts results.\n *\n * @param {Object} body Search body container element.\n */\n var hideContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.CONTACTS_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the non contacts results.\n *\n * @param {Object} body Search body container element.\n */\n var showNonContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.NON_CONTACTS_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the non contacts results.\n *\n * @param {Object} body Search body container element.\n */\n var hideNonContactsSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.NON_CONTACTS_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the messages results.\n *\n * @param {Object} body Search body container element.\n */\n var showMessagesSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.MESSAGES_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the messages results.\n *\n * @param {Object} body Search body container element.\n */\n var hideMessagesSearchResults = function(body) {\n var container = getSearchResultsContainer(body);\n container.find(SELECTORS.MESSAGES_CONTAINER).addClass('hidden');\n };\n\n /**\n * Disable the search input.\n *\n * @param {Object} header Search header container element.\n */\n var disableSearchInput = function(header) {\n getSearchInput(header).prop('disabled', true);\n };\n\n /**\n * Enable the search input.\n *\n * @param {Object} header Search header container element.\n */\n var enableSearchInput = function(header) {\n getSearchInput(header).prop('disabled', false);\n };\n\n /**\n * Clear the search input.\n *\n * @param {Object} header Search header container element.\n */\n var clearSearchInput = function(header) {\n getSearchInput(header).val('');\n };\n\n /**\n * Clear all search results\n *\n * @param {Object} body Search body container element.\n */\n var clearAllSearchResults = function(body) {\n body.find(SELECTORS.CONTACTS_LIST).empty();\n body.find(SELECTORS.NON_CONTACTS_LIST).empty();\n body.find(SELECTORS.MESSAGES_LIST).empty();\n hideNoSearchResults(body);\n showAllContactsSearchResults(body);\n showContactsSearchResults(body);\n showNonContactsSearchResults(body);\n showMessagesSearchResults(body);\n showLoadMoreUsersButton(body);\n showLoadMoreMessagesButton(body);\n };\n\n /**\n * Update the body and header to indicate the search is loading.\n *\n * @param {Object} header Search header container element.\n * @param {Object} body Search body container element.\n */\n var startLoading = function(header, body) {\n hideSearchIcon(header);\n hideEmptyMessage(body);\n hideSearchResults(body);\n showLoadingIcon(header);\n showLoadingPlaceholder(body);\n disableSearchInput(header);\n };\n\n /**\n * Update the body and header to indicate the search has stopped loading.\n *\n * @param {Object} header Search header container element.\n * @param {Object} body Search body container element.\n */\n var stopLoading = function(header, body) {\n showSearchIcon(header);\n hideEmptyMessage(body);\n showSearchResults(body);\n hideLoadingIcon(header);\n hideLoadingPlaceholder(body);\n enableSearchInput(header);\n };\n\n /**\n * Show the more users loading icon.\n *\n * @param {Object} root The more users container element.\n */\n var showUsersLoadingIcon = function(root) {\n var button = root.find(SELECTORS.LOAD_MORE_USERS);\n button.prop('disabled', true);\n button.find(SELECTORS.BUTTON_TEXT).addClass('hidden');\n button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the more users loading icon.\n *\n * @param {Object} root The more users container element.\n */\n var hideUsersLoadingIcon = function(root) {\n var button = root.find(SELECTORS.LOAD_MORE_USERS);\n button.prop('disabled', false);\n button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden');\n button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the load more users button.\n *\n * @param {Object} root The users container element.\n */\n var showLoadMoreUsersButton = function(root) {\n root.find(SELECTORS.LOAD_MORE_USERS).removeClass('hidden');\n };\n\n /**\n * Hide the load more users button.\n *\n * @param {Object} root The users container element.\n */\n var hideLoadMoreUsersButton = function(root) {\n root.find(SELECTORS.LOAD_MORE_USERS).addClass('hidden');\n };\n\n /**\n * Show the messages are loading icon.\n *\n * @param {Object} root Messages root element.\n */\n var showMessagesLoadingIcon = function(root) {\n var button = root.find(SELECTORS.LOAD_MORE_MESSAGES);\n button.prop('disabled', true);\n button.find(SELECTORS.BUTTON_TEXT).addClass('hidden');\n button.find(SELECTORS.LOADING_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the messages are loading icon.\n *\n * @param {Object} root Messages root element.\n */\n var hideMessagesLoadingIcon = function(root) {\n var button = root.find(SELECTORS.LOAD_MORE_MESSAGES);\n button.prop('disabled', false);\n button.find(SELECTORS.BUTTON_TEXT).removeClass('hidden');\n button.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the load more messages button.\n *\n * @param {Object} root The messages container element.\n */\n var showLoadMoreMessagesButton = function(root) {\n root.find(SELECTORS.LOAD_MORE_MESSAGES).removeClass('hidden');\n };\n\n /**\n * Hide the load more messages button.\n *\n * @param {Object} root The messages container element.\n */\n var hideLoadMoreMessagesButton = function(root) {\n root.find(SELECTORS.LOAD_MORE_MESSAGES).addClass('hidden');\n };\n\n /**\n * Find a contact in the search results.\n *\n * @param {Object} root Search results container element.\n * @param {Number} userId User id.\n * @return {Object} User container element.\n */\n var findContact = function(root, userId) {\n return root.find('[data-contact-user-id=\"' + userId + '\"]');\n };\n\n /**\n * Add a contact to the search results.\n *\n * @param {Object} root Search results container.\n * @param {Object} contact User in contacts list.\n */\n var addContact = function(root, contact) {\n var nonContactsContainer = getNonContactsContainer(root);\n var nonContact = findContact(nonContactsContainer, contact.userid);\n\n if (nonContact.length) {\n nonContact.remove();\n var contactsContainer = getContactsContainer(root);\n contactsContainer.removeClass('hidden');\n contactsContainer.find(SELECTORS.LIST).append(nonContact);\n }\n\n if (!nonContactsContainer.find(SELECTORS.LIST).children().length) {\n nonContactsContainer.addClass('hidden');\n }\n };\n\n /**\n * Remove a contact from the contacts results.\n *\n * @param {Object} root Search results container.\n * @param {Object} userId Contact user id.\n */\n var removeContact = function(root, userId) {\n var contactsContainer = getContactsContainer(root);\n var contact = findContact(contactsContainer, userId);\n\n if (contact.length) {\n contact.remove();\n var nonContactsContainer = getNonContactsContainer(root);\n nonContactsContainer.removeClass('hidden');\n nonContactsContainer.find(SELECTORS.LIST).append(contact);\n }\n\n if (!contactsContainer.find(SELECTORS.LIST).children().length) {\n contactsContainer.addClass('hidden');\n }\n };\n\n /**\n * Show the contact is blocked icon.\n *\n * @param {Object} root Search results container.\n * @param {Object} userId Contact user id.\n */\n var blockContact = function(root, userId) {\n var contact = findContact(root, userId);\n if (contact.length) {\n contact.find(SELECTORS.BLOCK_ICON_CONTAINER).removeClass('hidden');\n }\n };\n\n /**\n * Hide the contact is blocked icon.\n *\n * @param {Object} root Search results container.\n * @param {Object} userId Contact user id.\n */\n var unblockContact = function(root, userId) {\n var contact = findContact(root, userId);\n if (contact.length) {\n contact.find(SELECTORS.BLOCK_ICON_CONTAINER).addClass('hidden');\n }\n };\n\n /**\n * Highlight words in search results.\n *\n * @param {String} content HTML to search.\n * @param {String} searchText Search text.\n * @return {String} searchText with search wrapped in matchtext span.\n */\n var highlightSearch = function(content, searchText) {\n if (!content) {\n return '';\n }\n var regex = new RegExp('(' + searchText + ')', 'gi');\n return content.replace(regex, '$1');\n };\n\n\n /**\n * Render contacts in the contacts search results.\n *\n * @param {Object} root Search results container.\n * @param {Array} contacts List of contacts.\n * @return {Promise} Renderer promise.\n */\n var renderContacts = function(root, contacts) {\n var container = getContactsContainer(root);\n var frompanel = root.attr('data-in-panel');\n var list = container.find(SELECTORS.LIST);\n\n return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: contacts, frompanel: frompanel})\n .then(function(html) {\n list.append(html);\n return html;\n });\n };\n\n /**\n * Render non contacts in the contacts search results.\n *\n * @param {Object} root Search results container.\n * @param {Array} nonContacts List of non contacts.\n * @return {Promise} Renderer promise.\n */\n var renderNonContacts = function(root, nonContacts) {\n var container = getNonContactsContainer(root);\n var frompanel = root.attr('data-in-panel');\n var list = container.find(SELECTORS.LIST);\n\n return Templates.render(TEMPLATES.NON_CONTACTS_LIST, {noncontacts: nonContacts, frompanel: frompanel})\n .then(function(html) {\n list.append(html);\n return html;\n });\n };\n\n /**\n * Render messages in the messages search results.\n *\n * @param {Object} root Search results container.\n * @param {Array} messages List of messages.\n * @return {Promise} Renderer promise.\n */\n var renderMessages = function(root, messages) {\n var container = getMessagesContainer(root);\n var frompanel = root.attr('data-in-panel');\n var list = container.find(SELECTORS.LIST);\n\n return Templates.render(TEMPLATES.MESSAGES_LIST, {messages: messages, frompanel: frompanel})\n .then(function(html) {\n list.append(html);\n return html;\n });\n };\n\n /**\n * Load more users from the repository and render the results into the users search results.\n *\n * @param {Object} root Search results container.\n * @param {Number} loggedInUserId Current logged in user.\n * @param {String} text Search text.\n * @param {Number} limit Number of users to get.\n * @param {Number} offset Load users from\n * @return {Object} jQuery promise\n */\n var loadMoreUsers = function(root, loggedInUserId, text, limit, offset) {\n var loadedAll = false;\n showUsersLoadingIcon(root);\n\n return Repository.searchUsers(loggedInUserId, text, limit + 1, offset)\n .then(function(results) {\n var contacts = results.contacts;\n var noncontacts = results.noncontacts;\n\n if (contacts.length <= limit && noncontacts.length <= limit) {\n loadedAll = true;\n return {\n contacts: contacts,\n noncontacts: noncontacts\n };\n } else {\n return {\n contacts: contacts.slice(0, limit),\n noncontacts: noncontacts.slice(0, limit)\n };\n }\n })\n .then(function(results) {\n var contactsCount = results.contacts.length;\n var nonContactsCount = results.noncontacts.length;\n\n if (contactsCount) {\n results.contacts.forEach(function(contact) {\n contact.highlight = highlightSearch(contact.fullname, text);\n });\n }\n\n if (nonContactsCount) {\n results.noncontacts.forEach(function(contact) {\n contact.highlight = highlightSearch(contact.fullname, text);\n });\n }\n\n return $.when(\n contactsCount ? renderContacts(root, results.contacts) : true,\n nonContactsCount ? renderNonContacts(root, results.noncontacts) : true\n )\n .then(function() {\n return {\n contactsCount: contactsCount,\n nonContactsCount: nonContactsCount\n };\n });\n })\n .then(function(counts) {\n hideUsersLoadingIcon(root);\n\n if (loadedAll) {\n hideLoadMoreUsersButton(root);\n }\n\n return counts;\n })\n .catch(function(error) {\n hideUsersLoadingIcon(root);\n // Rethrow error for other handlers.\n throw error;\n });\n };\n\n /**\n * Load more messages from the repository and render the results into the messages search results.\n *\n * @param {Object} root Search results container.\n * @param {Number} loggedInUserId Current logged in user.\n * @param {String} text Search text.\n * @param {Number} limit Number of messages to get.\n * @param {Number} offset Load messages from\n * @return {Object} jQuery promise\n */\n var loadMoreMessages = function(root, loggedInUserId, text, limit, offset) {\n var loadedAll = false;\n showMessagesLoadingIcon(root);\n\n return Repository.searchMessages(loggedInUserId, text, limit + 1, offset)\n .then(function(results) {\n var messages = results.contacts;\n\n if (messages.length <= limit) {\n loadedAll = true;\n return messages;\n } else {\n return messages.slice(0, limit);\n }\n })\n .then(function(messages) {\n if (messages.length) {\n messages.forEach(function(message) {\n message.lastmessage = highlightSearch(message.lastmessage, text);\n });\n return renderMessages(root, messages)\n .then(function() {\n return messages.length;\n });\n } else {\n return messages.length;\n }\n })\n .then(function(count) {\n hideMessagesLoadingIcon(root);\n\n if (loadedAll) {\n hideLoadMoreMessagesButton(root);\n }\n\n return count;\n })\n .catch(function(error) {\n hideMessagesLoadingIcon(root);\n // Rethrow error for other handlers.\n throw error;\n });\n };\n\n /**\n * Search for users and messages.\n *\n * @param {Object} header Search header container element.\n * @param {Object} body Search body container element.\n * @param {String} searchText Search text.\n * @param {Number} usersLimit The users limit.\n * @param {Number} usersOffset The users offset.\n * @param {Number} messagesLimit The message limit.\n * @param {Number} messagesOffset The message offset.\n * @return {Object} jQuery promise\n */\n var search = function(header, body, searchText, usersLimit, usersOffset, messagesLimit, messagesOffset) {\n var loggedInUserId = getLoggedInUserId(body);\n startLoading(header, body);\n clearAllSearchResults(body);\n\n return $.when(\n loadMoreUsers(body, loggedInUserId, searchText, usersLimit, usersOffset),\n loadMoreMessages(body, loggedInUserId, searchText, messagesLimit, messagesOffset)\n )\n .then(function(userCounts, messagesCount) {\n var contactsCount = userCounts.contactsCount;\n var nonContactsCount = userCounts.nonContactsCount;\n\n stopLoading(header, body);\n\n if (!contactsCount && !nonContactsCount && !messagesCount) {\n showNoSearchResults(body);\n } else {\n if (!contactsCount && !nonContactsCount) {\n hideAllContactsSearchResults(body);\n } else {\n if (!contactsCount) {\n hideContactsSearchResults(body);\n }\n\n if (!nonContactsCount) {\n hideNonContactsSearchResults(body);\n }\n }\n\n if (!messagesCount) {\n hideMessagesSearchResults(body);\n }\n }\n\n return;\n });\n };\n\n\n /**\n * Listen to and handle events for searching.\n *\n * @param {Object} header Search header container element.\n * @param {Object} body Search body container element.\n */\n var registerEventListeners = function(header, body) {\n var loggedInUserId = getLoggedInUserId(body);\n var searchInput = getSearchInput(header);\n var searchText = '';\n var messagesOffset = 0;\n var usersOffset = 0;\n\n var searchEventHandler = function(e, data) {\n searchText = searchInput.val().trim();\n\n if (searchText !== '') {\n messagesOffset = 0;\n usersOffset = 0;\n search(\n header,\n body,\n searchText,\n USERS_INITIAL_SEARCH_LIMIT,\n usersOffset,\n MESSAGE_SEARCH_LIMIT,\n messagesOffset\n )\n .then(function() {\n searchInput.focus();\n usersOffset = usersOffset + USERS_INITIAL_SEARCH_LIMIT;\n messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;\n return;\n })\n .catch(Notification.exception);\n }\n\n data.originalEvent.preventDefault();\n };\n\n CustomEvents.define(searchInput, [CustomEvents.events.enter]);\n CustomEvents.define(header, [CustomEvents.events.activate]);\n CustomEvents.define(body, [CustomEvents.events.activate]);\n\n searchInput.on(CustomEvents.events.enter, searchEventHandler);\n\n header.on(CustomEvents.events.activate, SELECTORS.SEARCH_ACTION, searchEventHandler);\n\n body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_MESSAGES, function(e, data) {\n if (searchText !== '') {\n loadMoreMessages(body, loggedInUserId, searchText, MESSAGE_SEARCH_LIMIT, messagesOffset)\n .then(function() {\n messagesOffset = messagesOffset + MESSAGE_SEARCH_LIMIT;\n return;\n })\n .catch(Notification.exception);\n }\n data.originalEvent.preventDefault();\n });\n\n body.on(CustomEvents.events.activate, SELECTORS.LOAD_MORE_USERS, function(e, data) {\n if (searchText !== '') {\n loadMoreUsers(body, loggedInUserId, searchText, USERS_SEARCH_LIMIT, usersOffset)\n .then(function() {\n usersOffset = usersOffset + USERS_SEARCH_LIMIT;\n return;\n })\n .catch(Notification.exception);\n }\n data.originalEvent.preventDefault();\n });\n\n header.on(CustomEvents.events.activate, SELECTORS.CANCEL_SEARCH_BUTTON, function() {\n clearSearchInput(header);\n showEmptyMessage(body);\n showSearchIcon(header);\n hideSearchResults(body);\n hideLoadingIcon(header);\n hideLoadingPlaceholder(body);\n usersOffset = 0;\n messagesOffset = 0;\n });\n\n PubSub.subscribe(Events.CONTACT_ADDED, function(userId) {\n addContact(body, userId);\n });\n\n PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) {\n removeContact(body, userId);\n });\n\n PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) {\n blockContact(body, userId);\n });\n\n PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) {\n unblockContact(body, userId);\n });\n };\n\n /**\n * Setup the search page.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Contacts header container element.\n * @param {Object} body Contacts body container element.\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body) {\n if (!body.attr('data-init')) {\n registerEventListeners(header, body);\n body.attr('data-init', true);\n }\n var searchInput = getSearchInput(header);\n searchInput.focus();\n\n return $.Deferred().resolve().promise();\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Contacts header container element.\n * @return {Object} jQuery promise\n */\n var description = function(namespace, header) {\n if (typeof header !== 'object') {\n return Str.get_string('messagedrawerviewsearch', 'core_message');\n }\n var searchInput = getSearchInput(header);\n var searchText = searchInput.val().trim();\n return Str.get_string('messagedrawerviewsearch', 'core_message', searchText);\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","CustomEvents","Notification","PubSub","Str","Templates","Repository","Events","SELECTORS","TEMPLATES","getLoggedInUserId","body","attr","getEmptyMessageContainer","find","getLoadingIconContainer","header","getLoadingPlaceholder","getSearchIconContainer","getSearchInput","getSearchResultsContainer","getContactsContainer","getNonContactsContainer","hideEmptyMessage","addClass","hideLoadingIcon","hideLoadingPlaceholder","showSearchIcon","removeClass","hideSearchResults","clearAllSearchResults","empty","container","hideNoSearchResults","showAllContactsSearchResults","showContactsSearchResults","showNonContactsSearchResults","showMessagesSearchResults","showLoadMoreUsersButton","showLoadMoreMessagesButton","startLoading","hideSearchIcon","showLoadingIcon","showLoadingPlaceholder","prop","disableSearchInput","stopLoading","showSearchResults","enableSearchInput","hideUsersLoadingIcon","root","button","hideMessagesLoadingIcon","findContact","userId","highlightSearch","content","searchText","regex","RegExp","replace","renderMessages","messages","frompanel","list","render","then","html","append","loadMoreUsers","loggedInUserId","text","limit","offset","loadedAll","showUsersLoadingIcon","searchUsers","results","contacts","noncontacts","length","slice","contactsCount","nonContactsCount","forEach","contact","highlight","fullname","when","renderContacts","nonContacts","renderNonContacts","counts","hideLoadMoreUsersButton","catch","error","loadMoreMessages","showMessagesLoadingIcon","searchMessages","message","lastmessage","count","hideLoadMoreMessagesButton","search","usersLimit","usersOffset","messagesLimit","messagesOffset","userCounts","messagesCount","hideContactsSearchResults","hideNonContactsSearchResults","hideAllContactsSearchResults","hideMessagesSearchResults","showNoSearchResults","registerEventListeners","searchInput","searchEventHandler","e","data","val","trim","focus","exception","originalEvent","preventDefault","events","enter","activate","on","clearSearchInput","showEmptyMessage","subscribe","CONTACT_ADDED","nonContactsContainer","nonContact","userid","remove","contactsContainer","children","addContact","CONTACT_REMOVED","removeContact","CONTACT_BLOCKED","blockContact","CONTACT_UNBLOCKED","unblockContact","show","namespace","Deferred","resolve","promise","description","get_string"],"mappings":"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"}PKB\s{e"">build/message_drawer_view_contacts_section_contacts.min.js.mapnu[{"version":3,"file":"message_drawer_view_contacts_section_contacts.min.js","sources":["../src/message_drawer_view_contacts_section_contacts.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the contacts section of the contacts page.\n *\n * @module core_message/message_drawer_view_contacts_section_contacts\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/pubsub',\n 'core/templates',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_lazy_load_list'\n],\nfunction(\n $,\n Notification,\n PubSub,\n Templates,\n MessageRepository,\n Events,\n LazyLoadList\n) {\n\n var limit = 100;\n var initialOffset = 0;\n\n var SELECTORS = {\n BLOCK_ICON_CONTAINER: '[data-region=\"block-icon-container\"]',\n CONTACT: '[data-region=\"contact\"]',\n CONTENT_CONTAINER: '[data-region=\"contacts-content-container\"]'\n };\n\n var TEMPLATES = {\n CONTACTS_LIST: 'core_message/message_drawer_contacts_list'\n };\n\n /**\n * Find a contact element.\n *\n * @param {Object} body Contacts body container element.\n * @param {Number} userId User id of contact.\n * @return {Object} contact element.\n */\n var findContact = function(body, userId) {\n return body.find('[data-contact-user-id=\"' + userId + '\"]');\n };\n\n /**\n * Render the contacts in the content container.\n *\n * @param {Object} contentContainer Content container element.\n * @param {Array} contacts List of contacts.\n * @return {Object} jQuery promise\n */\n var render = function(contentContainer, contacts) {\n var formattedContacts = contacts.map(function(contact) {\n return $.extend(contact, {id: contact.userid});\n });\n return Templates.render(TEMPLATES.CONTACTS_LIST, {contacts: formattedContacts})\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Load the user contacts and call the renderer.\n *\n * @param {Number} offset The offset to use for loading contacts\n * @return {Function} the callback.\n */\n var getLoadFunction = function(offset) {\n return function(listRoot, userId) {\n return MessageRepository.getContacts(userId, (limit + 1), offset)\n .then(function(result) {\n return result;\n })\n .then(function(contacts) {\n if (contacts.length > limit) {\n contacts.pop();\n } else {\n LazyLoadList.setLoadedAll(listRoot, true);\n }\n return contacts;\n })\n .then(function(contacts) {\n offset = offset + limit;\n return contacts;\n })\n .catch(Notification.exception);\n };\n };\n\n /**\n * Remove contact from view.\n *\n * @param {Object} body Contacts body container element.\n * @param {Number} userId Contact userid.\n */\n var removeContact = function(body, userId) {\n findContact(body, userId).remove();\n };\n\n /**\n * Show the contact has been blocked.\n *\n * @param {Object} body Contacts body container element.\n * @param {Number} userId Contact userid.\n */\n var showContactBlocked = function(body, userId) {\n var contact = findContact(body, userId);\n if (contact.length) {\n contact.find(SELECTORS.BLOCK_ICON_CONTAINER).removeClass('hidden');\n }\n };\n\n /**\n * Show the contact has been unblocked.\n *\n * @param {Object} body Contacts body container element.\n * @param {Number} userId Contact userid.\n */\n var showContactUnblocked = function(body, userId) {\n var contact = findContact(body, userId);\n if (contact.length) {\n contact.find(SELECTORS.BLOCK_ICON_CONTAINER).addClass('hidden');\n }\n };\n\n /**\n * Listen to and handle events for contacts.\n *\n * @param {Object} root Contacts section container element.\n */\n var registerEventListeners = function(root) {\n PubSub.subscribe(Events.CONTACT_ADDED, function(profile) {\n var listContentContainer = LazyLoadList.getContentContainer(root);\n render(listContentContainer, [profile]);\n LazyLoadList.hideEmptyMessage(root);\n LazyLoadList.showContent(root);\n });\n\n PubSub.subscribe(Events.CONTACT_REMOVED, function(userId) {\n removeContact(root, userId);\n var contacts = root.find(SELECTORS.CONTACT);\n\n if (!contacts.length) {\n LazyLoadList.hideContent(root);\n LazyLoadList.showEmptyMessage(root);\n }\n });\n\n PubSub.subscribe(Events.CONTACT_BLOCKED, function(userId) {\n showContactBlocked(root, userId);\n });\n\n PubSub.subscribe(Events.CONTACT_UNBLOCKED, function(userId) {\n showContactUnblocked(root, userId);\n });\n };\n\n /**\n * Setup the contacts section.\n *\n * @param {Object} root Contacts section container.\n */\n var show = function(root) {\n if (!root.attr('data-contacts-init')) {\n registerEventListeners(root);\n root.attr('data-contacts-init', true);\n }\n\n // The root element is already the lazy loaded list root.\n LazyLoadList.show(root, getLoadFunction(initialOffset), render);\n };\n\n return {\n show: show,\n };\n});\n"],"names":["define","$","Notification","PubSub","Templates","MessageRepository","Events","LazyLoadList","SELECTORS","TEMPLATES","findContact","body","userId","find","render","contentContainer","contacts","formattedContacts","map","contact","extend","id","userid","then","html","append","catch","exception","registerEventListeners","root","subscribe","CONTACT_ADDED","profile","listContentContainer","getContentContainer","hideEmptyMessage","showContent","CONTACT_REMOVED","remove","removeContact","length","hideContent","showEmptyMessage","CONTACT_BLOCKED","removeClass","showContactBlocked","CONTACT_UNBLOCKED","addClass","showContactUnblocked","show","offset","attr","listRoot","getContacts","limit","result","pop","setLoadedAll"],"mappings":";;;;;;;AAsBAA,oEACA,CACI,SACA,oBACA,cACA,iBACA,kCACA,qCACA,+CAEJ,SACIC,EACAC,aACAC,OACAC,UACAC,kBACAC,OACAC,kBAMIC,+BACsB,uCADtBA,kBAES,0BAITC,wBACe,4CAUfC,YAAc,SAASC,KAAMC,eACtBD,KAAKE,KAAK,0BAA4BD,OAAS,OAUtDE,OAAS,SAASC,iBAAkBC,cAChCC,kBAAoBD,SAASE,KAAI,SAASC,gBACnClB,EAAEmB,OAAOD,QAAS,CAACE,GAAIF,QAAQG,mBAEnClB,UAAUU,OAAOL,wBAAyB,CAACO,SAAUC,oBACvDM,MAAK,SAASC,aACXT,iBAAiBU,OAAOD,MACjBA,QAEVE,MAAMxB,aAAayB,YAwExBC,uBAAyB,SAASC,MAClC1B,OAAO2B,UAAUxB,OAAOyB,eAAe,SAASC,aACxCC,qBAAuB1B,aAAa2B,oBAAoBL,MAC5Df,OAAOmB,qBAAsB,CAACD,UAC9BzB,aAAa4B,iBAAiBN,MAC9BtB,aAAa6B,YAAYP,SAG7B1B,OAAO2B,UAAUxB,OAAO+B,iBAAiB,SAASzB,SA3ClC,SAASD,KAAMC,QAC/BF,YAAYC,KAAMC,QAAQ0B,SA2CtBC,CAAcV,KAAMjB,QACLiB,KAAKhB,KAAKL,mBAEXgC,SACVjC,aAAakC,YAAYZ,MACzBtB,aAAamC,iBAAiBb,UAItC1B,OAAO2B,UAAUxB,OAAOqC,iBAAiB,SAAS/B,SA3C7B,SAASD,KAAMC,YAChCO,QAAUT,YAAYC,KAAMC,QAC5BO,QAAQqB,QACRrB,QAAQN,KAAKL,gCAAgCoC,YAAY,UAyCzDC,CAAmBhB,KAAMjB,WAG7BT,OAAO2B,UAAUxB,OAAOwC,mBAAmB,SAASlC,SAlC7B,SAASD,KAAMC,YAClCO,QAAUT,YAAYC,KAAMC,QAC5BO,QAAQqB,QACRrB,QAAQN,KAAKL,gCAAgCuC,SAAS,UAgCtDC,CAAqBnB,KAAMjB,kBAmB5B,CACHqC,KAXO,SAASpB,MA/FE,IAASqB,OAgGtBrB,KAAKsB,KAAK,wBACXvB,uBAAuBC,MACvBA,KAAKsB,KAAK,sBAAsB,IAIpC5C,aAAa0C,KAAKpB,MAtGSqB,OAhDX,EAiDT,SAASE,SAAUxC,eACfP,kBAAkBgD,YAAYzC,OAAS0C,IAAYJ,QACrD3B,MAAK,SAASgC,eACJA,UAEVhC,MAAK,SAASP,iBACPA,SAASwB,OAxDjB,IAyDQxB,SAASwC,MAETjD,aAAakD,aAAaL,UAAU,GAEjCpC,YAEVO,MAAK,SAASP,iBACXkC,QAhEJ,IAiEWlC,YAEVU,MAAMxB,aAAayB,aAoF4Bb"}PKB\vt^^:build/message_drawer_view_conversation_renderer.min.js.mapnu[{"version":3,"file":"message_drawer_view_conversation_renderer.min.js","sources":["../src/message_drawer_view_conversation_renderer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module updates the UI for the conversation page in the message\n * drawer.\n *\n * The module will take a patch from the message_drawer_view_conversation_patcher\n * module and update the UI to reflect the changes.\n *\n * This is the only module that ever modifies the UI of the conversation page.\n *\n * @module core_message/message_drawer_view_conversation_renderer\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/notification',\n 'core/str',\n 'core/templates',\n 'core/user_date',\n 'core_message/message_drawer_view_conversation_constants',\n 'core/aria',\n 'core/truncate',\n],\nfunction(\n $,\n Notification,\n Str,\n Templates,\n UserDate,\n Constants,\n Aria,\n Truncate,\n) {\n var SELECTORS = Constants.SELECTORS;\n var TEMPLATES = Constants.TEMPLATES;\n var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;\n\n /**\n * Get the messages container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getMessagesContainer = function(body) {\n return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER);\n };\n\n /**\n * Show the messages container element.\n *\n * @param {Object} body Conversation body container element.\n */\n var showMessagesContainer = function(body) {\n getMessagesContainer(body).removeClass('hidden');\n };\n\n /**\n * Hide the messages container element.\n *\n * @param {Object} body Conversation body container element.\n */\n var hideMessagesContainer = function(body) {\n getMessagesContainer(body).addClass('hidden');\n };\n\n /**\n * Get the self-conversation message container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getSelfConversationMessageContainer = function(body) {\n return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER);\n };\n\n /**\n * Hide the self-conversation message container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var hideSelfConversationMessageContainer = function(body) {\n return getSelfConversationMessageContainer(body).addClass('hidden');\n };\n\n /**\n * Get the contact request sent container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getContactRequestSentContainer = function(body) {\n return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER);\n };\n\n /**\n * Hide the contact request sent container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var hideContactRequestSentContainer = function(body) {\n return getContactRequestSentContainer(body).addClass('hidden');\n };\n\n /**\n * Get the footer container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer container element.\n */\n var getFooterContentContainer = function(footer) {\n return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER);\n };\n\n /**\n * Show the footer container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterContent = function(footer) {\n getFooterContentContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterContent = function(footer) {\n getFooterContentContainer(footer).addClass('hidden');\n };\n\n /**\n * Get the footer edit mode container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer container element.\n */\n var getFooterEditModeContainer = function(footer) {\n return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER);\n };\n\n /**\n * Show the footer edit mode container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterEditMode = function(footer) {\n getFooterEditModeContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer edit mode container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterEditMode = function(footer) {\n getFooterEditModeContainer(footer).addClass('hidden');\n };\n\n /**\n * Get the footer placeholder.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer placeholder container element.\n */\n var getFooterPlaceholderContainer = function(footer) {\n return footer.find(SELECTORS.PLACEHOLDER_CONTAINER);\n };\n\n /**\n * Show the footer placeholder\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterPlaceholder = function(footer) {\n getFooterPlaceholderContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer placeholder\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterPlaceholder = function(footer) {\n getFooterPlaceholderContainer(footer).addClass('hidden');\n };\n\n /**\n * Get the footer Require add as contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer Require add as contact container element.\n */\n var getFooterRequireContactContainer = function(footer) {\n return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER);\n };\n\n /**\n * Show the footer add as contact dialogue.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterRequireContact = function(footer) {\n getFooterRequireContactContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer add as contact dialogue.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterRequireContact = function(footer) {\n getFooterRequireContactContainer(footer).addClass('hidden');\n };\n\n /**\n * Get the footer Required to unblock contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer Required to unblock contact container element.\n */\n var getFooterRequireUnblockContainer = function(footer) {\n return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER);\n };\n\n /**\n * Show the footer Required to unblock contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterRequireUnblock = function(footer) {\n getFooterRequireUnblockContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer Required to unblock contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterRequireUnblock = function(footer) {\n getFooterRequireUnblockContainer(footer).addClass('hidden');\n };\n\n /**\n * Get the footer Unable to message contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The footer Unable to message contact container element.\n */\n var getFooterUnableToMessageContainer = function(footer) {\n return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER);\n };\n\n /**\n * Show the footer Unable to message contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var showFooterUnableToMessage = function(footer) {\n getFooterUnableToMessageContainer(footer).removeClass('hidden');\n };\n\n /**\n * Hide the footer Unable to message contact container element.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideFooterUnableToMessage = function(footer) {\n getFooterUnableToMessageContainer(footer).addClass('hidden');\n };\n\n /**\n * Hide all header elements.\n *\n * @param {Object} header Conversation header container element.\n */\n var hideAllHeaderElements = function(header) {\n hideHeaderContent(header);\n hideHeaderEditMode(header);\n hideHeaderPlaceholder(header);\n };\n\n /**\n * Hide all footer dialogues and messages.\n *\n * @param {Object} footer Conversation footer container element.\n */\n var hideAllFooterElements = function(footer) {\n hideFooterContent(footer);\n hideFooterEditMode(footer);\n hideFooterPlaceholder(footer);\n hideFooterRequireContact(footer);\n hideFooterRequireUnblock(footer);\n hideFooterUnableToMessage(footer);\n };\n\n /**\n * Get the content placeholder container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The body placeholder container element.\n */\n var getContentPlaceholderContainer = function(body) {\n return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER);\n };\n\n /**\n * Show the content placeholder.\n *\n * @param {Object} body Conversation body container element.\n */\n var showContentPlaceholder = function(body) {\n getContentPlaceholderContainer(body).removeClass('hidden');\n };\n\n /**\n * Hide the content placeholder.\n *\n * @param {Object} body Conversation body container element.\n */\n var hideContentPlaceholder = function(body) {\n getContentPlaceholderContainer(body).addClass('hidden');\n };\n\n /**\n * Get the header content container element.\n *\n * @param {Object} header Conversation header container element.\n * @return {Object} The header content container element.\n */\n var getHeaderContent = function(header) {\n return header.find(SELECTORS.HEADER);\n };\n\n /**\n * Show the header content.\n *\n * @param {Object} header Conversation header container element.\n */\n var showHeaderContent = function(header) {\n getHeaderContent(header).removeClass('hidden');\n };\n\n /**\n * Hide the header content.\n *\n * @param {Object} header Conversation header container element.\n */\n var hideHeaderContent = function(header) {\n getHeaderContent(header).addClass('hidden');\n };\n\n /**\n * Get the header edit mode container element.\n *\n * @param {Object} header Conversation header container element.\n * @return {Object} The header content container element.\n */\n var getHeaderEditMode = function(header) {\n return header.find(SELECTORS.HEADER_EDIT_MODE);\n };\n\n /**\n * Show the header edit mode container.\n *\n * @param {Object} header Conversation header container element.\n */\n var showHeaderEditMode = function(header) {\n getHeaderEditMode(header).removeClass('hidden');\n };\n\n /**\n * Hide the header edit mode container.\n *\n * @param {Object} header Conversation header container element.\n */\n var hideHeaderEditMode = function(header) {\n getHeaderEditMode(header).addClass('hidden');\n };\n\n /**\n * Get the header placeholder container element.\n *\n * @param {Object} header Conversation header container element.\n * @return {Object} The header placeholder container element.\n */\n var getHeaderPlaceholderContainer = function(header) {\n return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER);\n };\n\n /**\n * Show the header placeholder.\n *\n * @param {Object} header Conversation header container element.\n */\n var showHeaderPlaceholder = function(header) {\n getHeaderPlaceholderContainer(header).removeClass('hidden');\n };\n\n /**\n * Hide the header placeholder.\n *\n * @param {Object} header Conversation header container element.\n */\n var hideHeaderPlaceholder = function(header) {\n getHeaderPlaceholderContainer(header).addClass('hidden');\n };\n\n /**\n * Get the emoji picker container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The emoji picker container element.\n */\n var getEmojiPickerContainer = function(footer) {\n return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER);\n };\n\n /**\n * Get the emoji picker container element.\n *\n * @param {Object} footer Conversation footer container element.\n * @return {Object} The emoji picker container element.\n */\n var getEmojiAutoCompleteContainer = function(footer) {\n return footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);\n };\n\n /**\n * Get a message element.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number} messageId the Message id.\n * @return {Object} A message element from the conversation.\n */\n var getMessageElement = function(body, messageId) {\n var messagesContainer = getMessagesContainer(body);\n return messagesContainer.find('[data-message-id=\"' + messageId + '\"]');\n };\n\n /**\n * Get the day container element. The day container element holds a list of messages for that day.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number} dayTimeCreated Midnight timestamp for the day.\n * @return {Object} jQuery object\n */\n var getDayElement = function(body, dayTimeCreated) {\n var messagesContainer = getMessagesContainer(body);\n return messagesContainer.find('[data-day-id=\"' + dayTimeCreated + '\"]');\n };\n\n /**\n * Get the more messages loading icon container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The more messages loading container element.\n */\n var getMoreMessagesLoadingIconContainer = function(body) {\n return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER);\n };\n\n /**\n * Show the more messages loading icon.\n *\n * @param {Object} body Conversation body container element.\n */\n var showMoreMessagesLoadingIcon = function(body) {\n getMoreMessagesLoadingIconContainer(body).removeClass('hidden');\n };\n\n /**\n * Hide the more messages loading icon.\n *\n * @param {Object} body Conversation body container element.\n */\n var hideMoreMessagesLoadingIcon = function(body) {\n getMoreMessagesLoadingIconContainer(body).addClass('hidden');\n };\n\n /**\n * Get the confirm dialogue container element.\n *\n * @param {Object} root The container element to search.\n * @return {Object} The confirm dialogue container element.\n */\n var getConfirmDialogueContainer = function(root) {\n return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER);\n };\n\n /**\n * Show the confirm dialogue container element.\n *\n * @param {Object} root The container element containing a dialogue.\n */\n var showConfirmDialogueContainer = function(root) {\n var container = getConfirmDialogueContainer(root);\n var siblings = container.siblings(':not(.hidden)');\n Aria.hide(siblings.get());\n siblings.attr('data-confirm-dialogue-hidden', true);\n\n container.removeClass('hidden');\n };\n\n /**\n * Hide the confirm dialogue container element.\n *\n * @param {Object} root The container element containing a dialogue.\n */\n var hideConfirmDialogueContainer = function(root) {\n var container = getConfirmDialogueContainer(root);\n var siblings = container.siblings('[data-confirm-dialogue-hidden=\"true\"]');\n Aria.unhide(siblings.get());\n siblings.removeAttr('data-confirm-dialogue-hidden');\n\n container.addClass('hidden');\n };\n\n /**\n * Set the number of selected messages.\n *\n * @param {Object} header The header container element.\n * @param {Number} value The new number to display.\n */\n var setMessagesSelectedCount = function(header, value) {\n getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value);\n };\n\n /**\n * Format message for the mustache template, transform camelCase properties to lowercase properties.\n *\n * @param {Array} messages Array of message objects.\n * @param {Object} datesCache Cache timestamps and their formatted date string.\n * @return {Array} Messages formated for mustache template.\n */\n var formatMessagesForTemplate = function(messages, datesCache) {\n return messages.map(function(message) {\n return {\n id: message.id,\n isread: message.isRead,\n fromloggedinuser: message.fromLoggedInUser,\n userfrom: message.userFrom,\n text: message.text,\n formattedtime: message.timeCreated ? datesCache[message.timeCreated] : null\n };\n });\n };\n\n /**\n * Create rendering promises for each day containing messages.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Array} days Array of days containing messages.\n * @param {Object} datesCache Cache timestamps and their formatted date string.\n * @return {Promise} Days rendering promises.\n */\n var renderAddDays = function(header, body, footer, days, datesCache) {\n var messagesContainer = getMessagesContainer(body);\n var daysRenderPromises = days.map(function(data) {\n var timestampDate = new Date(data.value.timestamp * 1000);\n return Templates.render(TEMPLATES.DAY, {\n timestamp: data.value.timestamp,\n currentyear: timestampDate.getFullYear() === (new Date()).getFullYear(),\n messages: formatMessagesForTemplate(data.value.messages, datesCache)\n });\n });\n\n return $.when.apply($, daysRenderPromises).then(function() {\n // Wait until all of the rendering is done for each of the days\n // to ensure they are added to the page in the correct order.\n days.forEach(function(data, index) {\n daysRenderPromises[index]\n .then(function(html) {\n if (data.before) {\n var element = getDayElement(body, data.before.timestamp);\n return $(html).insertBefore(element);\n } else {\n return messagesContainer.append(html);\n }\n })\n .catch(function() {\n // Fail silently.\n });\n });\n\n return;\n });\n };\n\n /**\n * Add (more) messages to day containers.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Array} messages List of messages.\n * @param {Object} datesCache Cache timestamps and their formatted date string.\n * @return {Promise} Messages rendering promises.\n */\n var renderAddMessages = function(header, body, footer, messages, datesCache) {\n var messagesData = messages.map(function(data) {\n return data.value;\n });\n var formattedMessages = formatMessagesForTemplate(messagesData, datesCache);\n\n return Templates.render(TEMPLATES.MESSAGES, {messages: formattedMessages})\n .then(function(html) {\n var messageList = $(html);\n messages.forEach(function(data) {\n var messageHtml = messageList.find('[data-message-id=\"' + data.value.id + '\"]');\n if (data.before) {\n var element = getMessageElement(body, data.before.id);\n return messageHtml.insertBefore(element);\n } else {\n var dayContainer = getDayElement(body, data.day.timestamp);\n var dayMessagesContainer = dayContainer.find(SELECTORS.DAY_MESSAGES_CONTAINER);\n return dayMessagesContainer.append(messageHtml);\n }\n });\n\n return;\n });\n };\n\n /**\n * Update existing messages.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Array} messages List of messages.\n * @param {Object} datesCache Cache timestamps and their formatted date string.\n */\n var renderUpdateMessages = function(header, body, footer, messages, datesCache) {\n messages.forEach(function(message) {\n var before = message.before;\n var after = message.after;\n var element = getMessageElement(body, before.id);\n\n if (before.id != after.id) {\n element.attr('data-message-id', after.id);\n }\n\n if (before.timeCreated != after.timeCreated) {\n var formattedTime = datesCache[after.timeCreated];\n element.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');\n element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass('hidden');\n }\n\n if (before.sendState != after.sendState) {\n var loading = element.find(SELECTORS.LOADING_ICON_CONTAINER);\n var time = element.find(SELECTORS.TIME_CREATED);\n var retry = element.find(SELECTORS.RETRY_SEND);\n\n loading.addClass('hidden');\n Aria.hide(loading.get());\n\n time.addClass('hidden');\n Aria.hide(time.get());\n\n retry.addClass('hidden');\n Aria.hide(retry.get());\n\n element.removeClass('border border-danger');\n\n switch (after.sendState) {\n case 'pending':\n loading.removeClass('hidden');\n Aria.unhide(loading.get());\n break;\n case 'error':\n retry.removeClass('hidden');\n Aria.unhide(retry.get());\n element.addClass('border border-danger');\n break;\n case 'sent':\n time.removeClass('hidden');\n Aria.unhide(time.get());\n break;\n }\n }\n\n if (before.text != after.text) {\n element.find(SELECTORS.TEXT_CONTAINER).html(after.text);\n }\n\n if (before.errorMessage != after.errorMessage) {\n var messageContainer = element.find(SELECTORS.ERROR_MESSAGE_CONTAINER);\n var message = messageContainer.find(SELECTORS.ERROR_MESSAGE);\n\n if (after.errorMessage) {\n messageContainer.removeClass('hidden');\n Aria.unhide(messageContainer.get());\n message.text(after.errorMessage);\n } else {\n messageContainer.addClass('hidden');\n Aria.unhide(messageContainer.get());\n message.text('');\n }\n }\n });\n };\n\n /**\n * Remove days from conversation.\n *\n * @param {Object} body The body container element.\n * @param {Array} days Array of days to be removed.\n */\n var renderRemoveDays = function(body, days) {\n days.forEach(function(data) {\n getDayElement(body, data.timestamp).remove();\n });\n };\n\n /**\n * Remove messages from conversation.\n *\n * @param {Object} body The body container element.\n * @param {Array} messages Array of messages to be removed.\n */\n var renderRemoveMessages = function(body, messages) {\n messages.forEach(function(data) {\n getMessageElement(body, data.id).remove();\n });\n };\n\n /**\n * Render the full conversation base on input from the statemanager.\n *\n * This will pre-load all of the formatted timestamps for each message that\n * needs to render to reduce the number of networks requests.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data The conversation diff.\n * @return {Object} jQuery promise.\n */\n var renderConversation = function(header, body, footer, data) {\n var renderingPromises = [];\n var hasAddDays = data.days.add.length > 0;\n var hasAddMessages = data.messages.add.length > 0;\n var hasUpdateMessages = data.messages.update.length > 0;\n var timestampsToFormat = [];\n var datesCachePromise = $.Deferred().resolve({}).promise();\n\n if (hasAddDays) {\n // Search for all of the timeCreated values in all of the messages in all of\n // the days that we need to render.\n timestampsToFormat = timestampsToFormat.concat(data.days.add.reduce(function(carry, day) {\n return carry.concat(day.value.messages.reduce(function(timestamps, message) {\n if (message.timeCreated) {\n timestamps.push(message.timeCreated);\n }\n return timestamps;\n }, []));\n }, []));\n }\n\n if (hasAddMessages) {\n // Search for all of the timeCreated values in all of the messages that we\n // need to render.\n timestampsToFormat = timestampsToFormat.concat(data.messages.add.reduce(function(timestamps, message) {\n if (message.value.timeCreated) {\n timestamps.push(message.value.timeCreated);\n }\n return timestamps;\n }, []));\n }\n\n if (hasUpdateMessages) {\n timestampsToFormat = timestampsToFormat.concat(data.messages.update.reduce(function(timestamps, message) {\n if (message.before.timeCreated != message.after.timeCreated) {\n timestamps.push(message.after.timeCreated);\n }\n return timestamps;\n }, []));\n }\n\n if (timestampsToFormat.length) {\n // If we have timestamps then pre-load the formatted version of each of them\n // in a single request to the server. This saves the templates doing multiple\n // individual requests.\n datesCachePromise = Str.get_string('strftimetime24', 'core_langconfig')\n .then(function(format) {\n var requests = timestampsToFormat.map(function(timestamp) {\n return {\n timestamp: timestamp,\n format: format\n };\n });\n\n return UserDate.get(requests);\n })\n .then(function(formattedTimes) {\n return timestampsToFormat.reduce(function(carry, timestamp, index) {\n carry[timestamp] = formattedTimes[index];\n return carry;\n }, {});\n });\n }\n\n if (hasAddDays) {\n renderingPromises.push(datesCachePromise.then(function(datesCache) {\n return renderAddDays(header, body, footer, data.days.add, datesCache);\n }));\n }\n\n if (hasAddMessages) {\n renderingPromises.push(datesCachePromise.then(function(datesCache) {\n return renderAddMessages(header, body, footer, data.messages.add, datesCache);\n }));\n }\n\n if (hasUpdateMessages) {\n renderingPromises.push(datesCachePromise.then(function(datesCache) {\n return renderUpdateMessages(header, body, footer, data.messages.update, datesCache);\n }));\n }\n\n if (data.days.remove.length > 0) {\n renderRemoveDays(body, data.days.remove);\n }\n\n if (data.messages.remove.length > 0) {\n renderRemoveMessages(body, data.messages.remove);\n }\n\n return $.when.apply($, renderingPromises);\n };\n\n /**\n * Render the conversation header.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data Data for header.\n * @return {Object} jQuery promise\n */\n var renderHeader = function(header, body, footer, data) {\n var headerContainer = getHeaderContent(header);\n var template = TEMPLATES.HEADER_PUBLIC;\n data.context.showrouteback = (header.attr('data-from-panel') === \"false\");\n if (data.type == CONVERSATION_TYPES.PRIVATE) {\n template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS;\n } else if (data.type == CONVERSATION_TYPES.SELF) {\n template = TEMPLATES.HEADER_SELF;\n }\n\n return Templates.render(template, data.context)\n .then(function(html, js) {\n Templates.replaceNodeContents(headerContainer, html, js);\n return;\n });\n };\n\n /**\n * Render the conversation footer.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data Data for footer.\n * @return {Object} jQuery promise.\n */\n var renderFooter = function(header, body, footer, data) {\n hideAllFooterElements(footer);\n\n switch (data.type) {\n case 'placeholder':\n return showFooterPlaceholder(footer);\n case 'add-contact':\n return Str.get_strings([\n {\n key: 'requirecontacttomessage',\n component: 'core_message',\n param: data.user.fullname\n },\n {\n key: 'isnotinyourcontacts',\n component: 'core_message',\n param: data.user.fullname\n }\n ])\n .then(function(strings) {\n var title = strings[1];\n var text = strings[0];\n var footerContainer = getFooterRequireContactContainer(footer);\n footerContainer.find(SELECTORS.TITLE).text(title);\n footerContainer.find(SELECTORS.TEXT).text(text);\n showFooterRequireContact(footer);\n return strings;\n });\n case 'edit-mode':\n return showFooterEditMode(footer);\n case 'content':\n return showFooterContent(footer);\n case 'unblock':\n return showFooterRequireUnblock(footer);\n case 'unable-to-message':\n return showFooterUnableToMessage(footer);\n }\n\n return true;\n };\n\n /**\n * Scroll to a message in the conversation.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Number} messageId Message id.\n */\n var renderScrollToMessage = function(header, body, footer, messageId) {\n var messagesContainer = getMessagesContainer(body);\n var messageElement = getMessageElement(body, messageId);\n var position = messageElement.position();\n // Scroll the message container down to the top of the message element.\n if (position) {\n var scrollTop = messagesContainer.scrollTop() + position.top;\n messagesContainer.scrollTop(scrollTop);\n }\n };\n\n /**\n * Hide or show the conversation header.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} isLoadingMembers Members loading.\n */\n var renderLoadingMembers = function(header, body, footer, isLoadingMembers) {\n if (isLoadingMembers) {\n hideHeaderContent(header);\n showHeaderPlaceholder(header);\n } else {\n showHeaderContent(header);\n hideHeaderPlaceholder(header);\n }\n };\n\n /**\n * Hide or show loading conversation messages.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} isLoadingFirstMessages Messages loading.\n */\n var renderLoadingFirstMessages = function(header, body, footer, isLoadingFirstMessages) {\n if (isLoadingFirstMessages) {\n hideMessagesContainer(body);\n showContentPlaceholder(body);\n } else {\n showMessagesContainer(body);\n hideContentPlaceholder(body);\n }\n };\n\n /**\n * Hide or show loading more messages.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} isLoading Messages loading.\n */\n var renderLoadingMessages = function(header, body, footer, isLoading) {\n if (isLoading) {\n showMoreMessagesLoadingIcon(body);\n } else {\n hideMoreMessagesLoadingIcon(body);\n }\n };\n\n /**\n * Hide or show the emoji picker.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} show Should the emoji picker be visible.\n */\n var renderShowEmojiPicker = function(header, body, footer, show) {\n var container = getEmojiPickerContainer(footer);\n\n if (show) {\n container.removeClass('hidden');\n Aria.unhide(container.get());\n container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus();\n } else {\n container.addClass('hidden');\n Aria.hide(container.get());\n }\n };\n\n /**\n * Hide or show the emoji auto complete.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} show Should the emoji picker be visible.\n */\n var renderShowEmojiAutoComplete = function(header, body, footer, show) {\n var container = getEmojiAutoCompleteContainer(footer);\n\n if (show) {\n container.removeClass('hidden');\n Aria.unhide(container.get());\n } else {\n container.addClass('hidden');\n Aria.hide(container.get());\n }\n };\n\n /**\n * Show a confirmation dialogue\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {String} buttonSelectors Selectors for the buttons to show.\n * @param {String} bodyText Text to show in dialogue.\n * @param {String} headerText Text to show in dialogue header.\n * @param {Bool} canCancel Can this dialogue be cancelled.\n * @param {Bool} skipHeader Skip blanking out the header\n * @param {Bool} showOk Show an 'Okay' button for a dialogue which will close it\n */\n var showConfirmDialogue = function(\n header,\n body,\n footer,\n buttonSelectors,\n bodyText,\n headerText,\n canCancel,\n skipHeader,\n showOk\n ) {\n var dialogue = getConfirmDialogueContainer(body);\n var buttons = buttonSelectors.map(function(selector) {\n return dialogue.find(selector);\n });\n var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);\n var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);\n var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);\n var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);\n\n dialogue.find('button').addClass('hidden');\n\n if (canCancel) {\n cancelButton.removeClass('hidden');\n } else {\n cancelButton.addClass('hidden');\n }\n\n if (showOk) {\n okayButton.removeClass('hidden');\n } else {\n okayButton.addClass('hidden');\n }\n\n if (headerText) {\n // Create the dialogue header.\n dialogueHeader = $('

    ');\n dialogueHeader.text(headerText);\n // Prepend it to the confirmation body.\n var confirmDialogue = dialogue.find(SELECTORS.CONFIRM_DIALOGUE);\n confirmDialogue.prepend(dialogueHeader);\n } else if (dialogueHeader.length) {\n // Header text is empty but dialogue header is present, so remove it.\n dialogueHeader.remove();\n }\n\n buttons.forEach(function(button) {\n button.removeClass('hidden');\n });\n text.text(bodyText);\n showConfirmDialogueContainer(footer);\n showConfirmDialogueContainer(body);\n\n if (!skipHeader) {\n showConfirmDialogueContainer(header);\n }\n\n dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();\n };\n\n /**\n * Hide the dialogue\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @return {Bool} always true.\n */\n var hideConfirmDialogue = function(header, body, footer) {\n var dialogue = getConfirmDialogueContainer(body);\n var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);\n var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);\n var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);\n var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);\n\n hideCheckDeleteDialogue(body);\n hideConfirmDialogueContainer(body);\n hideConfirmDialogueContainer(footer);\n hideConfirmDialogueContainer(header);\n dialogue.find('button').addClass('hidden');\n cancelButton.removeClass('hidden');\n okayButton.removeClass('hidden');\n text.text('');\n\n // Remove dialogue header if present.\n if (dialogueHeader.length) {\n dialogueHeader.remove();\n }\n\n header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus();\n return true;\n };\n\n /**\n * Render the confirm block user dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} user User to block.\n * @return {Object} jQuery promise\n */\n var renderConfirmBlockUser = function(header, body, footer, user) {\n if (user) {\n const username = truncateUsername(user.fullname);\n if (user.canmessageevenifblocked) {\n return Str.get_string('cantblockuser', 'core_message', username)\n .then(function(string) {\n return showConfirmDialogue(header, body, footer, [], string, '', false, false, true);\n });\n } else {\n return Str.get_string('blockuserconfirm', 'core_message', username)\n .then(function(string) {\n return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_BLOCK], string, '', true, false);\n });\n }\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the confirm unblock user dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} user User to unblock.\n * @return {Object} jQuery promise\n */\n var renderConfirmUnblockUser = function(header, body, footer, user) {\n if (user) {\n return Str.get_string('unblockuserconfirm', 'core_message', truncateUsername(user.fullname))\n .then(function(string) {\n return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_UNBLOCK], string, '', true, false);\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the add user as contact dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} user User to add as contact.\n * @return {Object} jQuery promise\n */\n var renderConfirmAddContact = function(header, body, footer, user) {\n if (user) {\n // Truncate long usernames.\n const userFullName = Truncate.truncate(user.fullname, {\n length: 30,\n words: true,\n ellipsis: '...'\n });\n return Str.get_string('addcontactconfirm', 'core_message', userFullName)\n .then(function(string) {\n return showConfirmDialogue(\n header,\n body,\n footer,\n [SELECTORS.ACTION_CONFIRM_ADD_CONTACT],\n string,\n '',\n true,\n false\n );\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the remove user from contacts dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} user User to remove from contacts.\n * @return {Object} jQuery promise\n */\n var renderConfirmRemoveContact = function(header, body, footer, user) {\n if (user) {\n return Str.get_string('removecontactconfirm', 'core_message', truncateUsername(user.fullname))\n .then(function(string) {\n return showConfirmDialogue(\n header,\n body,\n footer,\n [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT],\n string,\n '',\n true,\n false\n );\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the delete selected messages dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data If the dialogue should show and checkbox shows to delete message for all users.\n * @return {Object} jQuery promise\n */\n var renderConfirmDeleteSelectedMessages = function(header, body, footer, data) {\n var showmessage = null;\n if (data.type == CONVERSATION_TYPES.SELF) {\n // Message displayed to self-conversations is slighly different.\n showmessage = 'deleteselectedmessagesconfirmselfconversation';\n } else {\n // This other message should be displayed.\n if (data.canDeleteMessagesForAllUsers) {\n showCheckDeleteDialogue(body);\n showmessage = 'deleteforeveryoneselectedmessagesconfirm';\n } else {\n showmessage = 'deleteselectedmessagesconfirm';\n }\n }\n\n if (data.show) {\n return Str.get_string(showmessage, 'core_message')\n .then(function(string) {\n return showConfirmDialogue(\n header,\n body,\n footer,\n [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES],\n string,\n '',\n true,\n false\n );\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the confirm delete conversation dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {int|Null} type The conversation type to be removed.\n * @return {Object} jQuery promise\n */\n var renderConfirmDeleteConversation = function(header, body, footer, type) {\n var showmessage = null;\n if (type == CONVERSATION_TYPES.SELF) {\n // Message displayed to self-conversations is slighly different.\n showmessage = 'deleteallselfconfirm';\n } else if (type) {\n // This other message should be displayed.\n showmessage = 'deleteallconfirm';\n }\n\n if (showmessage) {\n return Str.get_string(showmessage, 'core_message')\n .then(function(string) {\n return showConfirmDialogue(\n header,\n body,\n footer,\n [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION],\n string,\n '',\n true,\n false\n );\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Render the confirm delete conversation dialogue.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} user The other user object.\n * @return {Object} jQuery promise\n */\n var renderConfirmContactRequest = function(header, body, footer, user) {\n if (user) {\n return Str.get_string('userwouldliketocontactyou', 'core_message', truncateUsername(user.fullname))\n .then(function(string) {\n var buttonSelectors = [\n SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,\n SELECTORS.ACTION_DECLINE_CONTACT_REQUEST\n ];\n return showConfirmDialogue(header, body, footer, buttonSelectors, string, '', false, true);\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Show the checkbox to allow delete message for all.\n *\n * @param {Object} body The body container element.\n */\n var showCheckDeleteDialogue = function(body) {\n var dialogue = getConfirmDialogueContainer(body);\n var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);\n checkboxRegion.removeClass('hidden');\n };\n\n /**\n * Hide the checkbox to allow delete message for all.\n *\n * @param {Object} body The body container element.\n */\n var hideCheckDeleteDialogue = function(body) {\n var dialogue = getConfirmDialogueContainer(body);\n var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);\n var checkbox = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE);\n checkbox.prop('checked', false);\n checkboxRegion.addClass('hidden');\n };\n\n /**\n * Show or hide the block / unblock option in the header dropdown menu.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} isBlocked is user blocked.\n */\n var renderIsBlocked = function(header, body, footer, isBlocked) {\n if (isBlocked) {\n header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass('hidden');\n header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass('hidden');\n } else {\n header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass('hidden');\n header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass('hidden');\n }\n };\n\n /**\n * Show or hide the favourite / unfavourite option in the header dropdown menu\n * and the favourite star in the header title.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} state is this conversation a favourite.\n */\n var renderIsFavourite = function(header, body, footer, state) {\n var favouriteIcon = header.find(SELECTORS.FAVOURITE_ICON_CONTAINER);\n var addFavourite = header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE);\n var removeFavourite = header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE);\n\n switch (state) {\n case 'hide':\n favouriteIcon.addClass('hidden');\n addFavourite.addClass('hidden');\n removeFavourite.addClass('hidden');\n break;\n case 'show-add':\n favouriteIcon.addClass('hidden');\n addFavourite.removeClass('hidden');\n removeFavourite.addClass('hidden');\n break;\n case 'show-remove':\n favouriteIcon.removeClass('hidden');\n addFavourite.addClass('hidden');\n removeFavourite.removeClass('hidden');\n break;\n }\n };\n\n /**\n * Show or hide the mute / unmute option in the header dropdown menu\n * and the muted icon in the header title.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {string} state The state of the conversation as defined by the patcher.\n */\n var renderIsMuted = function(header, body, footer, state) {\n var muteIcon = header.find(SELECTORS.MUTED_ICON_CONTAINER);\n var setMuted = header.find(SELECTORS.ACTION_CONFIRM_MUTE);\n var unsetMuted = header.find(SELECTORS.ACTION_CONFIRM_UNMUTE);\n\n switch (state) {\n case 'hide':\n muteIcon.addClass('hidden');\n setMuted.addClass('hidden');\n unsetMuted.addClass('hidden');\n break;\n case 'show-mute':\n muteIcon.addClass('hidden');\n setMuted.removeClass('hidden');\n unsetMuted.addClass('hidden');\n break;\n case 'show-unmute':\n muteIcon.removeClass('hidden');\n setMuted.addClass('hidden');\n unsetMuted.removeClass('hidden');\n break;\n }\n };\n\n /**\n * Show or hide the add / remove user as contact option in the header dropdown menu.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} state the contact state.\n */\n var renderIsContact = function(header, body, footer, state) {\n var addContact = header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT);\n var removeContact = header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT);\n\n switch (state) {\n case 'pending-contact':\n addContact.addClass('hidden');\n removeContact.addClass('hidden');\n break;\n case 'contact':\n addContact.addClass('hidden');\n removeContact.removeClass('hidden');\n break;\n case 'non-contact':\n addContact.removeClass('hidden');\n removeContact.addClass('hidden');\n break;\n }\n };\n\n /**\n * Show or hide confirm action from confirm dialogue is loading.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} isLoading confirm action is loading.\n */\n var renderLoadingConfirmAction = function(header, body, footer, isLoading) {\n var dialogue = getConfirmDialogueContainer(body);\n var buttons = dialogue.find('button');\n var buttonText = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT);\n var loadingIcon = dialogue.find(SELECTORS.LOADING_ICON_CONTAINER);\n\n if (isLoading) {\n buttons.prop('disabled', true);\n buttonText.addClass('hidden');\n loadingIcon.removeClass('hidden');\n } else {\n buttons.prop('disabled', false);\n buttonText.removeClass('hidden');\n loadingIcon.addClass('hidden');\n }\n };\n\n /**\n * Show or hide the header and footer content for edit mode.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Bool} inEditMode In edit mode or not.\n */\n var renderInEditMode = function(header, body, footer, inEditMode) {\n var messages = null;\n\n if (inEditMode) {\n messages = body.find(SELECTORS.MESSAGE_NOT_SELECTED);\n messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');\n hideHeaderContent(header);\n showHeaderEditMode(header);\n } else {\n messages = getMessagesContainer(body);\n messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');\n messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');\n showHeaderContent(header);\n hideHeaderEditMode(header);\n }\n };\n\n /**\n * Select or unselect messages.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data The messages to select or unselect.\n */\n var renderSelectedMessages = function(header, body, footer, data) {\n var hasSelectedMessages = data.count > 0;\n\n if (data.add.length) {\n data.add.forEach(function(messageId) {\n var message = getMessageElement(body, messageId);\n message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');\n message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass('hidden');\n message.attr('aria-checked', true);\n });\n }\n\n if (data.remove.length) {\n data.remove.forEach(function(messageId) {\n var message = getMessageElement(body, messageId);\n\n if (hasSelectedMessages) {\n message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');\n }\n\n message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');\n message.attr('aria-checked', false);\n });\n }\n\n setMessagesSelectedCount(header, data.count);\n };\n\n /**\n * Show or hide the require add contact panel.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} data Whether the user has to be added a a contact.\n * @return {Object} jQuery promise\n */\n var renderRequireAddContact = function(header, body, footer, data) {\n if (data.show && !data.hasMessages) {\n return Str.get_strings([\n {\n key: 'requirecontacttomessage',\n component: 'core_message',\n param: data.user.fullname\n },\n {\n key: 'isnotinyourcontacts',\n component: 'core_message',\n param: data.user.fullname\n }\n ])\n .then(function(strings) {\n var title = strings[1];\n var text = strings[0];\n return showConfirmDialogue(\n header,\n body,\n footer,\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT],\n text,\n title,\n false,\n true\n );\n });\n } else {\n return hideConfirmDialogue(header, body, footer);\n }\n };\n\n /**\n * Show or hide the self-conversation message.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {Object} displayMessage should the message be displayed?.\n * @return {Object|true} jQuery promise\n */\n var renderSelfConversationMessage = function(header, body, footer, displayMessage) {\n var container = getSelfConversationMessageContainer(body);\n if (displayMessage) {\n container.removeClass('hidden');\n } else {\n container.addClass('hidden');\n }\n return true;\n };\n\n /**\n * Show or hide the require add contact panel.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @param {String} userFullName Full name of the other user.\n * @return {Object|true} jQuery promise\n */\n var renderContactRequestSent = function(header, body, footer, userFullName) {\n var container = getContactRequestSentContainer(body);\n if (userFullName) {\n return Str.get_string('yourcontactrequestpending', 'core_message', truncateUsername(userFullName))\n .then(function(string) {\n container.find(SELECTORS.TEXT).text(string);\n container.removeClass('hidden');\n return string;\n });\n } else {\n container.addClass('hidden');\n return true;\n }\n };\n\n /**\n * Reset the UI to the initial state.\n *\n * @param {Object} header The header container element.\n * @param {Object} body The body container element.\n * @param {Object} footer The footer container element.\n * @return {Bool}\n */\n var renderReset = function(header, body, footer) {\n hideConfirmDialogue(header, body, footer);\n hideContactRequestSentContainer(body);\n hideSelfConversationMessageContainer(body);\n hideAllHeaderElements(header);\n showHeaderPlaceholder(header);\n hideAllFooterElements(footer);\n showFooterPlaceholder(footer);\n return true;\n };\n\n var render = function(header, body, footer, patch) {\n var configs = [\n {\n // Resetting the UI needs to come first, if it's required.\n reset: renderReset\n },\n {\n // Any async rendering (stuff that requires templates, strings etc) should\n // go in here.\n conversation: renderConversation,\n header: renderHeader,\n footer: renderFooter,\n confirmBlockUser: renderConfirmBlockUser,\n confirmUnblockUser: renderConfirmUnblockUser,\n confirmAddContact: renderConfirmAddContact,\n confirmRemoveContact: renderConfirmRemoveContact,\n confirmDeleteSelectedMessages: renderConfirmDeleteSelectedMessages,\n confirmDeleteConversation: renderConfirmDeleteConversation,\n confirmContactRequest: renderConfirmContactRequest,\n requireAddContact: renderRequireAddContact,\n selfConversationMessage: renderSelfConversationMessage,\n contactRequestSent: renderContactRequestSent\n },\n {\n loadingMembers: renderLoadingMembers,\n loadingFirstMessages: renderLoadingFirstMessages,\n loadingMessages: renderLoadingMessages,\n isBlocked: renderIsBlocked,\n isContact: renderIsContact,\n isFavourite: renderIsFavourite,\n isMuted: renderIsMuted,\n loadingConfirmAction: renderLoadingConfirmAction,\n inEditMode: renderInEditMode,\n showEmojiPicker: renderShowEmojiPicker,\n showEmojiAutoComplete: renderShowEmojiAutoComplete,\n },\n {\n // Scrolling should be last to make sure everything\n // on the page is visible.\n scrollToMessage: renderScrollToMessage,\n selectedMessages: renderSelectedMessages\n }\n ];\n // Helper function to process each of the configs above.\n var processConfig = function(config) {\n var results = [];\n\n for (var key in patch) {\n if (config.hasOwnProperty(key)) {\n var renderFunc = config[key];\n var patchValue = patch[key];\n results.push(renderFunc(header, body, footer, patchValue));\n }\n }\n\n return results;\n };\n\n // The first config is special because it resets the UI.\n var renderingPromises = processConfig(configs[0]);\n // The second config is special because it contains async rendering.\n renderingPromises = renderingPromises.concat(processConfig(configs[1]));\n\n // Wait for the async rendering to complete before processing the\n // rest of the configs, in order.\n return $.when.apply($, renderingPromises)\n .then(function() {\n for (var i = 2; i < configs.length; i++) {\n processConfig(configs[i]);\n }\n\n return;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Truncate long usernames.\n *\n * @param {String} username Text to truncate.\n * @return {String} Truncated string.\n */\n var truncateUsername = function(username) {\n return Truncate.truncate(username, {\n length: 30,\n words: true,\n ellipsis: '...'\n });\n };\n\n return {\n render: render,\n };\n});\n"],"names":["define","$","Notification","Str","Templates","UserDate","Constants","Aria","Truncate","SELECTORS","TEMPLATES","CONVERSATION_TYPES","getMessagesContainer","body","find","CONTENT_MESSAGES_CONTAINER","getSelfConversationMessageContainer","SELF_CONVERSATION_MESSAGE_CONTAINER","getContactRequestSentContainer","CONTACT_REQUEST_SENT_MESSAGE_CONTAINER","getFooterContentContainer","footer","CONTENT_MESSAGES_FOOTER_CONTAINER","getFooterEditModeContainer","CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER","getFooterPlaceholderContainer","PLACEHOLDER_CONTAINER","showFooterPlaceholder","removeClass","getFooterRequireContactContainer","CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER","getFooterRequireUnblockContainer","CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER","getFooterUnableToMessageContainer","CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER","hideAllFooterElements","addClass","hideFooterContent","hideFooterEditMode","hideFooterPlaceholder","hideFooterRequireContact","hideFooterRequireUnblock","hideFooterUnableToMessage","getContentPlaceholderContainer","CONTENT_PLACEHOLDER_CONTAINER","getHeaderContent","header","HEADER","showHeaderContent","hideHeaderContent","getHeaderEditMode","HEADER_EDIT_MODE","hideHeaderEditMode","getHeaderPlaceholderContainer","HEADER_PLACEHOLDER_CONTAINER","showHeaderPlaceholder","hideHeaderPlaceholder","getMessageElement","messageId","getDayElement","dayTimeCreated","getMoreMessagesLoadingIconContainer","MORE_MESSAGES_LOADING_ICON_CONTAINER","getConfirmDialogueContainer","root","CONFIRM_DIALOGUE_CONTAINER","showConfirmDialogueContainer","container","siblings","hide","get","attr","hideConfirmDialogueContainer","unhide","removeAttr","formatMessagesForTemplate","messages","datesCache","map","message","id","isread","isRead","fromloggedinuser","fromLoggedInUser","userfrom","userFrom","text","formattedtime","timeCreated","renderConversation","data","renderingPromises","hasAddDays","days","add","length","hasAddMessages","hasUpdateMessages","update","timestampsToFormat","datesCachePromise","Deferred","resolve","promise","concat","reduce","carry","day","value","timestamps","push","before","after","get_string","then","format","requests","timestamp","formattedTimes","index","messagesContainer","daysRenderPromises","timestampDate","Date","render","DAY","currentyear","getFullYear","when","apply","forEach","html","element","insertBefore","append","catch","renderAddDays","messagesData","formattedMessages","MESSAGES","messageList","messageHtml","DAY_MESSAGES_CONTAINER","renderAddMessages","formattedTime","LOADING_ICON_CONTAINER","TIME_CREATED","sendState","loading","time","retry","RETRY_SEND","TEXT_CONTAINER","errorMessage","messageContainer","ERROR_MESSAGE_CONTAINER","ERROR_MESSAGE","renderUpdateMessages","remove","renderRemoveDays","renderRemoveMessages","renderHeader","headerContainer","template","HEADER_PUBLIC","context","showrouteback","type","PRIVATE","showControls","HEADER_PRIVATE","HEADER_PRIVATE_NO_CONTROLS","SELF","HEADER_SELF","js","replaceNodeContents","renderFooter","get_strings","key","component","param","user","fullname","strings","title","footerContainer","TITLE","TEXT","showFooterRequireContact","showFooterEditMode","showFooterContent","showFooterRequireUnblock","showFooterUnableToMessage","renderScrollToMessage","position","scrollTop","top","renderLoadingMembers","isLoadingMembers","renderLoadingFirstMessages","isLoadingFirstMessages","hideMessagesContainer","showContentPlaceholder","showMessagesContainer","hideContentPlaceholder","renderLoadingMessages","isLoading","showMoreMessagesLoadingIcon","hideMoreMessagesLoadingIcon","renderShowEmojiPicker","show","EMOJI_PICKER_CONTAINER","getEmojiPickerContainer","EMOJI_PICKER_SEARCH_INPUT","focus","renderShowEmojiAutoComplete","EMOJI_AUTO_COMPLETE_CONTAINER","getEmojiAutoCompleteContainer","showConfirmDialogue","buttonSelectors","bodyText","headerText","canCancel","skipHeader","showOk","dialogue","buttons","selector","cancelButton","CONFIRM_DIALOGUE_CANCEL_BUTTON","okayButton","CONFIRM_DIALOGUE_OKAY_BUTTON","CONFIRM_DIALOGUE_TEXT","dialogueHeader","CONFIRM_DIALOGUE_HEADER","CONFIRM_DIALOGUE","prepend","button","CAN_RECEIVE_FOCUS","filter","first","hideConfirmDialogue","hideCheckDeleteDialogue","renderConfirmBlockUser","username","truncateUsername","canmessageevenifblocked","string","ACTION_CONFIRM_BLOCK","renderConfirmUnblockUser","ACTION_CONFIRM_UNBLOCK","renderConfirmAddContact","userFullName","truncate","words","ellipsis","ACTION_CONFIRM_ADD_CONTACT","renderConfirmRemoveContact","ACTION_CONFIRM_REMOVE_CONTACT","renderConfirmDeleteSelectedMessages","showmessage","canDeleteMessagesForAllUsers","showCheckDeleteDialogue","ACTION_CONFIRM_DELETE_SELECTED_MESSAGES","renderConfirmDeleteConversation","ACTION_CONFIRM_DELETE_CONVERSATION","renderConfirmContactRequest","ACTION_ACCEPT_CONTACT_REQUEST","ACTION_DECLINE_CONTACT_REQUEST","DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER","checkboxRegion","DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE","prop","renderIsBlocked","isBlocked","ACTION_REQUEST_BLOCK","ACTION_REQUEST_UNBLOCK","renderIsFavourite","state","favouriteIcon","FAVOURITE_ICON_CONTAINER","addFavourite","ACTION_CONFIRM_FAVOURITE","removeFavourite","ACTION_CONFIRM_UNFAVOURITE","renderIsMuted","muteIcon","MUTED_ICON_CONTAINER","setMuted","ACTION_CONFIRM_MUTE","unsetMuted","ACTION_CONFIRM_UNMUTE","renderIsContact","addContact","ACTION_REQUEST_ADD_CONTACT","removeContact","ACTION_REQUEST_REMOVE_CONTACT","renderLoadingConfirmAction","buttonText","CONFIRM_DIALOGUE_BUTTON_TEXT","loadingIcon","renderInEditMode","inEditMode","MESSAGE_NOT_SELECTED","MESSAGE_NOT_SELECTED_ICON","showHeaderEditMode","MESSAGE_SELECTED_ICON","renderSelectedMessages","hasSelectedMessages","count","MESSAGES_SELECTED_COUNT","setMessagesSelectedCount","renderRequireAddContact","hasMessages","renderSelfConversationMessage","displayMessage","renderContactRequestSent","renderReset","hideContactRequestSentContainer","hideSelfConversationMessageContainer","hideAllHeaderElements","patch","configs","reset","conversation","confirmBlockUser","confirmUnblockUser","confirmAddContact","confirmRemoveContact","confirmDeleteSelectedMessages","confirmDeleteConversation","confirmContactRequest","requireAddContact","selfConversationMessage","contactRequestSent","loadingMembers","loadingFirstMessages","loadingMessages","isContact","isFavourite","isMuted","loadingConfirmAction","showEmojiPicker","showEmojiAutoComplete","scrollToMessage","selectedMessages","processConfig","config","results","hasOwnProperty","renderFunc","patchValue","i","exception"],"mappings":";;;;;;;;;;;;;AA4BAA,gEACA,CACI,SACA,oBACA,WACA,iBACA,iBACA,0DACA,YACA,kBAEJ,SACIC,EACAC,aACAC,IACAC,UACAC,SACAC,UACAC,KACAC,cAEIC,UAAYH,UAAUG,UACtBC,UAAYJ,UAAUI,UACtBC,mBAAqBL,UAAUK,mBAQ/BC,qBAAuB,SAASC,aACzBA,KAAKC,KAAKL,UAAUM,6BA2B3BC,oCAAsC,SAASH,aACxCA,KAAKC,KAAKL,UAAUQ,sCAmB3BC,+BAAiC,SAASL,aACnCA,KAAKC,KAAKL,UAAUU,yCAmB3BC,0BAA4B,SAASC,eAC9BA,OAAOP,KAAKL,UAAUa,oCA2B7BC,2BAA6B,SAASF,eAC/BA,OAAOP,KAAKL,UAAUe,8CA2B7BC,8BAAgC,SAASJ,eAClCA,OAAOP,KAAKL,UAAUiB,wBAQ7BC,sBAAwB,SAASN,QACjCI,8BAA8BJ,QAAQO,YAAY,WAkBlDC,iCAAmC,SAASR,eACrCA,OAAOP,KAAKL,UAAUqB,oDA2B7BC,iCAAmC,SAASV,eACrCA,OAAOP,KAAKL,UAAUuB,oDA2B7BC,kCAAoC,SAASZ,eACtCA,OAAOP,KAAKL,UAAUyB,sDAqC7BC,sBAAwB,SAASd,SAhKb,SAASA,QAC7BD,0BAA0BC,QAAQe,SAAS,UAgK3CC,CAAkBhB,QArIG,SAASA,QAC9BE,2BAA2BF,QAAQe,SAAS,UAqI5CE,CAAmBjB,QA1GK,SAASA,QACjCI,8BAA8BJ,QAAQe,SAAS,UA0G/CG,CAAsBlB,QA/EK,SAASA,QACpCQ,iCAAiCR,QAAQe,SAAS,UA+ElDI,CAAyBnB,QApDE,SAASA,QACpCU,iCAAiCV,QAAQe,SAAS,UAoDlDK,CAAyBpB,QAzBG,SAASA,QACrCY,kCAAkCZ,QAAQe,SAAS,UAyBnDM,CAA0BrB,SAS1BsB,+BAAiC,SAAS9B,aACnCA,KAAKC,KAAKL,UAAUmC,gCA2B3BC,iBAAmB,SAASC,eACrBA,OAAOhC,KAAKL,UAAUsC,SAQ7BC,kBAAoB,SAASF,QAC7BD,iBAAiBC,QAAQlB,YAAY,WAQrCqB,kBAAoB,SAASH,QAC7BD,iBAAiBC,QAAQV,SAAS,WASlCc,kBAAoB,SAASJ,eACtBA,OAAOhC,KAAKL,UAAU0C,mBAiB7BC,mBAAqB,SAASN,QAC9BI,kBAAkBJ,QAAQV,SAAS,WASnCiB,8BAAgC,SAASP,eAClCA,OAAOhC,KAAKL,UAAU6C,+BAQ7BC,sBAAwB,SAAST,QACjCO,8BAA8BP,QAAQlB,YAAY,WAQlD4B,sBAAwB,SAASV,QACjCO,8BAA8BP,QAAQV,SAAS,WA8B/CqB,kBAAoB,SAAS5C,KAAM6C,kBACX9C,qBAAqBC,MACpBC,KAAK,qBAAuB4C,UAAY,OAUjEC,cAAgB,SAAS9C,KAAM+C,uBACPhD,qBAAqBC,MACpBC,KAAK,iBAAmB8C,eAAiB,OASlEC,oCAAsC,SAAShD,aACxCA,KAAKC,KAAKL,UAAUqD,uCA2B3BC,4BAA8B,SAASC,aAChCA,KAAKlD,KAAKL,UAAUwD,6BAQ3BC,6BAA+B,SAASF,UACpCG,UAAYJ,4BAA4BC,MACxCI,SAAWD,UAAUC,SAAS,iBAClC7D,KAAK8D,KAAKD,SAASE,OACnBF,SAASG,KAAK,gCAAgC,GAE9CJ,UAAUvC,YAAY,WAQtB4C,6BAA+B,SAASR,UACpCG,UAAYJ,4BAA4BC,MACxCI,SAAWD,UAAUC,SAAS,yCAClC7D,KAAKkE,OAAOL,SAASE,OACrBF,SAASM,WAAW,gCAEpBP,UAAU/B,SAAS,WAoBnBuC,0BAA4B,SAASC,SAAUC,mBACxCD,SAASE,KAAI,SAASC,eAClB,CACHC,GAAID,QAAQC,GACZC,OAAQF,QAAQG,OAChBC,iBAAkBJ,QAAQK,iBAC1BC,SAAUN,QAAQO,SAClBC,KAAMR,QAAQQ,KACdC,cAAeT,QAAQU,YAAcZ,WAAWE,QAAQU,aAAe,UAsM/EC,mBAAqB,SAAS5C,OAAQjC,KAAMQ,OAAQsE,UAChDC,kBAAoB,GACpBC,WAAaF,KAAKG,KAAKC,IAAIC,OAAS,EACpCC,eAAiBN,KAAKf,SAASmB,IAAIC,OAAS,EAC5CE,kBAAoBP,KAAKf,SAASuB,OAAOH,OAAS,EAClDI,mBAAqB,GACrBC,kBAAoBpG,EAAEqG,WAAWC,QAAQ,IAAIC,iBAE7CX,aAGAO,mBAAqBA,mBAAmBK,OAAOd,KAAKG,KAAKC,IAAIW,QAAO,SAASC,MAAOC,YACzED,MAAMF,OAAOG,IAAIC,MAAMjC,SAAS8B,QAAO,SAASI,WAAY/B,gBAC3DA,QAAQU,aACRqB,WAAWC,KAAKhC,QAAQU,aAErBqB,aACR,OACJ,MAGHb,iBAGAG,mBAAqBA,mBAAmBK,OAAOd,KAAKf,SAASmB,IAAIW,QAAO,SAASI,WAAY/B,gBACrFA,QAAQ8B,MAAMpB,aACdqB,WAAWC,KAAKhC,QAAQ8B,MAAMpB,aAE3BqB,aACR,MAGHZ,oBACAE,mBAAqBA,mBAAmBK,OAAOd,KAAKf,SAASuB,OAAOO,QAAO,SAASI,WAAY/B,gBACxFA,QAAQiC,OAAOvB,aAAeV,QAAQkC,MAAMxB,aAC5CqB,WAAWC,KAAKhC,QAAQkC,MAAMxB,aAE3BqB,aACR,MAGHV,mBAAmBJ,SAInBK,kBAAoBlG,IAAI+G,WAAW,iBAAkB,mBAChDC,MAAK,SAASC,YACPC,SAAWjB,mBAAmBtB,KAAI,SAASwC,iBACpC,CACHA,UAAWA,UACXF,OAAQA,kBAIT/G,SAASiE,IAAI+C,aAEvBF,MAAK,SAASI,uBACJnB,mBAAmBM,QAAO,SAASC,MAAOW,UAAWE,cACxDb,MAAMW,WAAaC,eAAeC,OAC3Bb,QACR,QAIXd,YACAD,kBAAkBmB,KAAKV,kBAAkBc,MAAK,SAAStC,mBAxP3C,SAAS/B,OAAQjC,KAAMQ,OAAQyE,KAAMjB,gBACjD4C,kBAAoB7G,qBAAqBC,MACzC6G,mBAAqB5B,KAAKhB,KAAI,SAASa,UACnCgC,cAAgB,IAAIC,KAA4B,IAAvBjC,KAAKkB,MAAMS,kBACjClH,UAAUyH,OAAOnH,UAAUoH,IAAK,CACnCR,UAAW3B,KAAKkB,MAAMS,UACtBS,YAAaJ,cAAcK,iBAAmB,IAAIJ,MAAQI,cAC1DpD,SAAUD,0BAA0BgB,KAAKkB,MAAMjC,SAAUC,wBAI1D5E,EAAEgI,KAAKC,MAAMjI,EAAGyH,oBAAoBP,MAAK,WAG5CrB,KAAKqC,SAAQ,SAASxC,KAAM6B,OACxBE,mBAAmBF,OACdL,MAAK,SAASiB,SACPzC,KAAKqB,OAAQ,KACTqB,QAAU1E,cAAc9C,KAAM8E,KAAKqB,OAAOM,kBACvCrH,EAAEmI,MAAME,aAAaD,gBAErBZ,kBAAkBc,OAAOH,SAGvCI,OAAM,qBAiOJC,CAAc3F,EAAQjC,KAAMQ,EAAQsE,KAAKG,KAAKC,IAAKlB,gBAI9DoB,gBACAL,kBAAkBmB,KAAKV,kBAAkBc,MAAK,SAAStC,mBAnNvC,SAAS/B,OAAQjC,KAAMQ,OAAQuD,SAAUC,gBACzD6D,aAAe9D,SAASE,KAAI,SAASa,aAC9BA,KAAKkB,SAEZ8B,kBAAoBhE,0BAA0B+D,aAAc7D,mBAEzDzE,UAAUyH,OAAOnH,UAAUkI,SAAU,CAAChE,SAAU+D,oBAClDxB,MAAK,SAASiB,UACPS,YAAc5I,EAAEmI,MACpBxD,SAASuD,SAAQ,SAASxC,UAClBmD,YAAcD,YAAY/H,KAAK,qBAAuB6E,KAAKkB,MAAM7B,GAAK,SACtEW,KAAKqB,OAAQ,KACTqB,QAAU5E,kBAAkB5C,KAAM8E,KAAKqB,OAAOhC,WAC3C8D,YAAYR,aAAaD,gBAEb1E,cAAc9C,KAAM8E,KAAKiB,IAAIU,WACRxG,KAAKL,UAAUsI,wBAC3BR,OAAOO,mBAmMpCE,CAAkBlG,EAAQjC,KAAMQ,EAAQsE,KAAKf,SAASmB,IAAKlB,gBAItEqB,mBACAN,kBAAkBmB,KAAKV,kBAAkBc,MAAK,SAAStC,mBAvLpC,SAAS/B,OAAQjC,KAAMQ,OAAQuD,SAAUC,YAChED,SAASuD,SAAQ,SAASpD,aAClBiC,OAASjC,QAAQiC,OACjBC,MAAQlC,QAAQkC,MAChBoB,QAAU5E,kBAAkB5C,KAAMmG,OAAOhC,OAEzCgC,OAAOhC,IAAMiC,MAAMjC,IACnBqD,QAAQ9D,KAAK,kBAAmB0C,MAAMjC,IAGtCgC,OAAOvB,aAAewB,MAAMxB,YAAa,KACrCwD,cAAgBpE,WAAWoC,MAAMxB,aACrC4C,QAAQvH,KAAKL,UAAUyI,wBAAwB9G,SAAS,UACxDiG,QAAQvH,KAAKL,UAAU0I,cAAc5D,KAAK0D,eAAerH,YAAY,aAGrEoF,OAAOoC,WAAanC,MAAMmC,UAAW,KACjCC,QAAUhB,QAAQvH,KAAKL,UAAUyI,wBACjCI,KAAOjB,QAAQvH,KAAKL,UAAU0I,cAC9BI,MAAQlB,QAAQvH,KAAKL,UAAU+I,mBAEnCH,QAAQjH,SAAS,UACjB7B,KAAK8D,KAAKgF,QAAQ/E,OAElBgF,KAAKlH,SAAS,UACd7B,KAAK8D,KAAKiF,KAAKhF,OAEfiF,MAAMnH,SAAS,UACf7B,KAAK8D,KAAKkF,MAAMjF,OAEhB+D,QAAQzG,YAAY,wBAEZqF,MAAMmC,eACL,UACDC,QAAQzH,YAAY,UACpBrB,KAAKkE,OAAO4E,QAAQ/E,iBAEnB,QACDiF,MAAM3H,YAAY,UAClBrB,KAAKkE,OAAO8E,MAAMjF,OAClB+D,QAAQjG,SAAS,kCAEhB,OACDkH,KAAK1H,YAAY,UACjBrB,KAAKkE,OAAO6E,KAAKhF,WAKzB0C,OAAOzB,MAAQ0B,MAAM1B,MACrB8C,QAAQvH,KAAKL,UAAUgJ,gBAAgBrB,KAAKnB,MAAM1B,MAGlDyB,OAAO0C,cAAgBzC,MAAMyC,aAAc,KACvCC,iBAAmBtB,QAAQvH,KAAKL,UAAUmJ,yBAC1C7E,QAAU4E,iBAAiB7I,KAAKL,UAAUoJ,eAE1C5C,MAAMyC,cACNC,iBAAiB/H,YAAY,UAC7BrB,KAAKkE,OAAOkF,iBAAiBrF,OAC7BS,QAAQQ,KAAK0B,MAAMyC,gBAEnBC,iBAAiBvH,SAAS,UAC1B7B,KAAKkE,OAAOkF,iBAAiBrF,OAC7BS,QAAQQ,KAAK,SAwHVuE,CAAqBhH,EAAQjC,KAAMQ,EAAQsE,KAAKf,SAASuB,OAAQtB,gBAI5Ec,KAAKG,KAAKiE,OAAO/D,OAAS,GAhHX,SAASnF,KAAMiF,MAClCA,KAAKqC,SAAQ,SAASxC,MAClBhC,cAAc9C,KAAM8E,KAAK2B,WAAWyC,YA+GpCC,CAAiBnJ,KAAM8E,KAAKG,KAAKiE,QAGjCpE,KAAKf,SAASmF,OAAO/D,OAAS,GAxGX,SAASnF,KAAM+D,UACtCA,SAASuD,SAAQ,SAASxC,MACtBlC,kBAAkB5C,KAAM8E,KAAKX,IAAI+E,YAuGjCE,CAAqBpJ,KAAM8E,KAAKf,SAASmF,QAGtC9J,EAAEgI,KAAKC,MAAMjI,EAAG2F,oBAYvBsE,aAAe,SAASpH,OAAQjC,KAAMQ,OAAQsE,UAC1CwE,gBAAkBtH,iBAAiBC,QACnCsH,SAAW1J,UAAU2J,qBACzB1E,KAAK2E,QAAQC,cAAoD,UAAnCzH,OAAOyB,KAAK,mBACtCoB,KAAK6E,MAAQ7J,mBAAmB8J,QAChCL,SAAWzE,KAAK+E,aAAehK,UAAUiK,eAAiBjK,UAAUkK,2BAC7DjF,KAAK6E,MAAQ7J,mBAAmBkK,OACvCT,SAAW1J,UAAUoK,aAGlB1K,UAAUyH,OAAOuC,SAAUzE,KAAK2E,SAClCnD,MAAK,SAASiB,KAAM2C,IACjB3K,UAAU4K,oBAAoBb,gBAAiB/B,KAAM2C,QAc7DE,aAAe,SAASnI,OAAQjC,KAAMQ,OAAQsE,aAC9CxD,sBAAsBd,QAEdsE,KAAK6E,UACJ,qBACM7I,sBAAsBN,YAC5B,qBACMlB,IAAI+K,YAAY,CACf,CACIC,IAAK,0BACLC,UAAW,eACXC,MAAO1F,KAAK2F,KAAKC,UAErB,CACIJ,IAAK,sBACLC,UAAW,eACXC,MAAO1F,KAAK2F,KAAKC,YAGxBpE,MAAK,SAASqE,aACPC,MAAQD,QAAQ,GAChBjG,KAAOiG,QAAQ,GACfE,gBAAkB7J,iCAAiCR,eACvDqK,gBAAgB5K,KAAKL,UAAUkL,OAAOpG,KAAKkG,OAC3CC,gBAAgB5K,KAAKL,UAAUmL,MAAMrG,KAAKA,MAnrB/B,SAASlE,QACpCQ,iCAAiCR,QAAQO,YAAY,UAmrBrCiK,CAAyBxK,QAClBmK,eAEd,mBA/uBY,SAASnK,QAC9BE,2BAA2BF,QAAQO,YAAY,UA+uBhCkK,CAAmBzK,YACzB,iBA7wBW,SAASA,QAC7BD,0BAA0BC,QAAQO,YAAY,UA6wB/BmK,CAAkB1K,YACxB,iBA/pBkB,SAASA,QACpCU,iCAAiCV,QAAQO,YAAY,UA+pBtCoK,CAAyB3K,YAC/B,2BAroBmB,SAASA,QACrCY,kCAAkCZ,QAAQO,YAAY,UAqoBvCqK,CAA0B5K,eAGlC,GAWP6K,sBAAwB,SAASpJ,OAAQjC,KAAMQ,OAAQqC,eACnD+D,kBAAoB7G,qBAAqBC,MAEzCsL,SADiB1I,kBAAkB5C,KAAM6C,WACfyI,cAE1BA,SAAU,KACNC,UAAY3E,kBAAkB2E,YAAcD,SAASE,IACzD5E,kBAAkB2E,UAAUA,aAYhCE,qBAAuB,SAASxJ,OAAQjC,KAAMQ,OAAQkL,kBAClDA,kBACAtJ,kBAAkBH,QAClBS,sBAAsBT,UAEtBE,kBAAkBF,QAClBU,sBAAsBV,UAY1B0J,2BAA6B,SAAS1J,OAAQjC,KAAMQ,OAAQoL,wBACxDA,wBAj4BoB,SAAS5L,MACjCD,qBAAqBC,MAAMuB,SAAS,UAi4BhCsK,CAAsB7L,MAtoBD,SAASA,MAClC8B,+BAA+B9B,MAAMe,YAAY,UAsoB7C+K,CAAuB9L,QA54BH,SAASA,MACjCD,qBAAqBC,MAAMe,YAAY,UA64BnCgL,CAAsB/L,MAhoBD,SAASA,MAClC8B,+BAA+B9B,MAAMuB,SAAS,UAgoB1CyK,CAAuBhM,QAY3BiM,sBAAwB,SAAShK,OAAQjC,KAAMQ,OAAQ0L,WACnDA,UA3f0B,SAASlM,MACvCgD,oCAAoChD,MAAMe,YAAY,UA2flDoL,CAA4BnM,MAnfF,SAASA,MACvCgD,oCAAoChD,MAAMuB,SAAS,UAof/C6K,CAA4BpM,OAYhCqM,sBAAwB,SAASpK,OAAQjC,KAAMQ,OAAQ8L,UACnDhJ,UAhkBsB,SAAS9C,eAC5BA,OAAOP,KAAKL,UAAU2M,wBA+jBbC,CAAwBhM,QAEpC8L,MACAhJ,UAAUvC,YAAY,UACtBrB,KAAKkE,OAAON,UAAUG,OACtBH,UAAUrD,KAAKL,UAAU6M,2BAA2BC,UAEpDpJ,UAAU/B,SAAS,UACnB7B,KAAK8D,KAAKF,UAAUG,SAYxBkJ,4BAA8B,SAAS1K,OAAQjC,KAAMQ,OAAQ8L,UACzDhJ,UA3kB4B,SAAS9C,eAClCA,OAAOP,KAAKL,UAAUgN,+BA0kBbC,CAA8BrM,QAE1C8L,MACAhJ,UAAUvC,YAAY,UACtBrB,KAAKkE,OAAON,UAAUG,SAEtBH,UAAU/B,SAAS,UACnB7B,KAAK8D,KAAKF,UAAUG,SAiBxBqJ,oBAAsB,SACtB7K,OACAjC,KACAQ,OACAuM,gBACAC,SACAC,WACAC,UACAC,WACAC,YAEIC,SAAWnK,4BAA4BlD,MACvCsN,QAAUP,gBAAgB9I,KAAI,SAASsJ,iBAChCF,SAASpN,KAAKsN,aAErBC,aAAeH,SAASpN,KAAKL,UAAU6N,gCACvCC,WAAaL,SAASpN,KAAKL,UAAU+N,8BACrCjJ,KAAO2I,SAASpN,KAAKL,UAAUgO,uBAC/BC,eAAiBR,SAASpN,KAAKL,UAAUkO,0BAE7CT,SAASpN,KAAK,UAAUsB,SAAS,UAE7B2L,UACAM,aAAazM,YAAY,UAEzByM,aAAajM,SAAS,UAGtB6L,OACAM,WAAW3M,YAAY,UAEvB2M,WAAWnM,SAAS,UAGpB0L,cAEAY,eAAiBzO,EAAE,uDACJsF,KAAKuI,YAEEI,SAASpN,KAAKL,UAAUmO,kBAC9BC,QAAQH,iBACjBA,eAAe1I,QAEtB0I,eAAe3E,SAGnBoE,QAAQhG,SAAQ,SAAS2G,QACrBA,OAAOlN,YAAY,aAEvB2D,KAAKA,KAAKsI,UACV3J,6BAA6B7C,QAC7B6C,6BAA6BrD,MAExBmN,YACD9J,6BAA6BpB,QAGjCoL,SAASpN,KAAKL,UAAUsO,mBAAmBC,OAAO,YAAYC,QAAQ1B,SAWtE2B,oBAAsB,SAASpM,OAAQjC,KAAMQ,YACzC6M,SAAWnK,4BAA4BlD,MACvCwN,aAAeH,SAASpN,KAAKL,UAAU6N,gCACvCC,WAAaL,SAASpN,KAAKL,UAAU+N,8BACrCjJ,KAAO2I,SAASpN,KAAKL,UAAUgO,uBAC/BC,eAAiBR,SAASpN,KAAKL,UAAUkO,gCAE7CQ,wBAAwBtO,MACxB2D,6BAA6B3D,MAC7B2D,6BAA6BnD,QAC7BmD,6BAA6B1B,QAC7BoL,SAASpN,KAAK,UAAUsB,SAAS,UACjCiM,aAAazM,YAAY,UACzB2M,WAAW3M,YAAY,UACvB2D,KAAKA,KAAK,IAGNmJ,eAAe1I,QACf0I,eAAe3E,SAGnBjH,OAAOhC,KAAKL,UAAUsO,mBAAmBE,QAAQ1B,SAC1C,GAYP6B,uBAAyB,SAAStM,OAAQjC,KAAMQ,OAAQiK,SACpDA,KAAM,OACA+D,SAAWC,iBAAiBhE,KAAKC,iBACnCD,KAAKiE,wBACEpP,IAAI+G,WAAW,gBAAiB,eAAgBmI,UAClDlI,MAAK,SAASqI,eACJ7B,oBAAoB7K,OAAQjC,KAAMQ,OAAQ,GAAImO,OAAQ,IAAI,GAAO,GAAO,MAGhFrP,IAAI+G,WAAW,mBAAoB,eAAgBmI,UACrDlI,MAAK,SAASqI,eACJ7B,oBAAoB7K,OAAQjC,KAAMQ,OAAQ,CAACZ,UAAUgP,sBAAuBD,OAAQ,IAAI,GAAM,aAI1GN,oBAAoBpM,OAAQjC,KAAMQ,SAa7CqO,yBAA2B,SAAS5M,OAAQjC,KAAMQ,OAAQiK,aACtDA,KACOnL,IAAI+G,WAAW,qBAAsB,eAAgBoI,iBAAiBhE,KAAKC,WAC7EpE,MAAK,SAASqI,eACJ7B,oBAAoB7K,OAAQjC,KAAMQ,OAAQ,CAACZ,UAAUkP,wBAAyBH,OAAQ,IAAI,GAAM,MAGxGN,oBAAoBpM,OAAQjC,KAAMQ,SAa7CuO,wBAA0B,SAAS9M,OAAQjC,KAAMQ,OAAQiK,SACrDA,KAAM,OAEAuE,aAAerP,SAASsP,SAASxE,KAAKC,SAAU,CAClDvF,OAAQ,GACR+J,OAAO,EACPC,SAAU,eAEP7P,IAAI+G,WAAW,oBAAqB,eAAgB2I,cACtD1I,MAAK,SAASqI,eACJ7B,oBACH7K,OACAjC,KACAQ,OACA,CAACZ,UAAUwP,4BACXT,OACA,IACA,GACA,aAILN,oBAAoBpM,OAAQjC,KAAMQ,SAa7C6O,2BAA6B,SAASpN,OAAQjC,KAAMQ,OAAQiK,aACxDA,KACOnL,IAAI+G,WAAW,uBAAwB,eAAgBoI,iBAAiBhE,KAAKC,WAC/EpE,MAAK,SAASqI,eACJ7B,oBACH7K,OACAjC,KACAQ,OACA,CAACZ,UAAU0P,+BACXX,OACA,IACA,GACA,MAILN,oBAAoBpM,OAAQjC,KAAMQ,SAa7C+O,oCAAsC,SAAStN,OAAQjC,KAAMQ,OAAQsE,UACjE0K,YAAc,YACd1K,KAAK6E,MAAQ7J,mBAAmBkK,KAEhCwF,YAAc,gDAGV1K,KAAK2K,8BACLC,wBAAwB1P,MACxBwP,YAAc,4CAEdA,YAAc,gCAIlB1K,KAAKwH,KACEhN,IAAI+G,WAAWmJ,YAAa,gBAC9BlJ,MAAK,SAASqI,eACJ7B,oBACH7K,OACAjC,KACAQ,OACA,CAACZ,UAAU+P,yCACXhB,OACA,IACA,GACA,MAILN,oBAAoBpM,OAAQjC,KAAMQ,SAa7CoP,gCAAkC,SAAS3N,OAAQjC,KAAMQ,OAAQmJ,UAC7D6F,YAAc,YACd7F,MAAQ7J,mBAAmBkK,KAE3BwF,YAAc,uBACP7F,OAEP6F,YAAc,oBAGdA,YACOlQ,IAAI+G,WAAWmJ,YAAa,gBAC9BlJ,MAAK,SAASqI,eACJ7B,oBACH7K,OACAjC,KACAQ,OACA,CAACZ,UAAUiQ,oCACXlB,OACA,IACA,GACA,MAILN,oBAAoBpM,OAAQjC,KAAMQ,SAa7CsP,4BAA8B,SAAS7N,OAAQjC,KAAMQ,OAAQiK,aACzDA,KACOnL,IAAI+G,WAAW,4BAA6B,eAAgBoI,iBAAiBhE,KAAKC,WACpFpE,MAAK,SAASqI,YACP5B,gBAAkB,CAClBnN,UAAUmQ,8BACVnQ,UAAUoQ,uCAEPlD,oBAAoB7K,OAAQjC,KAAMQ,OAAQuM,gBAAiB4B,OAAQ,IAAI,GAAO,MAGtFN,oBAAoBpM,OAAQjC,KAAMQ,SAS7CkP,wBAA0B,SAAS1P,MACpBkD,4BAA4BlD,MACbC,KAAKL,UAAUqQ,gDAC9BlP,YAAY,WAQ3BuN,wBAA0B,SAAStO,UAC/BqN,SAAWnK,4BAA4BlD,MACvCkQ,eAAiB7C,SAASpN,KAAKL,UAAUqQ,gDAC9B5C,SAASpN,KAAKL,UAAUuQ,sCAC9BC,KAAK,WAAW,GACzBF,eAAe3O,SAAS,WAWxB8O,gBAAkB,SAASpO,OAAQjC,KAAMQ,OAAQ8P,WAC7CA,WACArO,OAAOhC,KAAKL,UAAU2Q,sBAAsBhP,SAAS,UACrDU,OAAOhC,KAAKL,UAAU4Q,wBAAwBzP,YAAY,YAE1DkB,OAAOhC,KAAKL,UAAU2Q,sBAAsBxP,YAAY,UACxDkB,OAAOhC,KAAKL,UAAU4Q,wBAAwBjP,SAAS,YAa3DkP,kBAAoB,SAASxO,OAAQjC,KAAMQ,OAAQkQ,WAC/CC,cAAgB1O,OAAOhC,KAAKL,UAAUgR,0BACtCC,aAAe5O,OAAOhC,KAAKL,UAAUkR,0BACrCC,gBAAkB9O,OAAOhC,KAAKL,UAAUoR,mCAEpCN,WACC,OACDC,cAAcpP,SAAS,UACvBsP,aAAatP,SAAS,UACtBwP,gBAAgBxP,SAAS,oBAExB,WACDoP,cAAcpP,SAAS,UACvBsP,aAAa9P,YAAY,UACzBgQ,gBAAgBxP,SAAS,oBAExB,cACDoP,cAAc5P,YAAY,UAC1B8P,aAAatP,SAAS,UACtBwP,gBAAgBhQ,YAAY,YAcpCkQ,cAAgB,SAAShP,OAAQjC,KAAMQ,OAAQkQ,WAC3CQ,SAAWjP,OAAOhC,KAAKL,UAAUuR,sBACjCC,SAAWnP,OAAOhC,KAAKL,UAAUyR,qBACjCC,WAAarP,OAAOhC,KAAKL,UAAU2R,8BAE/Bb,WACC,OACDQ,SAAS3P,SAAS,UAClB6P,SAAS7P,SAAS,UAClB+P,WAAW/P,SAAS,oBAEnB,YACD2P,SAAS3P,SAAS,UAClB6P,SAASrQ,YAAY,UACrBuQ,WAAW/P,SAAS,oBAEnB,cACD2P,SAASnQ,YAAY,UACrBqQ,SAAS7P,SAAS,UAClB+P,WAAWvQ,YAAY,YAa/ByQ,gBAAkB,SAASvP,OAAQjC,KAAMQ,OAAQkQ,WAC7Ce,WAAaxP,OAAOhC,KAAKL,UAAU8R,4BACnCC,cAAgB1P,OAAOhC,KAAKL,UAAUgS,sCAElClB,WACC,kBACDe,WAAWlQ,SAAS,UACpBoQ,cAAcpQ,SAAS,oBAEtB,UACDkQ,WAAWlQ,SAAS,UACpBoQ,cAAc5Q,YAAY,oBAEzB,cACD0Q,WAAW1Q,YAAY,UACvB4Q,cAAcpQ,SAAS,YAa/BsQ,2BAA6B,SAAS5P,OAAQjC,KAAMQ,OAAQ0L,eACxDmB,SAAWnK,4BAA4BlD,MACvCsN,QAAUD,SAASpN,KAAK,UACxB6R,WAAazE,SAASpN,KAAKL,UAAUmS,8BACrCC,YAAc3E,SAASpN,KAAKL,UAAUyI,wBAEtC6D,WACAoB,QAAQ8C,KAAK,YAAY,GACzB0B,WAAWvQ,SAAS,UACpByQ,YAAYjR,YAAY,YAExBuM,QAAQ8C,KAAK,YAAY,GACzB0B,WAAW/Q,YAAY,UACvBiR,YAAYzQ,SAAS,YAYzB0Q,iBAAmB,SAAShQ,OAAQjC,KAAMQ,OAAQ0R,gBAC9CnO,SAAW,KAEXmO,aACAnO,SAAW/D,KAAKC,KAAKL,UAAUuS,uBACtBlS,KAAKL,UAAUwS,2BAA2BrR,YAAY,UAC/DqB,kBAAkBH,QAjoCD,SAASA,QAC9BI,kBAAkBJ,QAAQlB,YAAY,UAioClCsR,CAAmBpQ,WAEnB8B,SAAWhE,qBAAqBC,OACvBC,KAAKL,UAAUwS,2BAA2B7Q,SAAS,UAC5DwC,SAAS9D,KAAKL,UAAU0S,uBAAuB/Q,SAAS,UACxDY,kBAAkBF,QAClBM,mBAAmBN,UAYvBsQ,uBAAyB,SAAStQ,OAAQjC,KAAMQ,OAAQsE,UACpD0N,oBAAsB1N,KAAK2N,MAAQ,EAEnC3N,KAAKI,IAAIC,QACTL,KAAKI,IAAIoC,SAAQ,SAASzE,eAClBqB,QAAUtB,kBAAkB5C,KAAM6C,WACtCqB,QAAQjE,KAAKL,UAAUwS,2BAA2B7Q,SAAS,UAC3D2C,QAAQjE,KAAKL,UAAU0S,uBAAuBvR,YAAY,UAC1DmD,QAAQR,KAAK,gBAAgB,MAIjCoB,KAAKoE,OAAO/D,QACZL,KAAKoE,OAAO5B,SAAQ,SAASzE,eACrBqB,QAAUtB,kBAAkB5C,KAAM6C,WAElC2P,qBACAtO,QAAQjE,KAAKL,UAAUwS,2BAA2BrR,YAAY,UAGlEmD,QAAQjE,KAAKL,UAAU0S,uBAAuB/Q,SAAS,UACvD2C,QAAQR,KAAK,gBAAgB,MA5gCV,SAASzB,OAAQ+D,OAC5C3D,kBAAkBJ,QAAQhC,KAAKL,UAAU8S,yBAAyBhO,KAAKsB,OA+gCvE2M,CAAyB1Q,OAAQ6C,KAAK2N,QAYtCG,wBAA0B,SAAS3Q,OAAQjC,KAAMQ,OAAQsE,aACrDA,KAAKwH,OAASxH,KAAK+N,YACZvT,IAAI+K,YAAY,CACf,CACIC,IAAK,0BACLC,UAAW,eACXC,MAAO1F,KAAK2F,KAAKC,UAErB,CACIJ,IAAK,sBACLC,UAAW,eACXC,MAAO1F,KAAK2F,KAAKC,YAGxBpE,MAAK,SAASqE,aACPC,MAAQD,QAAQ,GAChBjG,KAAOiG,QAAQ,UACZmC,oBACH7K,OACAjC,KACAQ,OACA,CAACZ,UAAU8R,4BACXhN,KACAkG,OACA,GACA,MAILyD,oBAAoBpM,OAAQjC,KAAMQ,SAa7CsS,8BAAgC,SAAS7Q,OAAQjC,KAAMQ,OAAQuS,oBAC3DzP,UAAYnD,oCAAoCH,aAChD+S,eACAzP,UAAUvC,YAAY,UAEtBuC,UAAU/B,SAAS,WAEhB,GAYPyR,yBAA2B,SAAS/Q,OAAQjC,KAAMQ,OAAQwO,kBACtD1L,UAAYjD,+BAA+BL,aAC3CgP,aACO1P,IAAI+G,WAAW,4BAA6B,eAAgBoI,iBAAiBO,eAC/E1I,MAAK,SAASqI,eACXrL,UAAUrD,KAAKL,UAAUmL,MAAMrG,KAAKiK,QACpCrL,UAAUvC,YAAY,UACf4N,WAGfrL,UAAU/B,SAAS,WACZ,IAYX0R,YAAc,SAAShR,OAAQjC,KAAMQ,eACrC6N,oBAAoBpM,OAAQjC,KAAMQ,QA1hDA,SAASR,MACpCK,+BAA+BL,MAAMuB,SAAS,UA0hDrD2R,CAAgClT,MA/iDO,SAASA,MACzCG,oCAAoCH,MAAMuB,SAAS,UA+iD1D4R,CAAqCnT,MA32Cb,SAASiC,QACjCG,kBAAkBH,QAClBM,mBAAmBN,QACnBU,sBAAsBV,QAy2CtBmR,CAAsBnR,QACtBS,sBAAsBT,QACtBX,sBAAsBd,QACtBM,sBAAsBN,SACf,GAqFPiO,iBAAmB,SAASD,iBACrB7O,SAASsP,SAAST,SAAU,CAC/BrJ,OAAQ,GACR+J,OAAO,EACPC,SAAU,eAIX,CACHnI,OA3FS,SAAS/E,OAAQjC,KAAMQ,OAAQ6S,WACpCC,QAAU,CACV,CAEIC,MAAON,aAEX,CAGIO,aAAc3O,mBACd5C,OAAQoH,aACR7I,OAAQ4J,aACRqJ,iBAAkBlF,uBAClBmF,mBAAoB7E,yBACpB8E,kBAAmB5E,wBACnB6E,qBAAsBvE,2BACtBwE,8BAA+BtE,oCAC/BuE,0BAA2BlE,gCAC3BmE,sBAAuBjE,4BACvBkE,kBAAmBpB,wBACnBqB,wBAAyBnB,8BACzBoB,mBAAoBlB,0BAExB,CACImB,eAAgB1I,qBAChB2I,qBAAsBzI,2BACtB0I,gBAAiBpI,sBACjBqE,UAAWD,gBACXiE,UAAW9C,gBACX+C,YAAa9D,kBACb+D,QAASvD,cACTwD,qBAAsB5C,2BACtBK,WAAYD,iBACZyC,gBAAiBrI,sBACjBsI,sBAAuBhI,6BAE3B,CAGIiI,gBAAiBvJ,sBACjBwJ,iBAAkBtC,yBAItBuC,cAAgB,SAASC,YACrBC,QAAU,OAET,IAAI1K,OAAO+I,SACR0B,OAAOE,eAAe3K,KAAM,KACxB4K,WAAaH,OAAOzK,KACpB6K,WAAa9B,MAAM/I,KACvB0K,QAAQ9O,KAAKgP,WAAWjT,OAAQjC,KAAMQ,OAAQ2U,oBAI/CH,SAIPjQ,kBAAoB+P,cAAcxB,QAAQ,WAE9CvO,kBAAoBA,kBAAkBa,OAAOkP,cAAcxB,QAAQ,KAI5DlU,EAAEgI,KAAKC,MAAMjI,EAAG2F,mBAClBuB,MAAK,eACG,IAAI8O,EAAI,EAAGA,EAAI9B,QAAQnO,OAAQiQ,IAChCN,cAAcxB,QAAQ8B,OAK7BzN,MAAMtI,aAAagW"}PKB\5h88"build/message_drawer_routes.min.jsnu[/** * Available routes for the message drawer. * * @module core_message/message_drawer_routes * @copyright 2018 Ryan Wyllie * @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.mapPKB\H+11build/message_drawer.min.jsnu[/** * Controls the message drawer. * * @module core_message/message_drawer * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer",["jquery","core/custom_interaction_events","core/pubsub","core_message/message_drawer_view_contact","core_message/message_drawer_view_contacts","core_message/message_drawer_view_conversation","core_message/message_drawer_view_group_info","core_message/message_drawer_view_overview","core_message/message_drawer_view_search","core_message/message_drawer_view_settings","core_message/message_drawer_router","core_message/message_drawer_routes","core_message/message_drawer_events","core_message/message_drawer_helper","core/pending","core/drawer","core/toast","core/str","core/config","core/ajax","core/local/aria/focuslock","core/modal_backdrop","core/templates","core/local/aria/selectors"],(function($,CustomEvents,PubSub,ViewContact,ViewContacts,ViewConversation,ViewGroupInfo,ViewOverview,ViewSearch,ViewSettings,Router,Routes,Events,Helper,Pending,Drawer,Toast,Str,Config,Ajax,FocusLock,ModalBackdrop,Templates,AriaSelectors){var SELECTORS={DRAWER:'[data-region="right-hand-drawer"]',PANEL_BODY_CONTAINER:'[data-region="panel-body-container"]',PANEL_HEADER_CONTAINER:'[data-region="panel-header-container"]',VIEW_CONTACT:'[data-region="view-contact"]',VIEW_CONTACTS:'[data-region="view-contacts"]',VIEW_CONVERSATION:'[data-region="view-conversation"]',VIEW_CONVERSATION_WITH_ID:'[data-region="view-conversation"][data-conversation-id]',VIEW_CONVERSATION_WITH_USER:'[data-region="view-conversation"][data-other-user-id]',VIEW_GROUP_INFO:'[data-region="view-group-info"]',VIEW_OVERVIEW:'[data-region="view-overview"]',VIEW_SEARCH:'[data-region="view-search"]',VIEW_SETTINGS:'[data-region="view-settings"]',ROUTES:"[data-route]",ROUTES_BACK:"[data-route-back]",HEADER_CONTAINER:'[data-region="header-container"]',BODY_CONTAINER:'[data-region="body-container"]',FOOTER_CONTAINER:'[data-region="footer-container"]',CLOSE_BUTTON:'[data-action="closedrawer"]',MESSAGE_INDEX:'[data-region="message-index"]',MESSAGE_TEXT_AREA:'[data-region="send-message-txt"]'},routes=[[Routes.VIEW_CONTACT,SELECTORS.VIEW_CONTACT,ViewContact.show,ViewContact.description],[Routes.VIEW_CONTACTS,SELECTORS.VIEW_CONTACTS,ViewContacts.show,ViewContacts.description],[Routes.VIEW_CONVERSATION,SELECTORS.VIEW_CONVERSATION,ViewConversation.show,ViewConversation.description],[Routes.VIEW_GROUP_INFO,SELECTORS.VIEW_GROUP_INFO,ViewGroupInfo.show,ViewGroupInfo.description],[Routes.VIEW_OVERVIEW,SELECTORS.VIEW_OVERVIEW,ViewOverview.show,ViewOverview.description],[Routes.VIEW_SEARCH,SELECTORS.VIEW_SEARCH,ViewSearch.show,ViewSearch.description],[Routes.VIEW_SETTINGS,SELECTORS.VIEW_SETTINGS,ViewSettings.show,ViewSettings.description]],createRoutes=function(namespace,root){routes.forEach((function(route){Router.add(namespace,route[0],function(namespace,root,selector){var header=root.find(SELECTORS.HEADER_CONTAINER).find(selector);header.length||(header=root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector));var body=root.find(SELECTORS.BODY_CONTAINER).find(selector);body.length||(body=root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector));var footer=root.find(SELECTORS.FOOTER_CONTAINER).find(selector);return[namespace,header.length?header:null,body.length?body:null,footer.length?footer:null]}(namespace,root,route[1]),route[2],route[3])}))};let backdropPromise=null;var setFocus=function(root,hasFocus){var drawerRoot=Drawer.getDrawerRoot(root);if(!drawerRoot.length)return;backdropPromise||(backdropPromise=Templates.render("core/modal_backdrop",{}).then((html=>new ModalBackdrop(html))));const backdropWithAdjustments=backdropPromise.then((modalBackdrop=>{const messageDrawerZIndex=window.getComputedStyle(drawerRoot[0]).zIndex;return messageDrawerZIndex&&modalBackdrop.setZIndex(messageDrawerZIndex-1),modalBackdrop.getAttachmentPoint().get(0).addEventListener("click",(e=>{PubSub.publish(Events.HIDE,{}),e.preventDefault()})),modalBackdrop}));hasFocus?(FocusLock.trapFocus(root[0]),backdropWithAdjustments.then((modalBackdrop=>{if(modalBackdrop){modalBackdrop.show();document.getElementById("page").style.overflow="hidden";var closeButton=root.find(SELECTORS.CLOSE_BUTTON);closeButton.length&&closeButton.focus()}return modalBackdrop}))):backdropWithAdjustments.then((modalBackdrop=>{if(modalBackdrop){FocusLock.untrapFocus();var button=$(SELECTORS.DRAWER).attr("data-origin");button&&$("#"+button).focus(),modalBackdrop.hide();document.getElementById("page").style.overflow="visible"}return modalBackdrop}))},show=function(namespace,root){root.attr("data-shown")||(Router.go(namespace,Routes.VIEW_OVERVIEW),root.attr("data-shown",!0));var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&(setFocus(root,!0),Drawer.show(drawerRoot))},hide=function(root){var drawerRoot=Drawer.getDrawerRoot(root);drawerRoot.length&&(setFocus(root,!1),Drawer.hide(drawerRoot))},isVisible=function(root){var drawerRoot=Drawer.getDrawerRoot(root);return!drawerRoot.length||Drawer.isVisible(drawerRoot)},setJumpFrom=function(buttonid){$(SELECTORS.DRAWER).attr("data-origin",buttonid)};const storeUnsentMessage=async()=>{const messageTextArea=document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);if(messageTextArea.value.trim().length>0&&!messageTextArea.hasAttribute("data-unsent-message-viewed")){let message=messageTextArea.value,conversationid=0,otheruserid=0;if(document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID)){conversationid=messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID).getAttribute("data-conversation-id")}if(document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER)){otheruserid=messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER).getAttribute("data-other-user-id")}setStoredUnsentMessage(message,conversationid,otheruserid)}},setStoredUnsentMessage=(message,conversationid,otheruserid)=>{const method="core_message_set_unsent_message",requestUrl=new URL("".concat(Config.wwwroot,"/lib/ajax/service.php"));requestUrl.searchParams.set("sesskey",Config.sesskey),requestUrl.searchParams.set("info",method),navigator.sendBeacon(requestUrl,JSON.stringify([{index:0,methodname:method,args:{message:message,conversationid:conversationid,otheruserid:otheruserid}}]))},getUnsentMessage=async(uniqueId,root)=>{let type,messageRoot;if(null!==document.querySelector(SELECTORS.MESSAGE_INDEX)){if(type="index",messageRoot=document.getElementById("message-index-".concat(uniqueId)),!messageRoot)return}else type="drawer",messageRoot=document.getElementById("message-drawer-".concat(uniqueId));const storedMessage=await Ajax.call([{methodname:"core_message_get_unsent_message",args:{}}])[0],messageTextArea=messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);storedMessage.message&&null!==messageTextArea&&showUnsentMessage(messageTextArea,storedMessage,type,uniqueId,root)},showUnsentMessage=(textArea,stored,type,uniqueId,root)=>{if(!textArea.hasAttribute("data-unsent-message-viewed")){switch(type){case"index":stored.conversationid?Router.go(uniqueId,Routes.VIEW_CONVERSATION,stored.conversationid,"frompanel"):stored.otheruserid&&Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid);break;case"drawer":if(stored.conversationid){let args={conversationid:stored.conversationid};Helper.showConversation(args)}else stored.otheruserid&&(show(uniqueId,root),Router.go(uniqueId,Routes.VIEW_CONVERSATION,null,"create",stored.otheruserid))}textArea.value=stored.message,textArea.setAttribute("data-unsent-message-viewed",1),Toast.add(Str.get_string("unsentmessagenotification","core_message"))}};return{init:function(root,uniqueId,alwaysVisible,route){if(root=$(root),createRoutes(uniqueId,root),function(namespace,root,alwaysVisible){CustomEvents.define(root,[CustomEvents.events.activate,CustomEvents.events.escape]);var paramRegex=/^data-route-param-?(\d*)$/;root.on(CustomEvents.events.activate,SELECTORS.ROUTES,(function(e,data){for(var element=$(e.target).closest(SELECTORS.ROUTES),route=element.attr("data-route"),attributes=[],i=0;i1?aParts[1]:0,bIndex=bParts.length>1?bParts[1]:0;return aIndex{var _$,drawer=$(e.target).closest(SELECTORS.DRAWER),toggleButtonId=null===(_$=$(SELECTORS.DRAWER))||void 0===_$?void 0:_$.attr("data-origin"),toggleButton="";if(void 0!==toggleButtonId&&toggleButtonId&&(toggleButton=$(e.target).closest("#"+toggleButtonId)),!drawer.length&&!toggleButton.length&&isVisible(root)){var focusableElement=$(e.target).closest(AriaSelectors.elements.focusable);focusableElement.length&&$(SELECTORS.DRAWER).attr("data-origin",""),hide(root),focusableElement.length&&focusableElement.focus()}})),root[0].querySelectorAll(".collapse").forEach((collapse=>{collapse.addEventListener("hide.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("hidden.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),root[0].querySelectorAll(".collapse").forEach((collapse=>{collapse.addEventListener("show.bs.collapse",(e=>{var pendingPromise=new Pending;e.target.addEventListener("shown.bs.collapse",(function(){pendingPromise.resolve()}),{once:!0})}))})),$(SELECTORS.JUMPTO).focus((function(){var firstInput=root.find(SELECTORS.CLOSE_BUTTON);firstInput.length?firstInput.focus():$(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus()})),$(SELECTORS.DRAWER).focus((function(){var button=$(this).attr("data-origin");button&&$("#"+button).focus()})),alwaysVisible||(PubSub.subscribe(Events.SHOW,(function(){show(namespace,root)})),PubSub.subscribe(Events.HIDE,(function(){hide(root)})),PubSub.subscribe(Events.TOGGLE_VISIBILITY,(function(buttonid){const buttonElement=document.getElementById(buttonid);isVisible(root)?(hide(root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!1),$(SELECTORS.JUMPTO).attr("tabindex",-1)):(show(namespace,root),null==buttonElement||buttonElement.setAttribute("aria-expanded",!0),setJumpFrom(buttonid),$(SELECTORS.JUMPTO).attr("tabindex",0))})),root.on(CustomEvents.events.escape,(function(){PubSub.publish(Events.HIDE,{})}))),PubSub.subscribe(Events.SHOW_CONVERSATION,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,args.conversationid)})),root.find(SELECTORS.CLOSE_BUTTON).on(CustomEvents.events.activate,(function(e,data){data.originalEvent.preventDefault(),setFocus(root,!1);var button=$(SELECTORS.DRAWER).attr("data-origin");button&&$("#"+button).focus(),PubSub.publish(Events.TOGGLE_VISIBILITY,button)})),PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER,(function(args){setJumpFrom(args.buttonid),show(namespace,root),Router.go(namespace,Routes.VIEW_CONVERSATION,null,"create",args.userid)})),PubSub.subscribe(Events.SHOW_SETTINGS,(function(){show(namespace,root),Router.go(namespace,Routes.VIEW_SETTINGS)})),PubSub.subscribe(Events.PREFERENCES_UPDATED,(function(preferences){var filteredPreferences=preferences.filter((function(preference){return"message_entertosend"==preference.type})),enterToSendPreference=filteredPreferences.length?filteredPreferences[0]:null;enterToSendPreference&&root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION).attr("data-enter-to-send",enterToSendPreference.value)}));const textArea=document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);textArea&&textArea.addEventListener("keyup",(function(){textArea.removeAttribute("data-unsent-message-viewed")})),window.addEventListener("beforeunload",storeUnsentMessage)}(uniqueId,root,alwaysVisible),alwaysVisible&&(show(uniqueId,root),route)){var routeParams=route.params||[];routeParams=[uniqueId,route.path].concat(routeParams),Router.go.apply(null,routeParams)}Helper.markDrawerReady(),getUnsentMessage(uniqueId,root)}}})); //# sourceMappingURL=message_drawer.min.js.mapPKB\+G G build/message_send_bulk.min.jsnu[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 * @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.mapPKB\#-build/default_notification_preferences.min.jsnu[define("core_message/default_notification_preferences",["exports","core/ajax","core/notification","core/toast"],(function(_exports,Ajax,_notification,_toast){var obj;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)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Ajax=function(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]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj} /** * 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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */(Ajax),_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};const selectors_provider=".defaultmessageoutputs .provider_enabled",selectors_lockSetting=".locked_message_setting",selectors_enabledSetting=".enabled_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.form-check").classList.toggle("dimmed_text",isEnabled)},toggleEnableProviderSettings=providerEnabledElement=>{const isEnabled=providerEnabledElement.checked||!1;providerEnabledElement.closest("tr").querySelectorAll(selectors_allSettings).forEach((element=>{element.toggleAttribute("disabled",!isEnabled)}))},setDefaultNotification=(isEnabled,preference)=>{Ajax.call([{methodname:"core_message_set_default_notification",args:{preference:preference,state:isEnabled?1:0}}])[0].then((data=>(0,_toast.add)(data.successmessage))).catch(_notification.default.exception)},container=document.querySelector(".preferences-container");container.querySelectorAll(selectors_provider).forEach((providerEnabledElement=>{providerEnabledElement.checked||toggleEnableProviderSettings(providerEnabledElement),providerEnabledElement.addEventListener("change",(e=>{toggleEnableProviderSettings(e.target),setDefaultNotification(e.target.checked,providerEnabledElement.parentElement.parentElement.dataset.preference)}))})),container.querySelectorAll(selectors_lockSetting).forEach((lockedElement=>{lockedElement.checked&&toggleLockSetting(lockedElement),lockedElement.addEventListener("change",(e=>{toggleLockSetting(e.target),setDefaultNotification(e.target.checked,lockedElement.parentElement.parentElement.dataset.preference)}))})),container.querySelectorAll(selectors_enabledSetting).forEach((enabledElement=>{enabledElement.addEventListener("change",(e=>{setDefaultNotification(e.target.checked,enabledElement.parentElement.parentElement.dataset.preference)}))}))})()}};return _exports.default=_default,_exports.default})); //# sourceMappingURL=default_notification_preferences.min.js.mapPKB\ΞR8R8&build/message_drawer_router.min.js.mapnu[{"version":3,"file":"message_drawer_router.min.js","sources":["../src/message_drawer_router.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A simple router for the message drawer that allows navigating between\n * the \"pages\" in the drawer.\n *\n * This module will maintain a linear history of the unique pages access\n * to allow navigating back.\n *\n * @module core_message/message_drawer_router\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_drawer_events',\n 'core/aria',\n 'core/pending',\n],\nfunction(\n $,\n PubSub,\n Str,\n MessageDrawerEvents,\n Aria,\n PendingPromise,\n) {\n\n /* @var {object} routes Message drawer route elements and callbacks. */\n var routes = {};\n\n /* @var {object} history Store for route objects history. */\n var history = {};\n\n var SELECTORS = {\n CAN_RECEIVE_FOCUS: 'input:not([type=\"hidden\"]), a[href], button, textarea, select, [tabindex]',\n ROUTES_BACK: '[data-route-back]'\n };\n\n /**\n * Add a route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} route Route config name.\n * @param {array} parameters Route parameters.\n * @param {callback} onGo Route initialization function.\n * @param {callback} getDescription Route initialization function.\n */\n var add = function(namespace, route, parameters, onGo, getDescription) {\n if (!routes[namespace]) {\n routes[namespace] = [];\n }\n\n routes[namespace][route] =\n {\n parameters: parameters,\n onGo: onGo,\n getDescription: getDescription\n };\n };\n\n /**\n * Go to a defined route and run the route callbacks.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {string} newRoute Route config name.\n * @return {object} record Current route record with route config name and parameters.\n */\n var changeRoute = function(namespace, newRoute) {\n var newConfig;\n var pendingPromise = new PendingPromise(`message-drawer-router-${namespace}-${newRoute}`);\n\n // Check if the Route change call is made from an element in the app panel.\n var fromPanel = [].slice.call(arguments).some(function(arg) {\n return arg == 'frompanel';\n });\n // Get the rest of the arguments, if any.\n var args = [].slice.call(arguments, 2);\n var renderPromise = $.Deferred().resolve().promise();\n\n Object.keys(routes[namespace]).forEach(function(route) {\n var config = routes[namespace][route];\n var isMatch = route === newRoute;\n\n if (isMatch) {\n newConfig = config;\n }\n\n config.parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n return;\n }\n\n element.removeClass('previous');\n element.attr('data-from-panel', false);\n\n if (isMatch) {\n if (fromPanel) {\n // Set this attribute to let the conversation renderer know not to show a back button.\n element.attr('data-from-panel', true);\n }\n element.removeClass('hidden');\n Aria.unhide(element.get());\n } else {\n // For the message index page elements in the left panel should not be hidden.\n if (!element.attr('data-in-panel')) {\n element.addClass('hidden');\n Aria.hide(element.get());\n } else if (newRoute == 'view-search' || newRoute == 'view-overview') {\n element.addClass('hidden');\n Aria.hide(element.get());\n }\n }\n });\n });\n\n if (newConfig) {\n if (newConfig.onGo) {\n renderPromise = newConfig.onGo.apply(undefined, newConfig.parameters.concat(args));\n var currentFocusElement = $(document.activeElement);\n var hasFocus = false;\n var firstFocusable = null;\n\n // No need to start at 0 as we know that is the namespace.\n for (var i = 1; i < newConfig.parameters.length; i++) {\n var element = newConfig.parameters[i];\n\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || element === null) {\n continue;\n }\n\n if (!firstFocusable) {\n firstFocusable = element;\n }\n\n if (element.has(currentFocusElement).length) {\n hasFocus = true;\n break;\n }\n }\n\n if (!hasFocus) {\n // This page doesn't have focus yet so focus the first focusable\n // element in the new view.\n firstFocusable.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();\n }\n }\n }\n\n var record = {\n route: newRoute,\n params: args,\n renderPromise: renderPromise\n };\n\n PubSub.publish(MessageDrawerEvents.ROUTE_CHANGED, record);\n\n renderPromise.then(() => pendingPromise.resolve());\n return record;\n };\n\n /**\n * Go to a defined route and store the route history.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {object} record Current route record with route config name and parameters.\n */\n var go = function(namespace) {\n var currentFocusElement = $(document.activeElement);\n\n var record = changeRoute.apply(namespace, arguments);\n var inHistory = false;\n\n if (!history[namespace]) {\n history[namespace] = [];\n }\n\n // History stores a unique list of routes. Check to see if the new route\n // is already in the history, if it is then forget all history after it.\n // This ensures there are no duplicate routes in history and that it represents\n // a linear path of routes (it never stores something like [foo, bar, foo])).\n history[namespace] = history[namespace].reduce(function(carry, previous) {\n if (previous.route === record.route) {\n inHistory = true;\n }\n\n if (!inHistory) {\n carry.push(previous);\n }\n\n return carry;\n }, []);\n\n var historylength = history[namespace].length;\n var previousRecord = historylength ? history[namespace][historylength - 1] : null;\n\n if (previousRecord) {\n var prevConfig = routes[namespace][previousRecord.route];\n var elements = prevConfig.parameters;\n\n // The first one will be the namespace, skip it.\n for (var i = 1; i < elements.length; i++) {\n // Some parameters may be null, or not an element.\n if (typeof elements[i] !== 'object' || elements[i] === null) {\n continue;\n }\n\n elements[i].addClass('previous');\n }\n\n previousRecord.focusElement = currentFocusElement;\n\n if (prevConfig.getDescription) {\n // If the route has a description then set it on the back button for\n // the new page we're displaying.\n prevConfig.getDescription.apply(null, prevConfig.parameters.concat(previousRecord.params))\n .then(function(description) {\n return Str.get_string('backto', 'core_message', description);\n })\n .then(function(label) {\n // Wait for the new page to finish rendering so that we know\n // that the back button is visible.\n return record.renderPromise.then(function() {\n // Find the elements for the new route we displayed.\n routes[namespace][record.route].parameters.forEach(function(element) {\n // Some parameters may be null, or not an element.\n if (typeof element !== 'object' || !element) {\n return;\n }\n // Update the aria label for the back button.\n element.find(SELECTORS.ROUTES_BACK).attr('aria-label', label);\n });\n });\n })\n .catch(function() {\n // Silently ignore.\n });\n }\n }\n history[namespace].push(record);\n return record;\n };\n\n /**\n * Go back to the previous route record stored in history.\n *\n * @param {String} namespace Unique identifier for the Routes\n */\n var back = function(namespace) {\n if (history[namespace].length) {\n // Remove the current route.\n history[namespace].pop();\n var previous = history[namespace].pop();\n\n if (previous) {\n // If we have a previous route then show it.\n go.apply(undefined, [namespace, previous.route].concat(previous.params));\n // Delay the focus 50 milliseconds otherwise it doesn't correctly\n // focus the element for some reason...\n window.setTimeout(function() {\n previous.focusElement.focus();\n }, 50);\n }\n }\n };\n\n return {\n add: add,\n go: go,\n back: back\n };\n});\n"],"names":["define","$","PubSub","Str","MessageDrawerEvents","Aria","PendingPromise","routes","history","SELECTORS","changeRoute","namespace","newRoute","newConfig","pendingPromise","fromPanel","slice","call","arguments","some","arg","args","renderPromise","Deferred","resolve","promise","Object","keys","forEach","route","config","isMatch","parameters","element","removeClass","attr","unhide","get","addClass","hide","onGo","apply","undefined","concat","currentFocusElement","document","activeElement","hasFocus","firstFocusable","i","length","has","find","filter","first","focus","record","params","publish","ROUTE_CHANGED","then","go","inHistory","reduce","carry","previous","push","historylength","previousRecord","prevConfig","elements","focusElement","getDescription","description","get_string","label","catch","add","back","pop","window","setTimeout"],"mappings":"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"}PKB\pq "build/message_drawer_events.min.jsnu[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.mapPKB\c?)build/message_drawer_view_settings.min.jsnu[/** * Controls the settings page in the message drawer. * * @module core_message/message_drawer_view_settings * @copyright 2018 Ryan Wyllie * @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.mapPKB\b5build/message_drawer_view_overview_section.min.js.mapnu[{"version":3,"file":"message_drawer_view_overview_section.min.js","sources":["../src/message_drawer_view_overview_section.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls a section of the overview page in the message drawer.\n *\n * @module core_message/message_drawer_view_overview_section\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pubsub',\n 'core/str',\n 'core/pending',\n 'core/templates',\n 'core/user_date',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_lazy_load_list',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n CustomEvents,\n Notification,\n PubSub,\n Str,\n Pending,\n Templates,\n UserDate,\n MessageRepository,\n MessageDrawerEvents,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n LazyLoadList,\n MessageDrawerViewConversationContants\n) {\n\n var SELECTORS = {\n TOGGLE: '[data-region=\"toggle\"]',\n CONVERSATION: '[data-conversation-id]',\n BLOCKED_ICON_CONTAINER: '[data-region=\"contact-icon-blocked\"]',\n LAST_MESSAGE: '[data-region=\"last-message\"]',\n LAST_MESSAGE_DATE: '[data-region=\"last-message-date\"]',\n MUTED_ICON_CONTAINER: '[data-region=\"muted-icon-container\"]',\n UNREAD_COUNT: '[data-region=\"unread-count\"]',\n SECTION_TOTAL_COUNT: '[data-region=\"section-total-count\"]',\n SECTION_TOTAL_COUNT_CONTAINER: '[data-region=\"section-total-count-container\"]',\n SECTION_UNREAD_COUNT: '[data-region=\"section-unread-count\"]',\n SECTION_UNREAD_COUNT_CONTAINER: '[data-region=\"section-unread-count-container\"]',\n PLACEHOLDER_CONTAINER: '[data-region=\"placeholder-container\"]'\n };\n\n var TEMPLATES = {\n CONVERSATIONS_LIST: 'core_message/message_drawer_conversations_list',\n CONVERSATIONS_LIST_ITEMS_PLACEHOLDER: 'core_message/message_drawer_conversations_list_items_placeholder'\n };\n\n var LOAD_LIMIT = 50;\n var loadedConversationsById = {};\n var deletedConversationsById = {};\n var loadedTotalCounts = false;\n var loadedUnreadCounts = false;\n\n /**\n * Get the section visibility status.\n *\n * @param {Object} root The section container element.\n * @return {Bool} Is section visible.\n */\n var isVisible = function(root) {\n return LazyLoadList.getRoot(root).hasClass('show');\n };\n\n /**\n * Set this section as expanded.\n *\n * @param {Object} root The section container element.\n */\n var setExpanded = function(root) {\n root.addClass('expanded');\n };\n\n /**\n * Set this section as collapsed.\n *\n * @param {Object} root The section container element.\n */\n var setCollapsed = function(root) {\n root.removeClass('expanded');\n };\n\n /**\n * Render the total count value and show it for the user. Also update the placeholder\n * HTML for better visuals.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The total count\n */\n var renderTotalCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_TOTAL_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_TOTAL_COUNT);\n countElement.text(count);\n container.removeClass('hidden');\n Str.get_string('totalconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n var numPlaceholders = count > 20 ? 20 : count;\n // Array of \"true\" up to the number of placeholders we want.\n var placeholders = Array.apply(null, Array(numPlaceholders)).map(function() {\n return true;\n });\n\n // Replace the current placeholder (loading spinner) with some nicer placeholders that\n // better represent the content.\n Templates.render(TEMPLATES.CONVERSATIONS_LIST_ITEMS_PLACEHOLDER, {placeholders: placeholders})\n .then(function(html) {\n var placeholderContainer = root.find(SELECTORS.PLACEHOLDER_CONTAINER);\n placeholderContainer.html(html);\n return;\n })\n .catch(function() {\n // Silently ignore. Doesn't matter if we can't render the placeholders.\n });\n };\n\n /**\n * Render the unread count value and show it for the user if it's higher than zero.\n *\n * @param {Object} root The section container element.\n * @param {Number} count The unread count\n */\n var renderUnreadCount = function(root, count) {\n var container = root.find(SELECTORS.SECTION_UNREAD_COUNT_CONTAINER);\n var countElement = container.find(SELECTORS.SECTION_UNREAD_COUNT);\n countElement.text(count);\n\n Str.get_string('unreadconversations', 'core_message', count).done(function(string) {\n $('#' + container.attr('aria-labelledby')).text(string);\n });\n\n if (count > 0) {\n container.removeClass('hidden');\n } else {\n container.addClass('hidden');\n }\n };\n\n /**\n * Create a formatted conversation object from the the one we get from events. The new object\n * will be in a format that matches what we receive from the server.\n *\n * @param {Object} conversation\n * @return {Object} formatted conversation.\n */\n var formatConversationFromEvent = function(conversation) {\n // Recursively lowercase all of the keys for an object.\n var recursivelyLowercaseKeys = function(object) {\n return Object.keys(object).reduce(function(carry, key) {\n if ($.isArray(object[key])) {\n carry[key.toLowerCase()] = object[key].map(recursivelyLowercaseKeys);\n } else {\n carry[key.toLowerCase()] = object[key];\n }\n\n return carry;\n }, {});\n };\n\n // Recursively lowercase all of the keys for the conversation.\n var formatted = recursivelyLowercaseKeys(conversation);\n\n // Make sure all messages have the useridfrom property set.\n formatted.messages = formatted.messages.map(function(message) {\n message.useridfrom = message.userfrom.id;\n return message;\n });\n\n return formatted;\n };\n\n /**\n * Render the messages in the overview page.\n *\n * @param {Array} conversations List of conversations to render.\n * @param {Number} userId Logged in user id.\n * @return {Object} jQuery promise.\n */\n var render = function(conversations, userId) {\n\n // Helper to format the last message for rendering.\n // Returns a promise which resolves to either a string, or null\n // (such as in the event of an empty personal space).\n var pending = new Pending();\n\n var formatMessagePreview = async function(lastMessage) {\n if (!lastMessage) {\n return null;\n }\n // Check the message html for a src attribute, indicative of media.\n // Replace LOAD_LIMIT) {\n conversations = conversations.slice(0, -1);\n } else {\n LazyLoadList.setLoadedAll(root, true);\n }\n\n offset = offset + LOAD_LIMIT;\n\n conversations.forEach(function(conversation) {\n loadedConversationsById[conversation.id] = conversation;\n });\n\n return conversations;\n })\n .catch(Notification.exception);\n };\n };\n\n /**\n * Get the total count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Total count container element.\n */\n var getTotalConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_TOTAL_COUNT);\n };\n\n /**\n * Get the unread conversations count container element.\n *\n * @param {Object} root Overview messages container element.\n * @return {Object} Unread conversations count container element.\n */\n var getTotalUnreadConversationCountElement = function(root) {\n return root.find(SELECTORS.SECTION_UNREAD_COUNT);\n };\n\n /**\n * Increment the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var incrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count + 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalConversationCount = function(root) {\n if (loadedTotalCounts) {\n var element = getTotalConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n element.text(count);\n }\n };\n\n /**\n * Decrement the total unread conversations count.\n *\n * @param {Object} root Overview messages container element.\n */\n var decrementTotalUnreadConversationCount = function(root) {\n if (loadedUnreadCounts) {\n var element = getTotalUnreadConversationCountElement(root);\n var count = parseInt(element.text());\n count = count - 1;\n // Re-render the unread count.\n renderUnreadCount(root, count);\n }\n };\n\n /**\n * Get a contact / conversation element.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} conversationId The conversation id.\n * @return {Object} Conversation element.\n */\n var getConversationElement = function(root, conversationId) {\n return root.find('[data-conversation-id=\"' + conversationId + '\"]');\n };\n\n /**\n * Get a contact / conversation element from a user id.\n *\n * @param {Object} root Overview messages container element.\n * @param {Number} userId The user id.\n * @return {Object} Conversation element.\n */\n var getConversationElementFromUserId = function(root, userId) {\n return root.find('[data-user-id=\"' + userId + '\"]');\n };\n\n /**\n * Show the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var muteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the conversation is muted icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unmuteConversation = function(conversationElement) {\n conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Show the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var blockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).removeClass('hidden');\n };\n\n /**\n * Hide the contact is blocked icon.\n *\n * @param {Object} conversationElement The conversation element.\n */\n var unblockContact = function(conversationElement) {\n conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).addClass('hidden');\n };\n\n /**\n * Create an render new conversation element in the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversation The conversation.\n * @param {Number} userId The logged in user id.\n * @return {Object} jQuery promise\n */\n var createNewConversationFromEvent = function(root, conversation, userId) {\n var existingConversations = root.find(SELECTORS.CONVERSATION);\n\n if (!existingConversations.length) {\n // If we didn't have any conversations then we need to show\n // the content of the list and hide the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.showContent(listRoot);\n LazyLoadList.hideEmptyMessage(listRoot);\n }\n\n // Cache the conversation.\n loadedConversationsById[conversation.id] = conversation;\n\n return render([conversation], userId)\n .then(function(html) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n return contentContainer.prepend(html);\n })\n .then(function() {\n return incrementTotalConversationCount(root);\n })\n .catch(Notification.exception);\n };\n\n /**\n * Delete a conversation from the list of conversations.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var deleteConversation = function(root, conversationElement) {\n conversationElement.remove();\n decrementTotalConversationCount(root);\n\n var conversations = root.find(SELECTORS.CONVERSATION);\n if (!conversations.length) {\n // If we don't have any conversations then we need to hide\n // the content of the list and show the empty message.\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.hideContent(listRoot);\n LazyLoadList.showEmptyMessage(listRoot);\n }\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Object} root Overview messages container element.\n * @param {Object} conversationElement The conversation element.\n */\n var markConversationAsRead = function(root, conversationElement) {\n var unreadCount = conversationElement.find(SELECTORS.UNREAD_COUNT);\n unreadCount.text('0');\n unreadCount.addClass('hidden');\n decrementTotalUnreadConversationCount(root);\n };\n\n /**\n * Listen to, and handle events in this section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The section container element.\n * @param {Function} loadCallback The callback to load items.\n * @param {Array|null} types The conversation types for this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {String} fromPanel Routing argument to send if the section is loaded in message index left panel.\n */\n var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites, fromPanel) {\n var listRoot = LazyLoadList.getRoot(root);\n var conversationBelongsToThisSection = function(conversation) {\n // Make sure the type is an int so that the index of check matches correctly.\n var conversationType = parseInt(conversation.type, 10);\n if (\n // If the conversation type isn't one this section cares about then we can ignore it.\n (types && types.indexOf(conversationType) < 0) ||\n // If this is the favourites section and the conversation isn't a favourite then ignore it.\n (includeFavourites && !conversation.isFavourite) ||\n // If this section doesn't include favourites and the conversation is a favourite then ignore it.\n (!includeFavourites && conversation.isFavourite)\n ) {\n return false;\n }\n\n return true;\n };\n\n\n root[0].addEventListener('show.bs.collapse', function() {\n setExpanded(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n });\n\n root[0].addEventListener('hidden.bs.collapse', function() {\n setCollapsed(root);\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_BLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n if (conversationElement.length) {\n blockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONTACT_UNBLOCKED, function(userId) {\n var conversationElement = getConversationElementFromUserId(root, userId);\n\n if (conversationElement.length) {\n unblockContact(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n muteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_MUTED, function(conversation) {\n var conversationId = conversation.id;\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n unmuteConversation(conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {\n if (!conversationBelongsToThisSection(conversation)) {\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_overview_section:new');\n var loggedInUserId = conversation.loggedInUserId;\n var conversationId = conversation.id;\n var element = getConversationElement(root, conversationId);\n conversation = formatConversationFromEvent(conversation);\n if (element.length) {\n var contentContainer = LazyLoadList.getContentContainer(root);\n render([conversation], loggedInUserId)\n .then(function(html) {\n if (deletedConversationsById[conversationId]) {\n // This conversation was deleted at some point since the messaging drawer was created.\n if (conversation.messages[0].timeadded < deletedConversationsById[conversationId]) {\n // The 'new' message was added before the conversation was deleted.\n // This is probably stale data.\n return;\n }\n }\n contentContainer.prepend(html);\n element.remove();\n\n return;\n })\n .then(pendingPromise.resolve)\n .catch(Notification.exception);\n } else if (conversation.messages.length) {\n createNewConversationFromEvent(root, conversation, loggedInUserId)\n .then(pendingPromise.resolve)\n .catch();\n } else {\n pendingPromise.resolve();\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_DELETED, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n delete loadedConversationsById[conversationId];\n deletedConversationsById[conversationId] = new Date();\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, function(conversationId) {\n var conversationElement = getConversationElement(root, conversationId);\n if (conversationElement.length) {\n markConversationAsRead(root, conversationElement);\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {\n var conversationElement = null;\n if (conversationBelongsToThisSection(conversation)) {\n conversationElement = getConversationElement(root, conversation.id);\n if (!conversationElement.length) {\n createNewConversationFromEvent(\n root,\n formatConversationFromEvent(conversation),\n conversation.loggedInUserId\n );\n }\n } else {\n conversationElement = getConversationElement(root, conversation.id);\n if (conversationElement.length) {\n deleteConversation(root, conversationElement);\n }\n }\n });\n\n CustomEvents.define(root, [CustomEvents.events.activate]);\n root.on(CustomEvents.events.activate, SELECTORS.CONVERSATION, function(e, data) {\n var conversationElement = $(e.target).closest(SELECTORS.CONVERSATION);\n var conversationId = conversationElement.attr('data-conversation-id');\n var conversation = loadedConversationsById[conversationId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONVERSATION, conversation, fromPanel);\n\n data.originalEvent.preventDefault();\n });\n };\n\n /**\n * Setup the section.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} header The header container element.\n * @param {Object} body The section container element.\n * @param {Object} footer The footer container element.\n * @param {Array} types The conversation types that show in this section\n * @param {bool} includeFavourites If this section includes favourites\n * @param {Object} totalCountPromise Resolves wth the total conversations count\n * @param {Object} unreadCountPromise Resolves wth the unread conversations count\n * @param {bool} fromPanel shown in message app panel.\n */\n var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise,\n fromPanel) {\n var root = $(body);\n\n if (!root.attr('data-init')) {\n var loadCallback = getLoadCallback(types, includeFavourites, 0);\n registerEventListeners(namespace, root, loadCallback, types, includeFavourites, fromPanel);\n\n if (isVisible(root)) {\n setExpanded(root);\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {\n return render(conversations, userId)\n .then(function(html) {\n contentContainer.append(html);\n return html;\n })\n .catch(Notification.exception);\n });\n }\n\n // This is given to us by the calling code because the total counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n totalCountPromise.then(function(count) {\n renderTotalCount(root, count);\n loadedTotalCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n // This is given to us by the calling code because the unread counts for all sections\n // are loaded in a single ajax request rather than one request per section.\n unreadCountPromise.then(function(count) {\n renderUnreadCount(root, count);\n loadedUnreadCounts = true;\n return;\n })\n .catch(function() {\n // Silently ignore if we can't updated the counts. No need to bother the user.\n });\n\n root.attr('data-init', true);\n }\n };\n\n return {\n show: show,\n isVisible: isVisible\n };\n});\n"],"names":["define","$","CustomEvents","Notification","PubSub","Str","Pending","Templates","UserDate","MessageRepository","MessageDrawerEvents","MessageDrawerRouter","MessageDrawerRoutes","LazyLoadList","MessageDrawerViewConversationContants","SELECTORS","TEMPLATES","loadedConversationsById","deletedConversationsById","loadedTotalCounts","loadedUnreadCounts","isVisible","root","getRoot","hasClass","setExpanded","addClass","renderUnreadCount","count","container","find","text","get_string","done","string","attr","removeClass","formatConversationFromEvent","conversation","recursivelyLowercaseKeys","object","Object","keys","reduce","carry","key","isArray","toLowerCase","map","formatted","messages","message","useridfrom","userfrom","id","render","conversations","userId","pending","mapPromises","lastMessage","length","async","tmpElement","document","createElement","innerHTML","replace","querySelector","messagePreview","indexOf","pix","label","includes","labelString","renderPix","error","exception","formatMessagePreview","then","formattedConversation","imageurl","name","subname","unreadcount","ismuted","lastmessagedate","timecreated","sentfromcurrentuser","lastmessage","otherUser","type","CONVERSATION_TYPES","SELF","members","PRIVATE","member","userid","showonlinestatus","isonline","isblocked","PUBLIC","lastsendername","fullname","catch","Promise","all","formattedConversations","forEach","Date","toDateString","istoday","html","js","resolve","Deferred","getTotalConversationCountElement","decrementTotalUnreadConversationCount","element","getTotalUnreadConversationCountElement","parseInt","getConversationElement","conversationId","getConversationElementFromUserId","createNewConversationFromEvent","listRoot","showContent","hideEmptyMessage","getContentContainer","prepend","incrementTotalConversationCount","deleteConversation","conversationElement","remove","decrementTotalConversationCount","hideContent","showEmptyMessage","registerEventListeners","namespace","loadCallback","types","includeFavourites","fromPanel","conversationBelongsToThisSection","conversationType","isFavourite","addEventListener","show","contentContainer","append","setCollapsed","subscribe","CONTACT_BLOCKED","blockContact","CONTACT_UNBLOCKED","unblockContact","CONVERSATION_SET_MUTED","muteConversation","CONVERSATION_UNSET_MUTED","unmuteConversation","CONVERSATION_NEW_LAST_MESSAGE","pendingPromise","loggedInUserId","timeadded","CONVERSATION_DELETED","CONVERSATION_READ","unreadCount","markConversationAsRead","CONVERSATION_SET_FAVOURITE","CONVERSATION_UNSET_FAVOURITE","events","activate","on","e","data","target","closest","go","VIEW_CONVERSATION","originalEvent","preventDefault","header","body","footer","totalCountPromise","unreadCountPromise","offset","includeSelfConversations","nonSelfConversationTypes","filter","candidate","getConversations","LOAD_LIMIT","response","slice","setLoadedAll","getLoadCallback","numPlaceholders","placeholders","Array","apply","renderTotalCount"],"mappings":"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"}PKB\x1 &build/message_drawer_helper.min.js.mapnu[{"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 .\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 \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}\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"}PKB\_,,;build/message_drawer_view_conversation_state_manager.min.jsnu[/** * 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 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_conversation_state_manager",["jquery"],(function($){var cloneState=function(state){return $.extend(!0,{},state)},formatMessages=function(messages,loggedInUserId,members){return messages.map((function(message){var fromLoggedInUser=message.useridfrom==loggedInUserId;return{id:""+message.id,fromLoggedInUser:fromLoggedInUser,userFrom:members[message.useridfrom],text:message.text,timeCreated:message.timecreated?parseInt(message.timecreated,10):null}}))};return{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:!1,deleteMessagesForAllUsers:!1,members:{},messages:[],hasTriedToLoadMessages:!1,loadingMessages:!0,loadingMembers:!0,loadingConfirmAction:!1,pendingBlockUserIds:[],pendingUnblockUserIds:[],pendingRemoveContactIds:[],pendingAddContactIds:[],pendingDeleteMessageIds:[],pendingSendMessageIds:[],pendingDeleteConversation:!1,selectedMessageIds:[],showEmojiAutoComplete:!1,showEmojiPicker:!1}},addMessages:function(state,messages){var newState=cloneState(state),formattedMessages=formatMessages(messages,state.loggedInUserId,state.members);formattedMessages=formattedMessages.map((function(message){return message.sendState=null,message.timeAdded=Date.now(),message.errorMessage=null,message}));var allMessages=state.messages.concat(formattedMessages);return allMessages.sort((function(a,b){if(null===a.timeCreated&&null===b.timeCreated){if(a.timeAddedb.timeAdded)return 1}return null===a.timeCreated&&null!==b.timeCreated?1:null!==a.timeCreated&&null===b.timeCreated||a.timeCreatedb.timeCreated?1:a.idb.id?1:0})),newState.messages=allMessages.filter((function(message,index,sortedMessages){return!index||message.id!=sortedMessages[index-1].id})),newState},updateMessages:function(state,data){var newState=cloneState(state),updatesById=data.reduce((function(carry,messageData){var oldMessage=messageData[0],newMessage=messageData[1],formattedMessage=formatMessages([newMessage],state.loggedInUserId,state.members)[0];return carry[oldMessage.id]=formattedMessage,carry}),{});return newState.messages=newState.messages.map((function(message){return message.id in updatesById?$.extend(message,updatesById[message.id]):message})),newState},removeMessages:function(state,messages){var newState=cloneState(state),removeMessageIds=messages.map((function(message){return""+message.id}));return newState.messages=newState.messages.filter((function(message){return removeMessageIds.indexOf(message.id)<0})),newState},removeMessagesById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.messages=newState.messages.filter((function(message){return messageIds.indexOf(message.id)<0})),newState},addMembers:function(state,members){var newState=cloneState(state),formattedMembers=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||[],cancreatecontact:member.cancreatecontact}}))}(members);return formattedMembers.forEach((function(member){newState.members[member.id]=member})),newState},removeMembers:function(state,members){var newState=cloneState(state);return members.forEach((function(member){delete newState.members[member.id]})),newState},setLoadingMessages:function(state,value){var newState=cloneState(state);return newState.loadingMessages=value,state.loadingMessages&&!value&&(newState.hasTriedToLoadMessages=!0),newState},setLoadingMembers:function(state,value){var newState=cloneState(state);return newState.loadingMembers=value,newState},setId:function(state,value){var newState=cloneState(state);return newState.id=value,newState},setName:function(state,value){var newState=cloneState(state);return newState.name=value,newState},setSubname:function(state,value){var newState=cloneState(state);return newState.subname=value,newState},setType:function(state,type){var newState=cloneState(state);return newState.type=type,newState},setIsFavourite:function(state,isFavourite){var newState=cloneState(state);return newState.isFavourite=isFavourite,newState},setIsMuted:function(state,isMuted){var newState=cloneState(state);return newState.isMuted=isMuted,newState},setCanDeleteMessagesForAllUsers:function(state,value){var newState=cloneState(state);return newState.canDeleteMessagesForAllUsers=value,newState},setDeleteMessagesForAllUsers:function(state,value){var newState=cloneState(state);return newState.deleteMessagesForAllUsers=value,newState},setTotalMemberCount:function(state,count){var newState=cloneState(state);return newState.totalMemberCount=count,newState},setImageUrl:function(state,url){var newState=cloneState(state);return newState.imageUrl=url,newState},setLoadingConfirmAction:function(state,value){var newState=cloneState(state);return newState.loadingConfirmAction=value,newState},setPendingDeleteConversation:function(state,value){var newState=cloneState(state);return newState.pendingDeleteConversation=value,newState},setMessagesSendPendingById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.messages.forEach((function(message){messageIds.indexOf(message.id)>=0&&(message.sendState="pending",message.errorMessage=null)})),newState},setMessagesSendSuccessById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.messages.forEach((function(message){messageIds.indexOf(message.id)>=0&&(message.sendState="sent",message.errorMessage=null)})),newState},setMessagesSendFailById:function(state,messageIds,errorMessage){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.messages.forEach((function(message){messageIds.indexOf(message.id)>=0&&(message.sendState="error",message.errorMessage=errorMessage)})),newState},setShowEmojiAutoComplete:function(state,show){var newState=cloneState(state);return newState.showEmojiAutoComplete=show,newState},setShowEmojiPicker:function(state,show){var newState=cloneState(state);return newState.showEmojiPicker=show,newState},addPendingBlockUsersById:function(state,userIds){var newState=cloneState(state);return userIds.forEach((function(id){newState.pendingBlockUserIds.push(id)})),newState},addPendingRemoveContactsById:function(state,userIds){var newState=cloneState(state);return userIds.forEach((function(id){newState.pendingRemoveContactIds.push(id)})),newState},addPendingUnblockUsersById:function(state,userIds){var newState=cloneState(state);return userIds.forEach((function(id){newState.pendingUnblockUserIds.push(id)})),newState},addPendingAddContactsById:function(state,userIds){var newState=cloneState(state);return userIds.forEach((function(id){newState.pendingAddContactIds.push(id)})),newState},addPendingDeleteMessagesById:function(state,messageIds){var newState=cloneState(state);return messageIds.forEach((function(id){newState.pendingDeleteMessageIds.push(id)})),newState},removePendingBlockUsersById:function(state,userIds){var newState=cloneState(state);return newState.pendingBlockUserIds=newState.pendingBlockUserIds.filter((function(id){return userIds.indexOf(id)<0})),newState},removePendingRemoveContactsById:function(state,userIds){var newState=cloneState(state);return newState.pendingRemoveContactIds=newState.pendingRemoveContactIds.filter((function(id){return userIds.indexOf(id)<0})),newState},removePendingUnblockUsersById:function(state,userIds){var newState=cloneState(state);return newState.pendingUnblockUserIds=newState.pendingUnblockUserIds.filter((function(id){return userIds.indexOf(id)<0})),newState},removePendingAddContactsById:function(state,userIds){var newState=cloneState(state);return newState.pendingAddContactIds=newState.pendingAddContactIds.filter((function(id){return userIds.indexOf(id)<0})),newState},removePendingDeleteMessagesById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.pendingDeleteMessageIds=newState.pendingDeleteMessageIds.filter((function(id){return messageIds.indexOf(id)<0})),newState},addSelectedMessagesById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.selectedMessageIds=newState.selectedMessageIds.concat(messageIds),newState},removeSelectedMessagesById:function(state,messageIds){var newState=cloneState(state);return messageIds=messageIds.map((function(id){return""+id})),newState.selectedMessageIds=newState.selectedMessageIds.filter((function(id){return messageIds.indexOf(id)<0})),newState},markMessagesAsRead:function(state,readMessages){var newState=cloneState(state),readMessageIds=readMessages.map((function(message){return message.id}));return newState.messages=newState.messages.map((function(message){return readMessageIds.indexOf(message.id)>=0&&(message.isRead=!0),message})),newState},addContactRequests:function(state,requests){var newState=cloneState(state);return requests.forEach((function(request){var fromUserId=request.userid,toUserId=request.requesteduserid;newState.members[fromUserId].contactrequests.push(request),newState.members[toUserId].contactrequests.push(request)})),newState},removeContactRequests:function(state,requests){var newState=cloneState(state);return requests.forEach((function(request){var fromUserId=request.userid,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}))})),newState}}})); //# sourceMappingURL=message_drawer_view_conversation_state_manager.min.js.mapPKB\d build/message_user_button.min.jsnu[/** * Module to message a user from their profile page. * * @module core_message/message_user_button * @copyright 2019 Mark Nelson * @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.mapPKB\; t7t7'build/message_drawer_view_search.min.jsnu[/** * Controls the search page of the message drawer. * * @module core_message/message_drawer_view_search * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_search",["jquery","core/custom_interaction_events","core/notification","core/pubsub","core/str","core/templates","core_message/message_repository","core_message/message_drawer_events"],(function($,CustomEvents,Notification,PubSub,Str,Templates,Repository,Events){var SELECTORS_BLOCK_ICON_CONTAINER='[data-region="block-icon-container"]',SELECTORS_CANCEL_SEARCH_BUTTON='[data-action="cancel-search"]',SELECTORS_CONTACTS_CONTAINER='[data-region="contacts-container"]',SELECTORS_CONTACTS_LIST='[data-region="contacts-container"] [data-region="list"]',SELECTORS_EMPTY_MESSAGE_CONTAINER='[data-region="empty-message-container"]',SELECTORS_LIST='[data-region="list"]',SELECTORS_LOADING_ICON_CONTAINER='[data-region="loading-icon-container"]',SELECTORS_LOADING_PLACEHOLDER='[data-region="loading-placeholder"]',SELECTORS_MESSAGES_LIST='[data-region="messages-container"] [data-region="list"]',SELECTORS_MESSAGES_CONTAINER='[data-region="messages-container"]',SELECTORS_NON_CONTACTS_CONTAINER='[data-region="non-contacts-container"]',SELECTORS_NON_CONTACTS_LIST='[data-region="non-contacts-container"] [data-region="list"]',SELECTORS_SEARCH_ICON_CONTAINER='[data-region="search-icon-container"]',SELECTORS_SEARCH_ACTION='[data-action="search"]',SELECTORS_SEARCH_INPUT='[data-region="search-input"]',SELECTORS_SEARCH_RESULTS_CONTAINER='[data-region="search-results-container"]',SELECTORS_LOAD_MORE_USERS='[data-action="load-more-users"]',SELECTORS_LOAD_MORE_MESSAGES='[data-action="load-more-messages"]',SELECTORS_BUTTON_TEXT='[data-region="button-text"]',SELECTORS_NO_RESULTS_CONTAINTER='[data-region="no-results-container"]',SELECTORS_ALL_CONTACTS_CONTAINER='[data-region="all-contacts-container"]',TEMPLATES_CONTACTS_LIST="core_message/message_drawer_contacts_list",TEMPLATES_NON_CONTACTS_LIST="core_message/message_drawer_non_contacts_list",TEMPLATES_MESSAGES_LIST="core_message/message_drawer_messages_list",getLoggedInUserId=function(body){return body.attr("data-user-id")},getEmptyMessageContainer=function(body){return body.find(SELECTORS_EMPTY_MESSAGE_CONTAINER)},getLoadingIconContainer=function(header){return header.find(SELECTORS_LOADING_ICON_CONTAINER)},getLoadingPlaceholder=function(body){return body.find(SELECTORS_LOADING_PLACEHOLDER)},getSearchIconContainer=function(header){return header.find(SELECTORS_SEARCH_ICON_CONTAINER)},getSearchInput=function(header){return header.find(SELECTORS_SEARCH_INPUT)},getSearchResultsContainer=function(body){return body.find(SELECTORS_SEARCH_RESULTS_CONTAINER)},getContactsContainer=function(body){return body.find(SELECTORS_CONTACTS_CONTAINER)},getNonContactsContainer=function(body){return body.find(SELECTORS_NON_CONTACTS_CONTAINER)},hideEmptyMessage=function(body){getEmptyMessageContainer(body).addClass("hidden")},hideLoadingIcon=function(header){getLoadingIconContainer(header).addClass("hidden")},hideLoadingPlaceholder=function(body){getLoadingPlaceholder(body).addClass("hidden")},showSearchIcon=function(header){getSearchIconContainer(header).removeClass("hidden")},hideSearchResults=function(body){getSearchResultsContainer(body).addClass("hidden")},clearAllSearchResults=function(body){body.find(SELECTORS_CONTACTS_LIST).empty(),body.find(SELECTORS_NON_CONTACTS_LIST).empty(),body.find(SELECTORS_MESSAGES_LIST).empty(),function(body){var container=getSearchResultsContainer(body);container.find(SELECTORS_ALL_CONTACTS_CONTAINER).removeClass("hidden"),container.find(SELECTORS_MESSAGES_CONTAINER).removeClass("hidden"),container.find(SELECTORS_NO_RESULTS_CONTAINTER).addClass("hidden")}(body),function(body){getSearchResultsContainer(body).find(SELECTORS_ALL_CONTACTS_CONTAINER).removeClass("hidden")}(body),function(body){getSearchResultsContainer(body).find(SELECTORS_CONTACTS_CONTAINER).removeClass("hidden")}(body),function(body){getSearchResultsContainer(body).find(SELECTORS_NON_CONTACTS_CONTAINER).removeClass("hidden")}(body),function(body){getSearchResultsContainer(body).find(SELECTORS_MESSAGES_CONTAINER).removeClass("hidden")}(body),showLoadMoreUsersButton(body),showLoadMoreMessagesButton(body)},startLoading=function(header,body){!function(header){getSearchIconContainer(header).addClass("hidden")}(header),hideEmptyMessage(body),hideSearchResults(body),function(header){getLoadingIconContainer(header).removeClass("hidden")}(header),function(body){getLoadingPlaceholder(body).removeClass("hidden")}(body),function(header){getSearchInput(header).prop("disabled",!0)}(header)},stopLoading=function(header,body){showSearchIcon(header),hideEmptyMessage(body),function(body){getSearchResultsContainer(body).removeClass("hidden")}(body),hideLoadingIcon(header),hideLoadingPlaceholder(body),function(header){getSearchInput(header).prop("disabled",!1)}(header)},hideUsersLoadingIcon=function(root){var button=root.find(SELECTORS_LOAD_MORE_USERS);button.prop("disabled",!1),button.find(SELECTORS_BUTTON_TEXT).removeClass("hidden"),button.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},showLoadMoreUsersButton=function(root){root.find(SELECTORS_LOAD_MORE_USERS).removeClass("hidden")},hideMessagesLoadingIcon=function(root){var button=root.find(SELECTORS_LOAD_MORE_MESSAGES);button.prop("disabled",!1),button.find(SELECTORS_BUTTON_TEXT).removeClass("hidden"),button.find(SELECTORS_LOADING_ICON_CONTAINER).addClass("hidden")},showLoadMoreMessagesButton=function(root){root.find(SELECTORS_LOAD_MORE_MESSAGES).removeClass("hidden")},findContact=function(root,userId){return root.find('[data-contact-user-id="'+userId+'"]')},highlightSearch=function(content,searchText){if(!content)return"";var regex=new RegExp("("+searchText+")","gi");return content.replace(regex,'$1')},renderMessages=function(root,messages){var container=root.find(SELECTORS_MESSAGES_CONTAINER),frompanel=root.attr("data-in-panel"),list=container.find(SELECTORS_LIST);return Templates.render(TEMPLATES_MESSAGES_LIST,{messages:messages,frompanel:frompanel}).then((function(html){return list.append(html),html}))},loadMoreUsers=function(root,loggedInUserId,text,limit,offset){var loadedAll=!1;return function(root){var button=root.find(SELECTORS_LOAD_MORE_USERS);button.prop("disabled",!0),button.find(SELECTORS_BUTTON_TEXT).addClass("hidden"),button.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")}(root),Repository.searchUsers(loggedInUserId,text,limit+1,offset).then((function(results){var contacts=results.contacts,noncontacts=results.noncontacts;return contacts.length<=limit&&noncontacts.length<=limit?(loadedAll=!0,{contacts:contacts,noncontacts:noncontacts}):{contacts:contacts.slice(0,limit),noncontacts:noncontacts.slice(0,limit)}})).then((function(results){var contactsCount=results.contacts.length,nonContactsCount=results.noncontacts.length;return contactsCount&&results.contacts.forEach((function(contact){contact.highlight=highlightSearch(contact.fullname,text)})),nonContactsCount&&results.noncontacts.forEach((function(contact){contact.highlight=highlightSearch(contact.fullname,text)})),$.when(!contactsCount||function(root,contacts){var container=getContactsContainer(root),frompanel=root.attr("data-in-panel"),list=container.find(SELECTORS_LIST);return Templates.render(TEMPLATES_CONTACTS_LIST,{contacts:contacts,frompanel:frompanel}).then((function(html){return list.append(html),html}))}(root,results.contacts),!nonContactsCount||function(root,nonContacts){var container=getNonContactsContainer(root),frompanel=root.attr("data-in-panel"),list=container.find(SELECTORS_LIST);return Templates.render(TEMPLATES_NON_CONTACTS_LIST,{noncontacts:nonContacts,frompanel:frompanel}).then((function(html){return list.append(html),html}))}(root,results.noncontacts)).then((function(){return{contactsCount:contactsCount,nonContactsCount:nonContactsCount}}))})).then((function(counts){return hideUsersLoadingIcon(root),loadedAll&&function(root){root.find(SELECTORS_LOAD_MORE_USERS).addClass("hidden")}(root),counts})).catch((function(error){throw hideUsersLoadingIcon(root),error}))},loadMoreMessages=function(root,loggedInUserId,text,limit,offset){var loadedAll=!1;return function(root){var button=root.find(SELECTORS_LOAD_MORE_MESSAGES);button.prop("disabled",!0),button.find(SELECTORS_BUTTON_TEXT).addClass("hidden"),button.find(SELECTORS_LOADING_ICON_CONTAINER).removeClass("hidden")}(root),Repository.searchMessages(loggedInUserId,text,limit+1,offset).then((function(results){var messages=results.contacts;return messages.length<=limit?(loadedAll=!0,messages):messages.slice(0,limit)})).then((function(messages){return messages.length?(messages.forEach((function(message){message.lastmessage=highlightSearch(message.lastmessage,text)})),renderMessages(root,messages).then((function(){return messages.length}))):messages.length})).then((function(count){return hideMessagesLoadingIcon(root),loadedAll&&function(root){root.find(SELECTORS_LOAD_MORE_MESSAGES).addClass("hidden")}(root),count})).catch((function(error){throw hideMessagesLoadingIcon(root),error}))},search=function(header,body,searchText,usersLimit,usersOffset,messagesLimit,messagesOffset){var loggedInUserId=getLoggedInUserId(body);return startLoading(header,body),clearAllSearchResults(body),$.when(loadMoreUsers(body,loggedInUserId,searchText,usersLimit,usersOffset),loadMoreMessages(body,loggedInUserId,searchText,messagesLimit,messagesOffset)).then((function(userCounts,messagesCount){var contactsCount=userCounts.contactsCount,nonContactsCount=userCounts.nonContactsCount;stopLoading(header,body),contactsCount||nonContactsCount||messagesCount?(contactsCount||nonContactsCount?(contactsCount||function(body){getSearchResultsContainer(body).find(SELECTORS_CONTACTS_CONTAINER).addClass("hidden")}(body),nonContactsCount||function(body){getSearchResultsContainer(body).find(SELECTORS_NON_CONTACTS_CONTAINER).addClass("hidden")}(body)):function(body){getSearchResultsContainer(body).find(SELECTORS_ALL_CONTACTS_CONTAINER).addClass("hidden")}(body),messagesCount||function(body){getSearchResultsContainer(body).find(SELECTORS_MESSAGES_CONTAINER).addClass("hidden")}(body)):function(body){var container=getSearchResultsContainer(body);container.find(SELECTORS_ALL_CONTACTS_CONTAINER).addClass("hidden"),container.find(SELECTORS_MESSAGES_CONTAINER).addClass("hidden"),container.find(SELECTORS_NO_RESULTS_CONTAINTER).removeClass("hidden")}(body)}))},registerEventListeners=function(header,body){var loggedInUserId=getLoggedInUserId(body),searchInput=getSearchInput(header),searchText="",messagesOffset=0,usersOffset=0,searchEventHandler=function(e,data){""!==(searchText=searchInput.val().trim())&&search(header,body,searchText,3,usersOffset=0,50,messagesOffset=0).then((function(){searchInput.focus(),usersOffset+=3,messagesOffset+=50})).catch(Notification.exception),data.originalEvent.preventDefault()};CustomEvents.define(searchInput,[CustomEvents.events.enter]),CustomEvents.define(header,[CustomEvents.events.activate]),CustomEvents.define(body,[CustomEvents.events.activate]),searchInput.on(CustomEvents.events.enter,searchEventHandler),header.on(CustomEvents.events.activate,SELECTORS_SEARCH_ACTION,searchEventHandler),body.on(CustomEvents.events.activate,SELECTORS_LOAD_MORE_MESSAGES,(function(e,data){""!==searchText&&loadMoreMessages(body,loggedInUserId,searchText,50,messagesOffset).then((function(){messagesOffset+=50})).catch(Notification.exception),data.originalEvent.preventDefault()})),body.on(CustomEvents.events.activate,SELECTORS_LOAD_MORE_USERS,(function(e,data){""!==searchText&&loadMoreUsers(body,loggedInUserId,searchText,50,usersOffset).then((function(){usersOffset+=50})).catch(Notification.exception),data.originalEvent.preventDefault()})),header.on(CustomEvents.events.activate,SELECTORS_CANCEL_SEARCH_BUTTON,(function(){!function(header){getSearchInput(header).val("")}(header),function(body){getEmptyMessageContainer(body).removeClass("hidden")}(body),showSearchIcon(header),hideSearchResults(body),hideLoadingIcon(header),hideLoadingPlaceholder(body),usersOffset=0,messagesOffset=0})),PubSub.subscribe(Events.CONTACT_ADDED,(function(userId){!function(root,contact){var nonContactsContainer=getNonContactsContainer(root),nonContact=findContact(nonContactsContainer,contact.userid);if(nonContact.length){nonContact.remove();var contactsContainer=getContactsContainer(root);contactsContainer.removeClass("hidden"),contactsContainer.find(SELECTORS_LIST).append(nonContact)}nonContactsContainer.find(SELECTORS_LIST).children().length||nonContactsContainer.addClass("hidden")}(body,userId)})),PubSub.subscribe(Events.CONTACT_REMOVED,(function(userId){!function(root,userId){var contactsContainer=getContactsContainer(root),contact=findContact(contactsContainer,userId);if(contact.length){contact.remove();var nonContactsContainer=getNonContactsContainer(root);nonContactsContainer.removeClass("hidden"),nonContactsContainer.find(SELECTORS_LIST).append(contact)}contactsContainer.find(SELECTORS_LIST).children().length||contactsContainer.addClass("hidden")}(body,userId)})),PubSub.subscribe(Events.CONTACT_BLOCKED,(function(userId){!function(root,userId){var contact=findContact(root,userId);contact.length&&contact.find(SELECTORS_BLOCK_ICON_CONTAINER).removeClass("hidden")}(body,userId)})),PubSub.subscribe(Events.CONTACT_UNBLOCKED,(function(userId){!function(root,userId){var contact=findContact(root,userId);contact.length&&contact.find(SELECTORS_BLOCK_ICON_CONTAINER).addClass("hidden")}(body,userId)}))};return{show:function(namespace,header,body){return body.attr("data-init")||(registerEventListeners(header,body),body.attr("data-init",!0)),getSearchInput(header).focus(),$.Deferred().resolve().promise()},description:function(namespace,header){if("object"!=typeof header)return Str.get_string("messagedrawerviewsearch","core_message");var searchText=getSearchInput(header).val().trim();return Str.get_string("messagedrawerviewsearch","core_message",searchText)}}})); //# sourceMappingURL=message_drawer_view_search.min.js.mapPKB\'build/message_drawer.min.js.mapnu[{"version":3,"file":"message_drawer.min.js","sources":["../src/message_drawer.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the message drawer.\n *\n * @module core_message/message_drawer\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_view_contact',\n 'core_message/message_drawer_view_contacts',\n 'core_message/message_drawer_view_conversation',\n 'core_message/message_drawer_view_group_info',\n 'core_message/message_drawer_view_overview',\n 'core_message/message_drawer_view_search',\n 'core_message/message_drawer_view_settings',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_helper',\n 'core/pending',\n 'core/drawer',\n 'core/toast',\n 'core/str',\n 'core/config',\n 'core/ajax',\n 'core/local/aria/focuslock',\n 'core/modal_backdrop',\n 'core/templates',\n 'core/local/aria/selectors',\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n ViewContact,\n ViewContacts,\n ViewConversation,\n ViewGroupInfo,\n ViewOverview,\n ViewSearch,\n ViewSettings,\n Router,\n Routes,\n Events,\n Helper,\n Pending,\n Drawer,\n Toast,\n Str,\n Config,\n Ajax,\n FocusLock,\n ModalBackdrop,\n Templates,\n AriaSelectors,\n) {\n\n var SELECTORS = {\n DRAWER: '[data-region=\"right-hand-drawer\"]',\n PANEL_BODY_CONTAINER: '[data-region=\"panel-body-container\"]',\n PANEL_HEADER_CONTAINER: '[data-region=\"panel-header-container\"]',\n VIEW_CONTACT: '[data-region=\"view-contact\"]',\n VIEW_CONTACTS: '[data-region=\"view-contacts\"]',\n VIEW_CONVERSATION: '[data-region=\"view-conversation\"]',\n VIEW_CONVERSATION_WITH_ID: '[data-region=\"view-conversation\"][data-conversation-id]',\n VIEW_CONVERSATION_WITH_USER: '[data-region=\"view-conversation\"][data-other-user-id]',\n VIEW_GROUP_INFO: '[data-region=\"view-group-info\"]',\n VIEW_OVERVIEW: '[data-region=\"view-overview\"]',\n VIEW_SEARCH: '[data-region=\"view-search\"]',\n VIEW_SETTINGS: '[data-region=\"view-settings\"]',\n ROUTES: '[data-route]',\n ROUTES_BACK: '[data-route-back]',\n HEADER_CONTAINER: '[data-region=\"header-container\"]',\n BODY_CONTAINER: '[data-region=\"body-container\"]',\n FOOTER_CONTAINER: '[data-region=\"footer-container\"]',\n CLOSE_BUTTON: '[data-action=\"closedrawer\"]',\n MESSAGE_INDEX: '[data-region=\"message-index\"]',\n MESSAGE_TEXT_AREA: '[data-region=\"send-message-txt\"]',\n };\n\n /**\n * Get elements for route.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n * @param {string} selector The route container.\n *\n * @return {array} elements Found route container objects.\n */\n var getParametersForRoute = function(namespace, root, selector) {\n\n var header = root.find(SELECTORS.HEADER_CONTAINER).find(selector);\n if (!header.length) {\n header = root.find(SELECTORS.PANEL_HEADER_CONTAINER).find(selector);\n }\n var body = root.find(SELECTORS.BODY_CONTAINER).find(selector);\n if (!body.length) {\n body = root.find(SELECTORS.PANEL_BODY_CONTAINER).find(selector);\n }\n var footer = root.find(SELECTORS.FOOTER_CONTAINER).find(selector);\n\n return [\n namespace,\n header.length ? header : null,\n body.length ? body : null,\n footer.length ? footer : null\n ];\n };\n\n var routes = [\n [Routes.VIEW_CONTACT, SELECTORS.VIEW_CONTACT, ViewContact.show, ViewContact.description],\n [Routes.VIEW_CONTACTS, SELECTORS.VIEW_CONTACTS, ViewContacts.show, ViewContacts.description],\n [Routes.VIEW_CONVERSATION, SELECTORS.VIEW_CONVERSATION, ViewConversation.show, ViewConversation.description],\n [Routes.VIEW_GROUP_INFO, SELECTORS.VIEW_GROUP_INFO, ViewGroupInfo.show, ViewGroupInfo.description],\n [Routes.VIEW_OVERVIEW, SELECTORS.VIEW_OVERVIEW, ViewOverview.show, ViewOverview.description],\n [Routes.VIEW_SEARCH, SELECTORS.VIEW_SEARCH, ViewSearch.show, ViewSearch.description],\n [Routes.VIEW_SETTINGS, SELECTORS.VIEW_SETTINGS, ViewSettings.show, ViewSettings.description]\n ];\n\n /**\n * Create routes.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @param {Object} root The message drawer container.\n */\n var createRoutes = function(namespace, root) {\n routes.forEach(function(route) {\n Router.add(namespace, route[0], getParametersForRoute(namespace, root, route[1]), route[2], route[3]);\n });\n };\n\n let backdropPromise = null;\n\n /**\n * Set the focus on the drawer.\n *\n * This method also creates or destroy any necessary backdrop zone and focus trap.\n *\n * @param {Object} root The message drawer container.\n * @param {Boolean} hasFocus Whether the drawer has focus or not.\n */\n var setFocus = function(root, hasFocus) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (!drawerRoot.length) {\n return;\n }\n if (!backdropPromise) {\n backdropPromise = Templates.render('core/modal_backdrop', {})\n .then(html => new ModalBackdrop(html));\n }\n const backdropWithAdjustments = backdropPromise.then(modalBackdrop => {\n const messageDrawerZIndex = window.getComputedStyle(drawerRoot[0]).zIndex;\n if (messageDrawerZIndex) {\n modalBackdrop.setZIndex(messageDrawerZIndex - 1);\n }\n modalBackdrop.getAttachmentPoint().get(0).addEventListener('click', e => {\n PubSub.publish(Events.HIDE, {});\n e.preventDefault();\n });\n return modalBackdrop;\n });\n if (hasFocus) {\n FocusLock.trapFocus(root[0]);\n // eslint-disable-next-line promise/catch-or-return\n backdropWithAdjustments.then(modalBackdrop => {\n if (modalBackdrop) {\n modalBackdrop.show();\n const pageWrapper = document.getElementById('page');\n pageWrapper.style.overflow = 'hidden';\n // Set the focus on the close button so when we press enter, it closes the drawer as it did before\n var closeButton = root.find(SELECTORS.CLOSE_BUTTON);\n if (closeButton.length) {\n closeButton.focus();\n }\n }\n return modalBackdrop;\n });\n } else {\n // eslint-disable-next-line promise/catch-or-return\n backdropWithAdjustments.then(modalBackdrop => {\n if (modalBackdrop) {\n FocusLock.untrapFocus();\n var button = $(SELECTORS.DRAWER).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n modalBackdrop.hide();\n const pageWrapper = document.getElementById('page');\n pageWrapper.style.overflow = 'visible';\n }\n return modalBackdrop;\n });\n }\n };\n /**\n * Show the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n */\n var show = function(namespace, root) {\n if (!root.attr('data-shown')) {\n Router.go(namespace, Routes.VIEW_OVERVIEW);\n root.attr('data-shown', true);\n }\n\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n setFocus(root, true);\n Drawer.show(drawerRoot);\n }\n };\n\n /**\n * Hide the message drawer.\n *\n * @param {Object} root The message drawer container.\n */\n var hide = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n setFocus(root, false);\n Drawer.hide(drawerRoot);\n }\n };\n\n /**\n * Check if the drawer is visible.\n *\n * @param {Object} root The message drawer container.\n * @return {boolean}\n */\n var isVisible = function(root) {\n var drawerRoot = Drawer.getDrawerRoot(root);\n if (drawerRoot.length) {\n return Drawer.isVisible(drawerRoot);\n }\n return true;\n };\n\n /**\n * Set Jump from button\n *\n * @param {String} buttonid The originating button id\n */\n var setJumpFrom = function(buttonid) {\n $(SELECTORS.DRAWER).attr('data-origin', buttonid);\n };\n\n /**\n * Store an unsent message.\n *\n * Don't store this if the user has already seen the unsent message.\n * This avoids spamming and ensures the user is only reminded once per unsent message.\n * If the unsent message is sent, this attribute is removed and notification is possible again (see sendMessage).\n */\n const storeUnsentMessage = async() => {\n const messageTextArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n\n if (messageTextArea.value.trim().length > 0 && !messageTextArea.hasAttribute('data-unsent-message-viewed')) {\n\n let message = messageTextArea.value;\n let conversationid = 0;\n let otheruserid = 0;\n\n // We don't always have a conversation to link the unsent message to, so let's check for that.\n const conversationId = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n if (conversationId) {\n const conversationWithId = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_ID);\n conversationid = conversationWithId.getAttribute('data-conversation-id');\n }\n // Store the 'other' user id if it is there. This can be used to create conversations.\n const conversationUser = document.querySelector(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n if (conversationUser) {\n const conversationWithUser = messageTextArea.closest(SELECTORS.VIEW_CONVERSATION_WITH_USER);\n otheruserid = conversationWithUser.getAttribute('data-other-user-id');\n }\n\n setStoredUnsentMessage(message, conversationid, otheruserid);\n }\n };\n\n /**\n * Get the stored unsent message from the session via web service.\n *\n * @returns {Promise}\n */\n const getStoredUnsentMessage = () => Ajax.call([{\n methodname: 'core_message_get_unsent_message',\n args: {}\n }])[0];\n\n /**\n * Set the unsent message value in the session via web service.\n *\n * SendBeacon is used here because this is called on 'beforeunload'.\n *\n * @param {string} message The message string.\n * @param {number} conversationid The conversation id.\n * @param {number} otheruserid The other user id.\n * @returns {Promise}\n */\n const setStoredUnsentMessage = (message, conversationid, otheruserid) => {\n const method = 'core_message_set_unsent_message';\n const requestUrl = new URL(`${Config.wwwroot}/lib/ajax/service.php`);\n requestUrl.searchParams.set('sesskey', Config.sesskey);\n requestUrl.searchParams.set('info', method);\n\n navigator.sendBeacon(requestUrl, JSON.stringify([{\n index: 0,\n methodname: method,\n args: {\n message: message,\n conversationid: conversationid,\n otheruserid: otheruserid,\n }\n }]));\n };\n\n /**\n * Check for an unsent message.\n *\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const getUnsentMessage = async(uniqueId, root) => {\n let type;\n let messageRoot;\n\n // We need to check if we are on the message/index page.\n // This logic is needed to handle the two message widgets here and ensure we are targetting the right one.\n const messageIndex = document.querySelector(SELECTORS.MESSAGE_INDEX);\n if (messageIndex !== null) {\n type = 'index';\n messageRoot = document.getElementById(`message-index-${uniqueId}`);\n if (!messageRoot) {\n // This is not the correct widget.\n return;\n }\n\n } else {\n type = 'drawer';\n messageRoot = document.getElementById(`message-drawer-${uniqueId}`);\n }\n\n const storedMessage = await getStoredUnsentMessage();\n const messageTextArea = messageRoot.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (storedMessage.message && messageTextArea !== null) {\n showUnsentMessage(messageTextArea, storedMessage, type, uniqueId, root);\n }\n };\n\n /**\n * Show an unsent message.\n *\n * There are two message widgets on the message/index page.\n * Because of that, we need to try and target the correct widget.\n *\n * @param {String} textArea The textarea element.\n * @param {Object} stored The stored message content.\n * @param {String} type Is this from the drawer or index page?\n * @param {String} uniqueId Unique identifier for the Routes.\n * @param {Object} root The message drawer container.\n */\n const showUnsentMessage = (textArea, stored, type, uniqueId, root) => {\n // The user has already been notified.\n if (textArea.hasAttribute('data-unsent-message-viewed')) {\n return;\n }\n\n // Depending on the type, show the conversation with the data we have available.\n // A conversation can be continued if there is a conversationid.\n // If the user was messaging a new non-contact, we won't have a conversationid yet.\n // In that case, we use the otheruserid value to start a conversation with them.\n switch (type) {\n case 'index':\n // Show the conversation in the main panel on the message/index page.\n if (stored.conversationid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, stored.conversationid, 'frompanel');\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n\n case 'drawer':\n // Open the drawer and show the conversation.\n if (stored.conversationid) {\n let args = {\n conversationid: stored.conversationid\n };\n Helper.showConversation(args);\n // There was no conversation id, let's get a conversation going using the user id.\n } else if (stored.otheruserid) {\n show(uniqueId, root);\n Router.go(uniqueId, Routes.VIEW_CONVERSATION, null, 'create', stored.otheruserid);\n }\n break;\n }\n\n // Populate the text area.\n textArea.value = stored.message;\n textArea.setAttribute('data-unsent-message-viewed', 1);\n\n // Notify the user.\n Toast.add(Str.get_string('unsentmessagenotification', 'core_message'));\n };\n\n /**\n * Listen to and handle events for routing, showing and hiding the message drawer.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} root The message drawer container.\n * @param {bool} alwaysVisible Is this messaging app always shown?\n */\n var registerEventListeners = function(namespace, root, alwaysVisible) {\n CustomEvents.define(root, [CustomEvents.events.activate, CustomEvents.events.escape]);\n var paramRegex = /^data-route-param-?(\\d*)$/;\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES, function(e, data) {\n var element = $(e.target).closest(SELECTORS.ROUTES);\n var route = element.attr('data-route');\n var attributes = [];\n\n for (var i = 0; i < element[0].attributes.length; i++) {\n attributes.push(element[0].attributes[i]);\n }\n\n var paramAttributes = attributes.filter(function(attribute) {\n var name = attribute.nodeName;\n var match = paramRegex.test(name);\n return match;\n });\n paramAttributes.sort(function(a, b) {\n var aParts = paramRegex.exec(a.nodeName);\n var bParts = paramRegex.exec(b.nodeName);\n var aIndex = aParts.length > 1 ? aParts[1] : 0;\n var bIndex = bParts.length > 1 ? bParts[1] : 0;\n\n if (aIndex < bIndex) {\n return -1;\n } else if (bIndex < aIndex) {\n return 1;\n } else {\n return 0;\n }\n });\n\n var params = paramAttributes.map(function(attribute) {\n return attribute.nodeValue;\n });\n\n var routeParams = [namespace, route].concat(params);\n\n Router.go.apply(null, routeParams);\n\n data.originalEvent.preventDefault();\n });\n\n root.on(CustomEvents.events.activate, SELECTORS.ROUTES_BACK, function(e, data) {\n Router.back(namespace);\n\n data.originalEvent.preventDefault();\n });\n\n // Close the message drawer if the drawer is visible and the click happened outside the drawer and the toggle button.\n $(document).on(CustomEvents.events.activate, e => {\n var drawer = $(e.target).closest(SELECTORS.DRAWER);\n var toggleButtonId = $(SELECTORS.DRAWER)?.attr('data-origin');\n var toggleButton = '';\n if (toggleButtonId !== undefined && toggleButtonId) {\n toggleButton = $(e.target).closest(\"#\" + toggleButtonId);\n }\n\n if (!drawer.length && !toggleButton.length && isVisible(root)) {\n // Determine if the element that was clicked is focusable.\n var focusableElement = $(e.target).closest(AriaSelectors.elements.focusable);\n if (focusableElement.length) {\n // We need to move the focus to the clicked element after the drawer is hidden,\n // so we need to clear the `data-origin` attribute first.\n $(SELECTORS.DRAWER).attr('data-origin', '');\n }\n // Hide the drawer.\n hide(root);\n // Move the focus to the clicked element if it is focusable.\n if (focusableElement.length) {\n focusableElement.focus();\n }\n }\n });\n\n // These are theme-specific to help us fix random behat fails.\n // These events target those events defined in BS3 and BS4 onwards.\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('hide.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('hidden.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n root[0].querySelectorAll('.collapse').forEach((collapse) => {\n collapse.addEventListener('show.bs.collapse', (e) => {\n var pendingPromise = new Pending();\n e.target.addEventListener('shown.bs.collapse', function() {\n pendingPromise.resolve();\n }, {once: true});\n });\n });\n\n $(SELECTORS.JUMPTO).focus(function() {\n var firstInput = root.find(SELECTORS.CLOSE_BUTTON);\n if (firstInput.length) {\n firstInput.focus();\n } else {\n $(SELECTORS.HEADER_CONTAINER).find(SELECTORS.ROUTES_BACK).focus();\n }\n });\n\n $(SELECTORS.DRAWER).focus(function() {\n var button = $(this).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n });\n\n if (!alwaysVisible) {\n PubSub.subscribe(Events.SHOW, function() {\n show(namespace, root);\n });\n\n PubSub.subscribe(Events.HIDE, function() {\n hide(root);\n });\n\n PubSub.subscribe(Events.TOGGLE_VISIBILITY, function(buttonid) {\n const buttonElement = document.getElementById(buttonid);\n if (isVisible(root)) {\n hide(root);\n buttonElement?.setAttribute('aria-expanded', false);\n $(SELECTORS.JUMPTO).attr('tabindex', -1);\n } else {\n show(namespace, root);\n buttonElement?.setAttribute('aria-expanded', true);\n setJumpFrom(buttonid);\n $(SELECTORS.JUMPTO).attr('tabindex', 0);\n }\n });\n root.on(CustomEvents.events.escape, function() {\n PubSub.publish(Events.HIDE, {});\n });\n }\n\n PubSub.subscribe(Events.SHOW_CONVERSATION, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, args.conversationid);\n });\n\n var closebutton = root.find(SELECTORS.CLOSE_BUTTON);\n closebutton.on(CustomEvents.events.activate, function(e, data) {\n data.originalEvent.preventDefault();\n setFocus(root, false);\n var button = $(SELECTORS.DRAWER).attr('data-origin');\n if (button) {\n $('#' + button).focus();\n }\n PubSub.publish(Events.TOGGLE_VISIBILITY, button);\n });\n\n PubSub.subscribe(Events.CREATE_CONVERSATION_WITH_USER, function(args) {\n setJumpFrom(args.buttonid);\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_CONVERSATION, null, 'create', args.userid);\n });\n\n PubSub.subscribe(Events.SHOW_SETTINGS, function() {\n show(namespace, root);\n Router.go(namespace, Routes.VIEW_SETTINGS);\n });\n\n PubSub.subscribe(Events.PREFERENCES_UPDATED, function(preferences) {\n var filteredPreferences = preferences.filter(function(preference) {\n return preference.type == 'message_entertosend';\n });\n var enterToSendPreference = filteredPreferences.length ? filteredPreferences[0] : null;\n\n if (enterToSendPreference) {\n var viewConversationFooter = root.find(SELECTORS.FOOTER_CONTAINER).find(SELECTORS.VIEW_CONVERSATION);\n viewConversationFooter.attr('data-enter-to-send', enterToSendPreference.value);\n }\n });\n\n // If our textarea is modified, remove the attribute which indicates the user has seen the unsent message notification.\n // This will allow the user to be notified again.\n const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (textArea) {\n textArea.addEventListener('keyup', function() {\n textArea.removeAttribute('data-unsent-message-viewed');\n });\n }\n\n // Catch any unsent messages and store them.\n window.addEventListener('beforeunload', storeUnsentMessage);\n };\n\n /**\n * Initialise the message drawer.\n *\n * @param {Object} root The message drawer container.\n * @param {String} uniqueId Unique identifier for the Routes\n * @param {bool} alwaysVisible Should we show the app now, or wait for the user?\n * @param {Object} route\n */\n var init = function(root, uniqueId, alwaysVisible, route) {\n root = $(root);\n createRoutes(uniqueId, root);\n registerEventListeners(uniqueId, root, alwaysVisible);\n\n if (alwaysVisible) {\n show(uniqueId, root);\n\n if (route) {\n var routeParams = route.params || [];\n routeParams = [uniqueId, route.path].concat(routeParams);\n Router.go.apply(null, routeParams);\n }\n }\n\n // Mark the drawer as ready.\n Helper.markDrawerReady();\n\n // Get and show any unsent message.\n getUnsentMessage(uniqueId, root);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","ViewContact","ViewContacts","ViewConversation","ViewGroupInfo","ViewOverview","ViewSearch","ViewSettings","Router","Routes","Events","Helper","Pending","Drawer","Toast","Str","Config","Ajax","FocusLock","ModalBackdrop","Templates","AriaSelectors","SELECTORS","DRAWER","PANEL_BODY_CONTAINER","PANEL_HEADER_CONTAINER","VIEW_CONTACT","VIEW_CONTACTS","VIEW_CONVERSATION","VIEW_CONVERSATION_WITH_ID","VIEW_CONVERSATION_WITH_USER","VIEW_GROUP_INFO","VIEW_OVERVIEW","VIEW_SEARCH","VIEW_SETTINGS","ROUTES","ROUTES_BACK","HEADER_CONTAINER","BODY_CONTAINER","FOOTER_CONTAINER","CLOSE_BUTTON","MESSAGE_INDEX","MESSAGE_TEXT_AREA","routes","show","description","createRoutes","namespace","root","forEach","route","add","selector","header","find","length","body","footer","getParametersForRoute","backdropPromise","setFocus","hasFocus","drawerRoot","getDrawerRoot","render","then","html","backdropWithAdjustments","modalBackdrop","messageDrawerZIndex","window","getComputedStyle","zIndex","setZIndex","getAttachmentPoint","get","addEventListener","e","publish","HIDE","preventDefault","trapFocus","document","getElementById","style","overflow","closeButton","focus","untrapFocus","button","attr","hide","go","isVisible","setJumpFrom","buttonid","storeUnsentMessage","async","messageTextArea","querySelector","value","trim","hasAttribute","message","conversationid","otheruserid","closest","getAttribute","setStoredUnsentMessage","method","requestUrl","URL","wwwroot","searchParams","set","sesskey","navigator","sendBeacon","JSON","stringify","index","methodname","args","getUnsentMessage","uniqueId","type","messageRoot","storedMessage","call","showUnsentMessage","textArea","stored","showConversation","setAttribute","get_string","init","alwaysVisible","events","activate","escape","paramRegex","on","data","element","target","attributes","i","push","paramAttributes","filter","attribute","name","nodeName","test","sort","a","b","aParts","exec","bParts","aIndex","bIndex","params","map","nodeValue","routeParams","concat","apply","originalEvent","back","drawer","toggleButtonId","_$","toggleButton","undefined","focusableElement","elements","focusable","querySelectorAll","collapse","pendingPromise","resolve","once","JUMPTO","firstInput","this","subscribe","SHOW","TOGGLE_VISIBILITY","buttonElement","SHOW_CONVERSATION","CREATE_CONVERSATION_WITH_USER","userid","SHOW_SETTINGS","PREFERENCES_UPDATED","preferences","filteredPreferences","preference","enterToSendPreference","removeAttribute","registerEventListeners","path","markDrawerReady"],"mappings":"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"}PKB\|Fyy build/message_popover.min.js.mapnu[{"version":3,"file":"message_popover.min.js","sources":["../src/message_popover.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the message popover in the nav bar.\n *\n * @module core_message/message_popover\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/custom_interaction_events',\n 'core/pubsub',\n 'core_message/message_drawer_events'\n],\nfunction(\n $,\n CustomEvents,\n PubSub,\n MessageDrawerEvents\n) {\n var SELECTORS = {\n COUNT_CONTAINER: '[data-region=\"count-container\"]'\n };\n\n /**\n * Toggle the message drawer visibility.\n *\n * @param {String} buttonid The button id for the popover.\n */\n var toggleMessageDrawerVisibility = function(buttonid) {\n PubSub.publish(MessageDrawerEvents.TOGGLE_VISIBILITY, buttonid);\n };\n\n /**\n * Decrement the unread conversation count in the nav bar if a conversation\n * is read. When there are no unread conversations then hide the counter.\n *\n * @param {Object} button The button element for the popover.\n * @return {Function}\n */\n var handleDecrementConversationCount = function(button) {\n return function() {\n var countContainer = button.find(SELECTORS.COUNT_CONTAINER);\n var count = parseInt(countContainer.text(), 10);\n\n if (isNaN(count)) {\n countContainer.addClass('hidden');\n } else if (!count || count < 2) {\n countContainer.addClass('hidden');\n } else {\n count = count - 1;\n countContainer.text(count);\n }\n };\n };\n\n /**\n * Add events listeners for when the popover icon is clicked and when conversations\n * are read.\n *\n * @param {Object} button The button element for the popover.\n */\n var registerEventListeners = function(button) {\n CustomEvents.define(button, [CustomEvents.events.activate]);\n\n button.on(CustomEvents.events.activate, function(e, data) {\n toggleMessageDrawerVisibility(button.attr('id'));\n data.originalEvent.preventDefault();\n });\n\n PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, handleDecrementConversationCount(button));\n PubSub.subscribe(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, handleDecrementConversationCount(button));\n };\n\n /**\n * Initialise the message popover.\n *\n * @param {Object} button The button element for the popover.\n */\n var init = function(button) {\n button = $(button);\n registerEventListeners(button);\n };\n\n return {\n init: init,\n };\n});\n"],"names":["define","$","CustomEvents","PubSub","MessageDrawerEvents","SELECTORS","handleDecrementConversationCount","button","countContainer","find","count","parseInt","text","isNaN","addClass","registerEventListeners","events","activate","on","e","data","buttonid","attr","publish","TOGGLE_VISIBILITY","originalEvent","preventDefault","subscribe","CONVERSATION_READ","CONTACT_REQUEST_ACCEPTED","CONTACT_REQUEST_DECLINED","init"],"mappings":";;;;;;;AAsBAA,sCACA,CACI,SACA,iCACA,cACA,uCAEJ,SACIC,EACAC,aACAC,OACAC,yBAEIC,0BACiB,kCAmBjBC,iCAAmC,SAASC,eACrC,eACCC,eAAiBD,OAAOE,KAAKJ,2BAC7BK,MAAQC,SAASH,eAAeI,OAAQ,IAExCC,MAAMH,SAEEA,OAASA,MAAQ,EADzBF,eAAeM,SAAS,WAIxBJ,OAAgB,EAChBF,eAAeI,KAAKF,UAW5BK,uBAAyB,SAASR,QAClCL,aAAaF,OAAOO,OAAQ,CAACL,aAAac,OAAOC,WAEjDV,OAAOW,GAAGhB,aAAac,OAAOC,UAAU,SAASE,EAAGC,MApCpB,IAASC,SAAAA,SAqCPd,OAAOe,KAAK,MApC9CnB,OAAOoB,QAAQnB,oBAAoBoB,kBAAmBH,UAqClDD,KAAKK,cAAcC,oBAGvBvB,OAAOwB,UAAUvB,oBAAoBwB,kBAAmBtB,iCAAiCC,SACzFJ,OAAOwB,UAAUvB,oBAAoByB,yBAA0BvB,iCAAiCC,SAChGJ,OAAOwB,UAAUvB,oBAAoB0B,yBAA0BxB,iCAAiCC,gBAa7F,CACHwB,KANO,SAASxB,QAChBA,OAASN,EAAEM,QACXQ,uBAAuBR"}PKB\UV+build/preferences_processor_form.min.js.mapnu[{"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 .\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 \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"}PKB\ },build/message_notification_preference.min.jsnu[/** * Controls the preference for an individual notification type on the * message preference page. * * @module core_message/message_notification_preference * @copyright 2016 Ryan Wyllie * @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.mapPKB\[Zp :build/message_drawer_view_contacts_section_contacts.min.jsnu[/** * Controls the contacts section of the contacts page. * * @module core_message/message_drawer_view_contacts_section_contacts * @copyright 2018 Ryan Wyllie * @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.mapPKB\=7build/message_drawer_view_conversation_constants.min.jsnu[/** * Constant values for the conversation page in the message drawer. * * @module core_message/message_drawer_view_conversation_constants * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_conversation_constants",[],(function(){return{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"]'},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:{PRIVATE:1,PUBLIC:2,SELF:3},NEWEST_MESSAGES_FIRST:!0,LOAD_MESSAGE_LIMIT:100,MILLISECONDS_IN_SEC:1e3}})); //# sourceMappingURL=message_drawer_view_conversation_constants.min.js.mapPKB\sG991build/message_drawer_view_overview_section.min.jsnu[/** * Controls a section of the overview page in the message drawer. * * @module core_message/message_drawer_view_overview_section * @copyright 2018 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("core_message/message_drawer_view_overview_section",["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_CONVERSATION="[data-conversation-id]",SELECTORS_BLOCKED_ICON_CONTAINER='[data-region="contact-icon-blocked"]',SELECTORS_MUTED_ICON_CONTAINER='[data-region="muted-icon-container"]',SELECTORS_UNREAD_COUNT='[data-region="unread-count"]',SELECTORS_SECTION_TOTAL_COUNT='[data-region="section-total-count"]',SELECTORS_SECTION_TOTAL_COUNT_CONTAINER='[data-region="section-total-count-container"]',SELECTORS_SECTION_UNREAD_COUNT='[data-region="section-unread-count"]',SELECTORS_SECTION_UNREAD_COUNT_CONTAINER='[data-region="section-unread-count-container"]',SELECTORS_PLACEHOLDER_CONTAINER='[data-region="placeholder-container"]',TEMPLATES_CONVERSATIONS_LIST="core_message/message_drawer_conversations_list",TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER="core_message/message_drawer_conversations_list_items_placeholder",loadedConversationsById={},deletedConversationsById={},loadedTotalCounts=!1,loadedUnreadCounts=!1,isVisible=function(root){return LazyLoadList.getRoot(root).hasClass("show")},setExpanded=function(root){root.addClass("expanded")},renderUnreadCount=function(root,count){var container=root.find(SELECTORS_SECTION_UNREAD_COUNT_CONTAINER);container.find(SELECTORS_SECTION_UNREAD_COUNT).text(count),Str.get_string("unreadconversations","core_message",count).done((function(string){$("#"+container.attr("aria-labelledby")).text(string)})),count>0?container.removeClass("hidden"):container.addClass("hidden")},formatConversationFromEvent=function(conversation){var recursivelyLowercaseKeys=function(object){return Object.keys(object).reduce((function(carry,key){return $.isArray(object[key])?carry[key.toLowerCase()]=object[key].map(recursivelyLowercaseKeys):carry[key.toLowerCase()]=object[key],carry}),{})},formatted=recursivelyLowercaseKeys(conversation);return formatted.messages=formatted.messages.map((function(message){return message.useridfrom=message.userfrom.id,message})),formatted},render=function(conversations,userId){var pending=new Pending,mapPromises=conversations.map((function(conversation){var lastMessage=conversation.messages.length?conversation.messages[conversation.messages.length-1]:null;return async function(lastMessage){if(!lastMessage)return null;var tmpElement=document.createElement("element");if(tmpElement.innerHTML=lastMessage.text.replace(/50?conversations=conversations.slice(0,-1):LazyLoadList.setLoadedAll(root,!0),offset+=50,conversations.forEach((function(conversation){loadedConversationsById[conversation.id]=conversation})),conversations})).catch(Notification.exception)}}(types,includeFavourites,0);if(registerEventListeners(namespace,root,loadCallback,types,includeFavourites,fromPanel),isVisible(root)){setExpanded(root);var listRoot=LazyLoadList.getRoot(root);LazyLoadList.show(listRoot,loadCallback,(function(contentContainer,conversations,userId){return render(conversations,userId).then((function(html){return contentContainer.append(html),html})).catch(Notification.exception)}))}totalCountPromise.then((function(count){!function(root,count){var container=root.find(SELECTORS_SECTION_TOTAL_COUNT_CONTAINER);container.find(SELECTORS_SECTION_TOTAL_COUNT).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,placeholders=Array.apply(null,Array(numPlaceholders)).map((function(){return!0}));Templates.render(TEMPLATES_CONVERSATIONS_LIST_ITEMS_PLACEHOLDER,{placeholders:placeholders}).then((function(html){root.find(SELECTORS_PLACEHOLDER_CONTAINER).html(html)})).catch((function(){}))}(root,count),loadedTotalCounts=!0})).catch((function(){})),unreadCountPromise.then((function(count){renderUnreadCount(root,count),loadedUnreadCounts=!0})).catch((function(){})),root.attr("data-init",!0)}},isVisible:isVisible}})); //# sourceMappingURL=message_drawer_view_overview_section.min.js.mapPKB\H7aa,build/notification_processor_settings.min.jsnu[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.mapPKB\4U661build/default_notification_preferences.min.js.mapnu[{"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 .\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 \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport * as Ajax from 'core/ajax';\nimport Notification from 'core/notification';\nimport {add as addToast} from 'core/toast';\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.form-check');\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 /**\n * AJAX call to update the default notification element status on the server.\n *\n * @param {Boolean} isEnabled\n * @param {string} preference\n */\n const setDefaultNotification = (isEnabled, preference) => {\n // AJAX call to update the provider's enable status on the server.\n Ajax.call([{\n methodname: 'core_message_set_default_notification',\n args: {\n preference: preference,\n state: isEnabled ? 1 : 0\n }\n }])[0]\n .then((data) => addToast(data.successmessage))\n .catch(Notification.exception);\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 setDefaultNotification(\n e.target.checked,\n providerEnabledElement.parentElement.parentElement.dataset.preference);\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 setDefaultNotification(\n e.target.checked,\n lockedElement.parentElement.parentElement.dataset.preference);\n });\n });\n\n container.querySelectorAll(selectors.enabledSetting).forEach((enabledElement) => {\n enabledElement.addEventListener('change', (e) => {\n setDefaultNotification(\n e.target.checked,\n enabledElement.parentElement.parentElement.dataset.preference);\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","setDefaultNotification","preference","Ajax","call","methodname","args","state","then","data","successmessage","catch","Notification","exception","container","querySelector","addEventListener","e","target","parentElement","dataset","enabledElement","registerEventListeners"],"mappings":";;;;;;;;;;sFA6BMA,mBACQ,2CADRA,sBAEW,0BAFXA,yBAGc,2BAHdA,sBAIW,iEAuGF,CACXC,KALS,KA7FkB,YAOrBC,kBAAqBC,sBACjBC,UAAYD,cAAcE,UAAW,EACrCC,UAAYH,cAAcI,GAAGC,QAAQ,WAAY,aAEhCC,SAASC,eAAeJ,WAAWK,QAAQ,kBACnDC,UAAUC,OAAO,cAAeT,YAQ7CU,6BAAgCC,+BAC5BX,UAAYW,uBAAuBV,UAAW,EAClCU,uBAAuBJ,QAAQ,MAEtBK,iBAAiBhB,uBACnCiB,SAASC,UACdA,QAAQC,gBAAgB,YAAaf,eAUvCgB,uBAAyB,CAAChB,UAAWiB,cAEvCC,KAAKC,KAAK,CAAC,CACPC,WAAY,wCACZC,KAAM,CACFJ,WAAYA,WACZK,MAAOtB,UAAY,EAAI,MAE3B,GACHuB,MAAMC,OAAS,cAASA,KAAKC,kBAC7BC,MAAMC,sBAAaC,YAGlBC,UAAYxB,SAASyB,cAAc,0BAEzCD,UAAUjB,iBAAiBhB,oBAAoBiB,SAASF,yBAE/CA,uBAAuBV,SACxBS,6BAA6BC,wBAGjCA,uBAAuBoB,iBAAiB,UAAWC,IAC/CtB,6BAA6BsB,EAAEC,QAE/BjB,uBACIgB,EAAEC,OAAOhC,QACTU,uBAAuBuB,cAAcA,cAAcC,QAAQlB,kBAIvEY,UAAUjB,iBAAiBhB,uBAAuBiB,SAASd,gBAEnDA,cAAcE,SACdH,kBAAkBC,eAGtBA,cAAcgC,iBAAiB,UAAWC,IACtClC,kBAAkBkC,EAAEC,QAEpBjB,uBACIgB,EAAEC,OAAOhC,QACTF,cAAcmC,cAAcA,cAAcC,QAAQlB,kBAI9DY,UAAUjB,iBAAiBhB,0BAA0BiB,SAASuB,iBAC1DA,eAAeL,iBAAiB,UAAWC,IACvChB,uBACIgB,EAAEC,OAAOhC,QACTmC,eAAeF,cAAcA,cAAcC,QAAQlB,mBAS/DoB"}PKB\"; 6build/preferences_notifications_list_controller.min.jsnu[/** * 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 * @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.mapPKB\hD(build/message_drawer_view_contact.min.jsnu[/** * Controls the contact page in the message drawer. * * @module core_message/message_drawer_view_contact * @copyright 2018 Ryan Wyllie * @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.mapPKB\9/build/message_drawer_view_group_info.min.js.mapnu[{"version":3,"file":"message_drawer_view_group_info.min.js","sources":["../src/message_drawer_view_group_info.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the group info page of the message drawer.\n *\n * @module core_message/message_drawer_view_group_info\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/str',\n 'core/templates',\n 'core_message/message_repository',\n 'core_message/message_drawer_lazy_load_list',\n],\nfunction(\n $,\n Str,\n Templates,\n Repository,\n LazyLoadList\n) {\n\n var LOAD_MEMBERS_LIMIT = 50;\n\n var SELECTORS = {\n CONTENT_CONTAINER: '[data-region=\"group-info-content-container\"]',\n MEMBERS_LIST: '[data-region=\"members-list\"]',\n };\n\n var TEMPLATES = {\n CONTENT: 'core_message/message_drawer_view_group_info_body_content',\n MEMBERS_LIST: 'core_message/message_drawer_view_group_info_participants_list'\n };\n\n /**\n * Get the content container of the group info view container.\n *\n * @param {Object} root Contact container element.\n * @return {Object} jQuery object\n */\n var getContentContainer = function(root) {\n return root.find(SELECTORS.CONTENT_CONTAINER);\n };\n\n /**\n * Render the group info page.\n *\n * @param {Object} root Container element.\n * @param {Object} conversation The group conversation.\n * @param {Number} loggedInUserId The logged in user's id.\n * @return {Object} jQuery promise\n */\n var render = function(root, conversation, loggedInUserId) {\n var placeholderCount = conversation.totalMemberCount > 50 ? 50 : conversation.totalMemberCount;\n var placeholders = Array.apply(null, Array(placeholderCount)).map(function() {\n return true;\n });\n var templateContext = {\n name: conversation.name,\n subname: conversation.subname,\n imageurl: conversation.imageUrl,\n placeholders: placeholders,\n loggedinuser: {\n id: loggedInUserId\n }\n };\n\n return Templates.render(TEMPLATES.CONTENT, templateContext)\n .then(function(html) {\n getContentContainer(root).append(html);\n return html;\n });\n };\n\n /**\n * Get the callback to load members of the conversation.\n *\n * @param {Object} conversation The conversation\n * @param {Number} limit How many members to load\n * @param {Number} offset How many memebers to skip\n * @return {Function} the callback.\n */\n var getLoadMembersCallback = function(conversation, limit, offset) {\n return function(root, userId) {\n return Repository.getConversationMembers(conversation.id, userId, limit + 1, offset)\n .then(function(members) {\n if (members.length > limit) {\n members = members.slice(0, -1);\n } else {\n LazyLoadList.setLoadedAll(root, true);\n }\n\n offset = offset + limit;\n\n // Filter out the logged in user so that they don't appear in the list.\n return members.filter(function(member) {\n return member.id != userId;\n });\n });\n };\n };\n\n /**\n * Function to render the members in the list.\n *\n * @param {Object} contentContainer The list content container.\n * @param {Array} members The list of members to render\n * @return {Object} jQuery promise\n */\n var renderMembersCallback = function(contentContainer, members) {\n return Templates.render(TEMPLATES.MEMBERS_LIST, {contacts: members})\n .then(function(html) {\n contentContainer.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 container element.\n * @param {Object} body Contact body container element.\n * @param {Object} footer Contact body container element.\n * @param {Number} conversation The conversation\n * @param {Number} loggedInUserId The logged in user id\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, conversation, loggedInUserId) {\n var root = $(body);\n\n getContentContainer(root).empty();\n return render(root, conversation, loggedInUserId)\n .then(function() {\n var listRoot = LazyLoadList.getRoot(root);\n LazyLoadList.show(\n listRoot,\n getLoadMembersCallback(conversation, LOAD_MEMBERS_LIMIT, 0),\n renderMembersCallback\n );\n return;\n });\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @param {Object} root Contact container element.\n * @param {Number} conversation The conversation\n * @return {Object} jQuery promise\n */\n var description = function(root, conversation) {\n return Str.get_string('messagedrawerviewgroupinfo', 'core_message', conversation.name);\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","Str","Templates","Repository","LazyLoadList","SELECTORS","TEMPLATES","getContentContainer","root","find","renderMembersCallback","contentContainer","members","render","contacts","then","html","append","show","namespace","header","body","footer","conversation","loggedInUserId","empty","placeholderCount","totalMemberCount","placeholders","Array","apply","map","templateContext","name","subname","imageurl","imageUrl","loggedinuser","id","listRoot","getRoot","limit","offset","userId","getConversationMembers","length","slice","setLoadedAll","filter","member","getLoadMembersCallback","description","get_string"],"mappings":";;;;;;;AAsBAA,qDACA,CACI,SACA,WACA,iBACA,kCACA,+CAEJ,SACIC,EACAC,IACAC,UACAC,WACAC,kBAKIC,4BACmB,+CAInBC,kBACS,2DADTA,uBAEc,gEASdC,oBAAsB,SAASC,aACxBA,KAAKC,KAAKJ,8BAoEjBK,sBAAwB,SAASC,iBAAkBC,gBAC5CV,UAAUW,OAAOP,uBAAwB,CAACQ,SAAUF,UACtDG,MAAK,SAASC,aACXL,iBAAiBM,OAAOD,MACjBA,eA0CZ,CACHE,KA5BO,SAASC,UAAWC,OAAQC,KAAMC,OAAQC,aAAcC,oBAC3DhB,KAAOR,EAAEqB,aAEbd,oBAAoBC,MAAMiB,QA/EjB,SAASjB,KAAMe,aAAcC,oBAClCE,iBAAmBH,aAAaI,iBAAmB,GAAK,GAAKJ,aAAaI,iBAC1EC,aAAeC,MAAMC,MAAM,KAAMD,MAAMH,mBAAmBK,KAAI,kBACvD,KAEPC,gBAAkB,CAClBC,KAAMV,aAAaU,KACnBC,QAASX,aAAaW,QACtBC,SAAUZ,aAAaa,SACvBR,aAAcA,aACdS,aAAc,CACVC,GAAId,wBAILtB,UAAUW,OAAOP,kBAAmB0B,iBACtCjB,MAAK,SAASC,aACXT,oBAAoBC,MAAMS,OAAOD,MAC1BA,QA8DRH,CAAOL,KAAMe,aAAcC,gBAC7BT,MAAK,eACEwB,SAAWnC,aAAaoC,QAAQhC,MACpCJ,aAAac,KACTqB,SAtDa,SAAShB,aAAckB,MAAOC,eAChD,SAASlC,KAAMmC,eACXxC,WAAWyC,uBAAuBrB,aAAae,GAAIK,OAAQF,MAAQ,EAAGC,QACxE3B,MAAK,SAASH,gBACPA,QAAQiC,OAASJ,MACjB7B,QAAUA,QAAQkC,MAAM,GAAI,GAE5B1C,aAAa2C,aAAavC,MAAM,GAGpCkC,QAAkBD,MAGX7B,QAAQoC,QAAO,SAASC,eACpBA,OAAOX,IAAMK,cAyCxBO,CAAuB3B,aAnHd,GAmHgD,GACzDb,2BAmBZyC,YANc,SAAS3C,KAAMe,qBACtBtB,IAAImD,WAAW,6BAA8B,eAAgB7B,aAAaU"}PKB\ & &&build/toggle_contact_button.min.js.mapnu[{"version":3,"file":"toggle_contact_button.min.js","sources":["../src/toggle_contact_button.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to add/remove contact using ajax.\n *\n * @module core_message/toggle_contact_button\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/custom_interaction_events'],\n function($, Ajax, Templates, Notification, CustomEvents) {\n\n /**\n * Check the state of the element, if the current user is a contact or not.\n *\n * @method isContact\n * @param {object} element jQuery object for the button\n * @return {bool}\n */\n var isContact = function(element) {\n return element.attr('data-is-contact') == '1';\n };\n\n /**\n * Check the state of the element, if the current user has sent a contact request or not.\n *\n * @method isRequested\n * @param {object} element jQuery object for the button\n * @return {bool}\n */\n let isRequested = (element) => element.attr('data-is-requested') == '1';\n\n /**\n * Record that the user has sent a contact request.\n *\n * @method setContactRequested\n * @param {object} element jQuery object for the button\n */\n var setContactRequested = function(element) {\n element.attr('data-is-requested', '1');\n };\n\n /**\n * Record that the user is not a contact.\n *\n * @method setNotContact\n * @param {object} element jQuery object for the button\n */\n var setNotContact = function(element) {\n element.attr('data-is-contact', '0');\n };\n\n /**\n * Get the id for the user being viewed.\n *\n * @method getUserId\n * @param {object} element jQuery object for the button\n * @return {int}\n */\n var getUserId = function(element) {\n return element.attr('data-userid');\n };\n\n /**\n * Get the id for the logged in user.\n *\n * @method getUserId\n * @param {object} element jQuery object for the button\n * @return {int}\n */\n var getCurrentUserId = function(element) {\n return element.attr('data-currentuserid');\n };\n\n /**\n * Check whether a text label should be displayed or not.\n *\n * @method getUserId\n * @param {object} element jQuery object for the button\n * @return {int}\n */\n var displayTextLabel = function(element) {\n return element.attr('data-display-text-label') == '1';\n };\n\n /**\n * Check if this element is currently loading.\n *\n * @method isLoading\n * @param {object} element jQuery object for the button\n * @return {bool}\n */\n var isLoading = function(element) {\n return element.hasClass('loading') || element.attr('disabled');\n };\n\n /**\n * Sends an ajax request to the server and handles the element state\n * while the request is being performed.\n *\n * @method sendRequest\n * @param {object} element jQuery object for the button\n * @param {object} request Request hash to send\n * @return {object} jQuery promise\n */\n var sendRequest = function(element, request) {\n if (isLoading(element)) {\n return $.Deferred();\n }\n\n element.addClass('loading');\n element.attr('disabled', 'disabled');\n\n return Ajax.call([request])[0]\n .fail(Notification.exception)\n .always(function() {\n element.removeClass('loading');\n element.removeAttr('disabled');\n });\n };\n\n /**\n * Send a request to the server to add the current user as\n * a contact. The contents of the button are changed to the\n * remove contact button upon success.\n *\n * @method addContact\n * @param {object} element jQuery object for the button\n */\n var addContact = function(element) {\n if (isLoading(element)) {\n return;\n }\n\n var request = {\n methodname: 'core_message_create_contact_request',\n args: {\n userid: getCurrentUserId(element),\n requesteduserid: getUserId(element),\n }\n };\n sendRequest(element, request).done(function() {\n setContactRequested(element);\n element.addClass('disabled border-0');\n const templateContext = {\n 'displaytextlabel': displayTextLabel(element)\n };\n Templates.render('message/contact_request_sent', templateContext).done(function(html, js) {\n Templates.replaceNodeContents(element, html, js);\n });\n });\n };\n\n /**\n * Send a request to the server to remove the current user as\n * a contact. The contents of the button are changed to the\n * add contact button upon success.\n *\n * @method removeContact\n * @param {object} element jQuery object for the button\n */\n var removeContact = function(element) {\n if (isLoading(element)) {\n return;\n }\n\n var request = {\n methodname: 'core_message_delete_contacts',\n args: {\n userids: [getUserId(element)],\n }\n };\n\n sendRequest(element, request).done(function() {\n setNotContact(element);\n const templateContext = {\n 'displaytextlabel': displayTextLabel(element)\n };\n Templates.render('message/add_contact_button', templateContext).done(function(html, js) {\n Templates.replaceNodeContents(element, html, js);\n });\n });\n };\n\n /**\n * Enhances the given element with a loading gif and event handles to make\n * ajax requests to add or remove a contact where appropriate.\n *\n * @public\n * @method enhance\n * @param {object} element jQuery object for the button\n */\n var enhance = function(element) {\n element = $(element);\n\n if (!element.children('.loading-icon').length && !isRequested(element)) {\n // Add the loading gif if it isn't already there.\n Templates.render('core/loading', {}).done(function(html, js) {\n element.append(html, js);\n });\n }\n\n CustomEvents.define(element, [CustomEvents.events.activate]);\n\n element.on(CustomEvents.events.activate, function(e, data) {\n if (isContact(element)) {\n removeContact(element);\n } else if (!isRequested(element)) {\n addContact(element);\n }\n e.preventDefault();\n data.originalEvent.preventDefault();\n });\n };\n\n return {\n enhance: enhance\n };\n});\n"],"names":["define","$","Ajax","Templates","Notification","CustomEvents","isRequested","element","attr","getUserId","getCurrentUserId","displayTextLabel","isLoading","hasClass","sendRequest","request","Deferred","addClass","call","fail","exception","always","removeClass","removeAttr","enhance","children","length","render","done","html","js","append","events","activate","on","e","data","isContact","methodname","args","userid","requesteduserid","setContactRequested","templateContext","replaceNodeContents","addContact","userids","setNotContact","removeContact","preventDefault","originalEvent"],"mappings":";;;;;;;AAsBAA,4CAAO,CAAC,SAAU,YAAa,iBAAkB,oBAAqB,mCAC9D,SAASC,EAAGC,KAAMC,UAAWC,aAAcC,kBAoB3CC,YAAeC,SAAiD,KAArCA,QAAQC,KAAK,yBA6BxCC,UAAY,SAASF,gBACdA,QAAQC,KAAK,gBAUpBE,iBAAmB,SAASH,gBACrBA,QAAQC,KAAK,uBAUpBG,iBAAmB,SAASJ,eACsB,KAA3CA,QAAQC,KAAK,4BAUpBI,UAAY,SAASL,gBACdA,QAAQM,SAAS,YAAcN,QAAQC,KAAK,aAYnDM,YAAc,SAASP,QAASQ,gBAC5BH,UAAUL,SACHN,EAAEe,YAGbT,QAAQU,SAAS,WACjBV,QAAQC,KAAK,WAAY,YAElBN,KAAKgB,KAAK,CAACH,UAAU,GACvBI,KAAKf,aAAagB,WAClBC,QAAO,WACJd,QAAQe,YAAY,WACpBf,QAAQgB,WAAW,uBAkGxB,CACHC,QAxBU,SAASjB,UACnBA,QAAUN,EAAEM,UAECkB,SAAS,iBAAiBC,QAAWpB,YAAYC,UAE1DJ,UAAUwB,OAAO,eAAgB,IAAIC,MAAK,SAASC,KAAMC,IACrDvB,QAAQwB,OAAOF,KAAMC,OAI7BzB,aAAaL,OAAOO,QAAS,CAACF,aAAa2B,OAAOC,WAElD1B,QAAQ2B,GAAG7B,aAAa2B,OAAOC,UAAU,SAASE,EAAGC,OAzLzC,SAAS7B,eACqB,KAAnCA,QAAQC,KAAK,mBAyLZ6B,CAAU9B,SAEFD,YAAYC,UA9Ef,SAASA,aAClBK,UAAUL,cAIVQ,QAAU,CACVuB,WAAY,sCACZC,KAAM,CACFC,OAAQ9B,iBAAiBH,SACzBkC,gBAAiBhC,UAAUF,WAGnCO,YAAYP,QAASQ,SAASa,MAAK,YAvGb,SAASrB,SAC/BA,QAAQC,KAAK,oBAAqB,KAuG9BkC,CAAoBnC,SACpBA,QAAQU,SAAS,2BACX0B,gBAAkB,kBACAhC,iBAAiBJ,UAEzCJ,UAAUwB,OAAO,+BAAgCgB,iBAAiBf,MAAK,SAASC,KAAMC,IAClF3B,UAAUyC,oBAAoBrC,QAASsB,KAAMC,WA4D7Ce,CAAWtC,SA/CH,SAASA,aACrBK,UAAUL,cAIVQ,QAAU,CACVuB,WAAY,+BACZC,KAAM,CACFO,QAAS,CAACrC,UAAUF,YAI5BO,YAAYP,QAASQ,SAASa,MAAK,YA7HnB,SAASrB,SACzBA,QAAQC,KAAK,kBAAmB,KA6H5BuC,CAAcxC,eACRoC,gBAAkB,kBACAhC,iBAAiBJ,UAEzCJ,UAAUwB,OAAO,6BAA8BgB,iBAAiBf,MAAK,SAASC,KAAMC,IAChF3B,UAAUyC,oBAAoBrC,QAASsB,KAAMC,WA2B7CkB,CAAczC,SAIlB4B,EAAEc,iBACFb,KAAKc,cAAcD"}PKB\Rjj1build/message_drawer_view_conversation.min.js.mapnu[{"version":3,"file":"message_drawer_view_conversation.min.js","sources":["../src/message_drawer_view_conversation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Controls the conversation page in the message drawer.\n *\n * This function handles all of the user actions that the user can take\n * when interacting with the conversation page.\n *\n * It maintains a view state which is a data representation of the view\n * and only operates on that data.\n *\n * The view state is immutable and should never be modified directly. Instead\n * all changes to the view state should be done using the StateManager which\n * will generate a new version of the view state with the requested changes.\n *\n * After any changes to the view state the module will call the render function\n * to ask the renderer to update the UI.\n *\n * General rules for this module:\n * 1.) Never modify viewState directly. All changes should be via the StateManager.\n * 2.) Call render() with the new state when you want to update the UI\n * 3.) Never modify the UI directly in this module. This module is only concerned\n * with the data in the view state.\n *\n * The general flow for a user interaction will be something like:\n * User interaction: User clicks \"confirm block\" button to block the other user\n * 1.) This module is hears the click\n * 2.) This module sends a request to the server to block the user\n * 3.) The server responds with the new user profile\n * 4.) This module generates a new state using the StateManager with the updated\n * user profile.\n * 5.) This module asks the Patcher to generate a patch from the current state and\n * the newly generated state. This patch tells the renderer what has changed\n * between the states.\n * 6.) This module gives the Renderer the generated patch. The renderer updates\n * the UI with changes according to the patch.\n *\n * @module core_message/message_drawer_view_conversation\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/auto_rows',\n 'core/backoff_timer',\n 'core/custom_interaction_events',\n 'core/notification',\n 'core/pending',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_view_conversation_constants',\n 'core_message/message_drawer_view_conversation_patcher',\n 'core_message/message_drawer_view_conversation_renderer',\n 'core_message/message_drawer_view_conversation_state_manager',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core/emoji/auto_complete',\n 'core/emoji/picker'\n],\nfunction(\n $,\n AutoRows,\n BackOffTimer,\n CustomEvents,\n Notification,\n Pending,\n PubSub,\n Str,\n Repository,\n MessageDrawerEvents,\n Constants,\n Patcher,\n Renderer,\n StateManager,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n initialiseEmojiAutoComplete,\n initialiseEmojiPicker\n) {\n\n // Contains a cache of all view states that have been loaded so far\n // which saves us having to reload stuff with network requests when\n // switching between conversations.\n var stateCache = {};\n // The current data representation of the view.\n var viewState = null;\n var loadedAllMessages = false;\n var messagesOffset = 0;\n var newMessagesPollTimer = null;\n var isRendering = false;\n var renderBuffer = [];\n // If the UI is currently resetting.\n var isResetting = true;\n // If the UI is currently sending a message.\n var isSendingMessage = false;\n // If the UI is currently deleting a conversation.\n var isDeletingConversationContent = false;\n // A buffer of messages to send.\n var sendMessageBuffer = [];\n // These functions which will be generated when this module is\n // first called. See generateRenderFunction for details.\n var render = null;\n // The list of renderers that have been registered to render\n // this conversation. See generateRenderFunction for details.\n var renderers = [];\n\n var NEWEST_FIRST = Constants.NEWEST_MESSAGES_FIRST;\n var LOAD_MESSAGE_LIMIT = Constants.LOAD_MESSAGE_LIMIT;\n var MILLISECONDS_IN_SEC = Constants.MILLISECONDS_IN_SEC;\n var SELECTORS = Constants.SELECTORS;\n var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;\n\n /**\n * Get the other user userid.\n *\n * @return {Number} Userid.\n */\n var getOtherUserId = function() {\n if (!viewState || viewState.type == CONVERSATION_TYPES.PUBLIC) {\n return null;\n }\n\n var loggedInUserId = viewState.loggedInUserId;\n if (viewState.type == CONVERSATION_TYPES.SELF) {\n // It's a self-conversation, so the other user is the one logged in.\n return loggedInUserId;\n }\n\n var otherUserIds = Object.keys(viewState.members).filter(function(userId) {\n return loggedInUserId != userId;\n });\n\n return otherUserIds.length ? otherUserIds[0] : null;\n };\n\n /**\n * Search the cache to see if we've already loaded a private conversation\n * with the given user id.\n *\n * @param {Number} userId The id of the other user.\n * @return {Number|null} Conversation id.\n */\n var getCachedPrivateConversationIdFromUserId = function(userId) {\n return Object.keys(stateCache).reduce(function(carry, id) {\n if (!carry) {\n var state = stateCache[id].state;\n\n if (state.type != CONVERSATION_TYPES.PUBLIC) {\n if (userId in state.members) {\n // We've found a cached conversation for this user!\n carry = state.id;\n }\n }\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Get profile info for logged in user.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object}\n */\n var getLoggedInUserProfile = function(body) {\n return {\n id: parseInt(body.attr('data-user-id'), 10),\n fullname: null,\n profileimageurl: null,\n profileimageurlsmall: null,\n isonline: null,\n showonlinestatus: null,\n isblocked: null,\n iscontact: null,\n isdeleted: null,\n canmessage: null,\n canmessageevenifblocked: null,\n requirescontact: null,\n contactrequests: [],\n cancreatecontact: null,\n };\n };\n\n /**\n * Get the messages offset value to load more messages.\n *\n * @return {Number}\n */\n var getMessagesOffset = function() {\n return messagesOffset;\n };\n\n /**\n * Set the messages offset value for loading more messages.\n *\n * @param {Number} value The offset value\n */\n var setMessagesOffset = function(value) {\n messagesOffset = value;\n stateCache[viewState.id].messagesOffset = value;\n };\n\n /**\n * Check if all messages have been loaded.\n *\n * @return {Bool}\n */\n var hasLoadedAllMessages = function() {\n return loadedAllMessages;\n };\n\n /**\n * Set whether all messages have been loaded or not.\n *\n * @param {Bool} value If all messages have been loaded.\n */\n var setLoadedAllMessages = function(value) {\n loadedAllMessages = value;\n stateCache[viewState.id].loadedAllMessages = value;\n };\n\n /**\n * Get the messages container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getMessagesContainer = function(body) {\n return body.find(SELECTORS.MESSAGES_CONTAINER);\n };\n\n /**\n * Reformat the conversation for an event payload.\n *\n * @param {Object} state The view state.\n * @return {Object} New formatted conversation.\n */\n var formatConversationForEvent = function(state) {\n return {\n id: state.id,\n name: state.name,\n subname: state.subname,\n imageUrl: state.imageUrl,\n isFavourite: state.isFavourite,\n isMuted: state.isMuted,\n type: state.type,\n totalMemberCount: state.totalMemberCount,\n loggedInUserId: state.loggedInUserId,\n messages: state.messages.map(function(message) {\n return $.extend({}, message);\n }),\n members: Object.keys(state.members).map(function(id) {\n var formattedMember = $.extend({}, state.members[id]);\n formattedMember.contactrequests = state.members[id].contactrequests.map(function(request) {\n return $.extend({}, request);\n });\n return formattedMember;\n })\n };\n };\n\n /**\n * Load up an empty private conversation between the logged in user and the\n * other user. Sets all of the conversation details based on the other user.\n *\n * A conversation isn't created until the user sends the first message.\n *\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} otherUserId The other user id.\n * @return {Object} Profile returned from repository.\n */\n var loadEmptyPrivateConversation = function(loggedInUserProfile, otherUserId) {\n var loggedInUserId = loggedInUserProfile.id;\n // If the other user id is the same as the logged in user then this is a self\n // conversation.\n var conversationType = loggedInUserId == otherUserId ? CONVERSATION_TYPES.SELF : CONVERSATION_TYPES.PRIVATE;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true)\n .then(function(profiles) {\n if (profiles.length) {\n return profiles[0];\n } else {\n throw new Error('Unable to load other user profile');\n }\n })\n .then(function(profile) {\n // If the conversation is a self conversation then the profile loaded is the\n // logged in user so only add that to the members array.\n var members = conversationType == CONVERSATION_TYPES.SELF ? [profile] : [profile, loggedInUserProfile];\n var newState = StateManager.addMembers(viewState, members);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setName(newState, profile.fullname);\n newState = StateManager.setType(newState, conversationType);\n newState = StateManager.setImageUrl(newState, profile.profileimageurl);\n newState = StateManager.setTotalMemberCount(newState, members.length);\n render(newState);\n return profile;\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a new state from a conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Number} loggedInUserId The logged in user id.\n * @return {Object} new state.\n */\n var updateStateFromConversation = function(conversation, loggedInUserId) {\n var otherUser = null;\n if (conversation.type == CONVERSATION_TYPES.PRIVATE) {\n // For private conversations, remove current logged in user from the members list to get the other user.\n var otherUsers = conversation.members.filter(function(member) {\n return member.id != loggedInUserId;\n });\n otherUser = otherUsers.length ? otherUsers[0] : null;\n } else if (conversation.type == CONVERSATION_TYPES.SELF) {\n // Self-conversations have only one member.\n otherUser = conversation.members[0];\n }\n\n var name = conversation.name;\n var imageUrl = conversation.imageurl;\n\n if (conversation.type != CONVERSATION_TYPES.PUBLIC) {\n name = name || otherUser ? otherUser.fullname : '';\n imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : '';\n }\n\n var newState = StateManager.addMembers(viewState, conversation.members);\n newState = StateManager.setName(newState, name);\n newState = StateManager.setSubname(newState, conversation.subname);\n newState = StateManager.setType(newState, conversation.type);\n newState = StateManager.setImageUrl(newState, imageUrl);\n newState = StateManager.setTotalMemberCount(newState, conversation.membercount);\n newState = StateManager.setIsFavourite(newState, conversation.isfavourite);\n newState = StateManager.setIsMuted(newState, conversation.ismuted);\n newState = StateManager.addMessages(newState, conversation.messages);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, conversation.candeletemessagesforallusers);\n return newState;\n };\n\n /**\n * Get the details for a conversation from the conversation id.\n *\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Number} messageOffset The number of messages to skip.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadNewConversation = function(\n conversationId,\n loggedInUserProfile,\n messageLimit,\n messageOffset,\n newestFirst\n ) {\n var loggedInUserId = loggedInUserProfile.id;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getConversation(\n loggedInUserId,\n conversationId,\n true,\n true,\n 0,\n 0,\n messageLimit + 1,\n messageOffset,\n newestFirst\n )\n .then(function(conversation) {\n if (conversation.messages.length > messageLimit) {\n conversation.messages = conversation.messages.slice(1);\n } else {\n setLoadedAllMessages(true);\n }\n\n setMessagesOffset(messageOffset + messageLimit);\n\n return conversation;\n })\n .then(function(conversation) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return conversation;\n });\n })\n .then(function() {\n return markConversationAsRead(conversationId);\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Get the details for a conversation from and existing conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadExistingConversation = function(\n conversation,\n loggedInUserProfile,\n messageLimit,\n newestFirst\n ) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var messageCount = conversation.messages.length;\n var hasLoadedEnoughMessages = messageCount >= messageLimit;\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, !hasLoadedEnoughMessages);\n var renderPromise = render(newState);\n\n return renderPromise.then(function() {\n if (!hasLoadedEnoughMessages) {\n // We haven't got enough messages so let's load some more.\n return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []);\n } else {\n // We've got enough messages. No need to load any more for now.\n return {messages: conversation.messages};\n }\n })\n .then(function() {\n var messages = viewState.messages;\n // Update the offset to reflect the number of messages we've loaded.\n setMessagesOffset(messages.length);\n markConversationAsRead(viewState.id);\n\n return messages;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Load messages for this conversation and pass them to the renderer.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Number} limit Number of messages to load.\n * @param {Number} offset Get messages from offset.\n * @param {Bool} newestFirst Get newest messages first.\n * @param {Array} ignoreList Ignore any messages with ids in this list.\n * @param {Number|null} timeFrom Only get messages from this time onwards.\n * @return {Promise} renderer promise.\n */\n var loadMessages = function(conversationId, limit, offset, newestFirst, ignoreList, timeFrom) {\n return Repository.getMessages(\n viewState.loggedInUserId,\n conversationId,\n limit ? limit + 1 : limit,\n offset,\n newestFirst,\n timeFrom\n )\n .then(function(result) {\n // Prevent older requests from contaminating the current view.\n if (result.id != viewState.id) {\n result.messages = [];\n // Purge old conversation cache to prevent messages lose.\n if (result.id in stateCache) {\n delete stateCache[result.id];\n }\n }\n\n return result;\n })\n .then(function(result) {\n if (result.messages.length && ignoreList.length) {\n result.messages = result.messages.filter(function(message) {\n // Skip any messages in our ignore list.\n return ignoreList.indexOf(parseInt(message.id, 10)) < 0;\n });\n }\n\n return result;\n })\n .then(function(result) {\n if (!limit) {\n return result;\n } else if (result.messages.length > limit) {\n // Ignore the last result which was just to test if there are more\n // to load.\n result.messages = result.messages.slice(0, -1);\n } else {\n setLoadedAllMessages(true);\n }\n\n return result;\n })\n .then(function(result) {\n var membersToAdd = result.members.filter(function(member) {\n return !(member.id in viewState.members);\n });\n var newState = StateManager.addMembers(viewState, membersToAdd);\n newState = StateManager.addMessages(newState, result.messages);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return result;\n });\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMessages(viewState, false);\n render(newState);\n // Re-throw the error for other error handlers.\n throw error;\n });\n };\n\n /**\n * Create a callback function for getting new messages for this conversation.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Bool} newestFirst Show newest messages first\n * @return {Function} Callback function that returns a renderer promise.\n */\n var getLoadNewMessagesCallback = function(conversationId, newestFirst) {\n return function() {\n var messages = viewState.messages;\n var mostRecentMessage = messages.length ? messages[messages.length - 1] : null;\n var lastTimeCreated = mostRecentMessage ? mostRecentMessage.timeCreated : null;\n\n if (lastTimeCreated && !isResetting && !isSendingMessage && !isDeletingConversationContent) {\n // There may be multiple messages with the same time created value since\n // the accuracy is only down to the second. The server will include these\n // messages in the result (since it does a >= comparison on time from) so\n // we need to filter them back out of the result so that we're left only\n // with the new messages.\n var ignoreMessageIds = [];\n for (var i = messages.length - 1; i >= 0; i--) {\n var message = messages[i];\n if (message.timeCreated === lastTimeCreated) {\n ignoreMessageIds.push(message.id);\n } else {\n // Since the messages are ordered in ascending order of time created\n // we can break as soon as we hit a message with a different time created\n // because we know all other messages will have lower values.\n break;\n }\n }\n\n return loadMessages(\n conversationId,\n 0,\n 0,\n newestFirst,\n ignoreMessageIds,\n lastTimeCreated\n )\n .then(function(result) {\n if (result.messages.length) {\n // If we found some results then restart the polling timer\n // because the other user might be sending messages.\n newMessagesPollTimer.restart();\n // We've also got a new last message so publish that for other\n // components to update.\n var conversation = formatConversationForEvent(viewState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return markConversationAsRead(conversationId);\n } else {\n return result;\n }\n });\n }\n\n return $.Deferred().resolve().promise();\n };\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Number} conversationId The conversation id.\n * @return {Promise} The renderer promise.\n */\n var markConversationAsRead = function(conversationId) {\n var loggedInUserId = viewState.loggedInUserId;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.markAllConversationMessagesAsRead(loggedInUserId, conversationId)\n .then(function() {\n var newState = StateManager.markMessagesAsRead(viewState, viewState.messages);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_READ, conversationId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is request to block a user and run the renderer\n * to show the block user dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestBlockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingBlockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to block a user, update the statemanager and publish\n * a contact has been blocked.\n *\n * @param {Number} userId User id of user to block.\n * @return {Promise} Renderer promise.\n */\n var blockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:blockUser');\n\n render(newState);\n\n return Repository.blockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_BLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to unblock a user and run the renderer\n * to show the unblock user dialogue.\n *\n * @param {Number} userId User id of user to unblock.\n */\n var requestUnblockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to unblock a user, update the statemanager and publish\n * a contact has been unblocked.\n *\n * @param {Number} userId User id of user to unblock.\n * @return {Promise} Renderer promise.\n */\n var unblockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unblockUser');\n render(newState);\n\n return Repository.unblockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_UNBLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to remove a user from the contact list\n * and run the renderer to show the remove user from contacts dialogue.\n *\n * @param {Number} userId User id of user to remove from contacts.\n */\n var requestRemoveContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to remove a user from the contacts list. update the statemanager\n * and publish a contact has been removed.\n *\n * @param {Number} userId User id of user to remove from contacts.\n * @return {Promise} Renderer promise.\n */\n var removeContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:removeContact');\n render(newState);\n\n return Repository.deleteContacts(viewState.loggedInUserId, [userId])\n .then(function(profiles) {\n var newState = StateManager.addMembers(viewState, profiles);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_REMOVED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to add a user to the contact list\n * and run the renderer to show the add user to contacts dialogue.\n *\n * @param {Number} userId User id of user to add to contacts.\n */\n var requestAddContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingAddContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to add a user to the contacts list. update the statemanager\n * and publish a contact has been added.\n *\n * @param {Number} userId User id of user to add to contacts.\n * @return {Promise} Renderer promise.\n */\n var addContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:addContactRequests');\n render(newState);\n\n return Repository.createContactRequest(viewState.loggedInUserId, userId)\n .then(function(response) {\n if (!response.request) {\n throw new Error(response.warnings[0].message);\n }\n\n return response.request;\n })\n .then(function(request) {\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.addContactRequests(newState, [request]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:setFavourite');\n\n return Repository.setFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unsetFavourite');\n\n return Repository.unsetFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.setMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_MUTED,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n\n return Repository.unsetMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_MUTED,\n formatConversationForEvent(viewState)\n );\n });\n };\n\n /**\n * Tell the statemanager there is a request to delete the selected messages\n * and run the renderer to show confirm delete messages dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestDeleteSelectedMessages = function(userId) {\n var selectedMessageIds = viewState.selectedMessageIds;\n cancelRequest(userId);\n var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete the messages pending deletion. Update the statemanager\n * and publish a message deletion event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteSelectedMessages = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:deleteSelectedMessages');\n var messageIds = viewState.pendingDeleteMessageIds;\n var sentMessages = viewState.messages.filter(function(message) {\n // If a message sendState is null then it means it was loaded from the server or if it's\n // set to sent then it means the user has successfully sent it in this page load.\n return messageIds.indexOf(message.id) >= 0 && (message.sendState == 'sent' || message.sendState === null);\n });\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n\n render(newState);\n\n var deleteMessagesPromise = $.Deferred().resolve().promise();\n\n\n if (sentMessages.length) {\n // We only need to send a request to the server if we're trying to delete messages that\n // have successfully been sent.\n var sentMessageIds = sentMessages.map(function(message) {\n return message.id;\n });\n if (newState.deleteMessagesForAllUsers) {\n deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, sentMessageIds);\n } else {\n deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, sentMessageIds);\n }\n }\n\n // Mark that we are deleting content from the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return deleteMessagesPromise.then(function() {\n var newState = StateManager.removeMessagesById(viewState, messageIds);\n newState = StateManager.removePendingDeleteMessagesById(newState, messageIds);\n newState = StateManager.removeSelectedMessagesById(newState, messageIds);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n\n var prevLastMessage = viewState.messages[viewState.messages.length - 1];\n var newLastMessage = newState.messages.length ? newState.messages[newState.messages.length - 1] : null;\n\n if (newLastMessage && newLastMessage.id != prevLastMessage.id) {\n var conversation = formatConversationForEvent(newState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n } else if (!newState.messages.length) {\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n }\n\n isDeletingConversationContent = false;\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Tell the statemanager there is a request to delete a conversation\n * and run the renderer to show confirm delete conversation dialogue.\n *\n * @param {Number} userId User id of other user.\n */\n var requestDeleteConversation = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.setPendingDeleteConversation(viewState, true);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete a conversation. Update the statemanager\n * and publish a conversation deleted event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteConversation = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n // Mark that we are deleting the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return Repository.deleteConversation(viewState.loggedInUserId, viewState.id)\n .then(function() {\n var newState = StateManager.removeMessages(viewState, viewState.messages);\n newState = StateManager.removeSelectedMessagesById(newState, viewState.selectedMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n\n isDeletingConversationContent = false;\n\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager to cancel all pending actions.\n *\n * @param {Number} userId User id.\n */\n var cancelRequest = function(userId) {\n var pendingDeleteMessageIds = viewState.pendingDeleteMessageIds;\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.removePendingDeleteMessagesById(newState, pendingDeleteMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n render(newState);\n };\n\n /**\n * Accept the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var acceptContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:acceptContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.acceptContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_ADDED, viewState.members[userId]);\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Decline the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var declineContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:declineContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.declineContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Send all of the messages in the buffer to the server to be created. Update the\n * UI with the newly created message information.\n *\n * This function will recursively call itself in order to make sure the buffer is\n * always being processed.\n */\n var processSendMessageBuffer = function() {\n if (isSendingMessage) {\n // We're already sending messages so nothing to do.\n return;\n }\n if (!sendMessageBuffer.length) {\n // No messages waiting to send. Nothing to do.\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:processSendMessageBuffer');\n\n // Flag that we're processing the queue.\n isSendingMessage = true;\n // Grab all of the messages in the buffer.\n var messagesToSend = sendMessageBuffer.slice();\n // Empty the buffer since we're processing it.\n sendMessageBuffer = [];\n var conversationId = viewState.id;\n var newConversationId = null;\n var messagesText = messagesToSend.map(function(message) {\n return message.text;\n });\n var messageIds = messagesToSend.map(function(message) {\n return message.id;\n });\n var sendMessagePromise = null;\n var newCanDeleteMessagesForAllUsers = null;\n if (!conversationId && (viewState.type != CONVERSATION_TYPES.PUBLIC)) {\n // If it's a new private conversation then we need to use the old\n // web service function to create the conversation.\n var otherUserId = getOtherUserId();\n sendMessagePromise = Repository.sendMessagesToUser(otherUserId, messagesText)\n .then(function(messages) {\n if (messages.length) {\n newConversationId = parseInt(messages[0].conversationid, 10);\n newCanDeleteMessagesForAllUsers = messages[0].candeletemessagesforallusers;\n }\n return messages;\n });\n } else {\n sendMessagePromise = Repository.sendMessagesToConversation(conversationId, messagesText);\n }\n\n sendMessagePromise\n .then(function(messages) {\n var newMessageIds = messages.map(function(message) {\n return message.id;\n });\n var data = [];\n var selectedToRemove = [];\n var selectedToAdd = [];\n\n messagesToSend.forEach(function(oldMessage, index) {\n var newMessage = messages[index];\n // Update messages expects and array of arrays where the first value\n // is the old message to update and the second value is the new values\n // to set.\n data.push([oldMessage, newMessage]);\n\n if (viewState.selectedMessageIds.indexOf(oldMessage.id) >= 0) {\n // If the message was added to the \"selected messages\" list while it was still\n // being sent then we should update it's id in that list now to make sure future\n // actions work.\n selectedToRemove.push(oldMessage.id);\n selectedToAdd.push(newMessage.id);\n }\n });\n var newState = StateManager.updateMessages(viewState, data);\n newState = StateManager.setMessagesSendSuccessById(newState, newMessageIds);\n\n if (selectedToRemove.length) {\n newState = StateManager.removeSelectedMessagesById(newState, selectedToRemove);\n }\n\n if (selectedToAdd.length) {\n newState = StateManager.addSelectedMessagesById(newState, selectedToAdd);\n }\n\n var conversation = formatConversationForEvent(newState);\n\n if (!newState.id) {\n // If this message created the conversation then save the conversation\n // id.\n newState = StateManager.setId(newState, newConversationId);\n conversation.id = newConversationId;\n resetMessagePollTimer(newConversationId);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED, conversation);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, newCanDeleteMessagesForAllUsers);\n }\n\n // Update the UI with the new message values from the server.\n render(newState);\n // Recurse just in case there has been more messages added to the buffer.\n isSendingMessage = false;\n processSendMessageBuffer();\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n var errorMessage;\n if (e.message) {\n errorMessage = $.Deferred().resolve(e.message).promise();\n } else {\n errorMessage = Str.get_string('unknownerror', 'core');\n }\n\n var handleFailedMessages = function(errorMessage) {\n // We failed to create messages so remove the old messages from the pending queue\n // and update the UI to indicate that the message failed.\n var newState = StateManager.setMessagesSendFailById(viewState, messageIds, errorMessage);\n render(newState);\n isSendingMessage = false;\n processSendMessageBuffer();\n };\n\n errorMessage.then(handleFailedMessages)\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n // Hrmm, we can't even load the error messages string! We'll have to\n // hard code something in English here if we still haven't got a message\n // to show.\n var finalError = e.message || 'Something went wrong!';\n handleFailedMessages(finalError);\n });\n });\n };\n\n /**\n * Create a plain version of an HTML text.\n *\n * This texts is used as a message preview while is sent to the server. This way\n * it is possible to prevent self-xss.\n *\n * @param {String} text Text to send.\n * @return {String} The plain text version of the text.\n */\n const previewText = function(text) {\n // Remove all script and styles from text (we don't want it there).\n let plaintext = text.replace(//gi, '');\n plaintext = plaintext.replace(//gi, '');\n // Beautify a bit the output adding some line breaks.\n plaintext = plaintext.replace(/<\\/div>/ig, '\\n');\n plaintext = plaintext.replace(/<\\/li>/ig, '\\n');\n plaintext = plaintext.replace(/
  • /ig, ' * ');\n plaintext = plaintext.replace(/<\\/ul>/ig, '\\n');\n plaintext = plaintext.replace(/<\\/p>/ig, '\\n');\n plaintext = plaintext.replace(/]*>/gi, '\\n');\n // Remove all remaining tags and convert line breaks into html.\n plaintext = plaintext.replace(/<[^>]+>/ig, '');\n plaintext = plaintext.replace(/\\n+/ig, '\\n');\n return plaintext.replace(/\\n/ig, '
    ');\n };\n\n /**\n * Buffers messages to be sent to the server. We use a buffer here to allow the\n * user to freely input messages without blocking the interface for them.\n *\n * Instead we just queue all of their messages up and send them as fast as we can.\n *\n * @param {String} text Text to send.\n */\n var sendMessage = function(text) {\n var id = 'temp' + Date.now();\n // Render a preview version of the message while sending.\n let loadingmessage = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: previewText(text),\n timecreated: null\n };\n var newState = StateManager.addMessages(viewState, [loadingmessage]);\n render(newState);\n // Send the real message.\n var message = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: text,\n timecreated: null\n };\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\n\n // Remove the unsent message attribute so we can be notified of new unsent messages (see storeUnsentMessage).\n const textArea = document.querySelector(SELECTORS.MESSAGE_TEXT_AREA);\n if (textArea) {\n textArea.removeAttribute('data-unsent-message-viewed');\n }\n };\n\n /**\n * Retry sending a message that failed.\n *\n * @param {Object} message The message to send.\n */\n var retrySendMessage = function(message) {\n var newState = StateManager.setMessagesSendPendingById(viewState, [message.id]);\n render(newState);\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\n };\n\n /**\n * Toggle the selected messages update the statemanager and render the result.\n *\n * @param {Number} messageId The id of the message to be toggled\n */\n var toggleSelectMessage = function(messageId) {\n var newState = viewState;\n\n if (viewState.selectedMessageIds.indexOf(messageId) > -1) {\n newState = StateManager.removeSelectedMessagesById(viewState, [messageId]);\n } else {\n newState = StateManager.addSelectedMessagesById(viewState, [messageId]);\n }\n\n render(newState);\n };\n\n /**\n * Cancel edit mode (selecting the messages).\n */\n var cancelEditMode = function() {\n cancelRequest(getOtherUserId());\n var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds);\n render(newState);\n };\n\n /**\n * Process the patches in the render buffer one at a time in order until the\n * buffer is empty.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n */\n var processRenderBuffer = function(header, body, footer) {\n if (isRendering) {\n return;\n }\n\n if (!renderBuffer.length) {\n return;\n }\n\n isRendering = true;\n var renderable = renderBuffer.shift();\n var renderPromises = renderers.map(function(renderFunc) {\n return renderFunc(renderable.patch);\n });\n\n $.when.apply(null, renderPromises)\n .then(function() {\n isRendering = false;\n renderable.deferred.resolve(true);\n // Keep processing the buffer until it's empty.\n processRenderBuffer(header, body, footer);\n\n return;\n })\n .catch(function(error) {\n isRendering = false;\n renderable.deferred.reject(error);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a function to render the Conversation.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n * @param {Bool} isNewConversation Has someone else already initialised a conversation?\n * @return {Promise} Renderer promise.\n */\n var generateRenderFunction = function(header, body, footer, isNewConversation) {\n var rendererFunc = function(patch) {\n return Renderer.render(header, body, footer, patch);\n };\n\n if (!isNewConversation) {\n // Looks like someone got here before us! We'd better update our\n // UI to make sure it matches.\n var initialState = StateManager.buildInitialState(viewState.midnight, viewState.loggedInUserId, viewState.id);\n var syncPatch = Patcher.buildPatch(initialState, viewState);\n rendererFunc(syncPatch);\n }\n\n renderers.push(rendererFunc);\n\n return function(newState) {\n var patch = Patcher.buildPatch(viewState, newState);\n var deferred = $.Deferred();\n\n // Check if the patch has any data. Ignore empty patches.\n if (Object.keys(patch).length) {\n // Add the patch to the render buffer which gets processed in order.\n renderBuffer.push({\n patch: patch,\n deferred: deferred\n });\n } else {\n deferred.resolve(true);\n }\n // This is a great place to add in some console logging if you need\n // to debug something. You can log the current state, the next state,\n // and the generated patch and see exactly what will be updated.\n\n // Optimistically update the state. We're going to assume that the rendering\n // will always succeed. The rendering is asynchronous (annoyingly) so it's buffered\n // but it'll reach eventual consistency with the current state.\n viewState = newState;\n if (newState.id) {\n // Only cache created conversations.\n stateCache[newState.id] = {\n state: newState,\n messagesOffset: getMessagesOffset(),\n loadedAllMessages: hasLoadedAllMessages()\n };\n }\n\n // Start processing the buffer.\n processRenderBuffer(header, body, footer);\n\n return deferred.promise();\n };\n };\n\n /**\n * Create a confirm action function.\n *\n * @param {Function} actionCallback The callback function.\n * @return {Function} Confirm action handler.\n */\n var generateConfirmActionHandler = function(actionCallback) {\n return function(e, data) {\n if (!viewState.loadingConfirmAction) {\n actionCallback(getOtherUserId());\n var newState = StateManager.setLoadingConfirmAction(viewState, false);\n render(newState);\n }\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Send message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSendMessage = function(e, data) {\n var target = $(e.target);\n var footerContainer = target.closest(SELECTORS.FOOTER_CONTAINER);\n var textArea = footerContainer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var text = textArea.val().trim();\n\n if (text !== '') {\n sendMessage(text);\n textArea.val('');\n textArea.focus();\n }\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Select message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSelectMessage = function(e, data) {\n var selection = window.getSelection();\n var target = $(e.target);\n\n if (selection.toString() != '') {\n // Bail if we're selecting.\n return;\n }\n\n if (target.is('a')) {\n // Clicking on a link in the message so ignore it.\n return;\n }\n\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n\n toggleSelectMessage(messageId);\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle retry sending of message.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleRetrySendMessage = function(e, data) {\n var target = $(e.target);\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n var messages = viewState.messages.filter(function(message) {\n return message.id == messageId;\n });\n var message = messages.length ? messages[0] : null;\n\n if (message) {\n retrySendMessage(message);\n }\n\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n e.stopPropagation();\n };\n\n /**\n * Cancel edit mode event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleCancelEditMode = function(e, data) {\n cancelEditMode();\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View contact handler.\n */\n var generateHandleViewContact = function(namespace) {\n return function(e, data) {\n var otherUserId = getOtherUserId();\n var otherUser = viewState.members[otherUserId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONTACT, otherUser);\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Set this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetFavourite = function(e, data) {\n setFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetFavourite = function(e, data) {\n unsetFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view group info page.\n * Set this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetMuted = function(e, data) {\n setMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetMuted = function(e, data) {\n unsetMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking on the checkbox that toggles deleting messages for\n * all users.\n *\n * @param {Object} e Element this event handler is called on.\n */\n var handleDeleteMessagesForAllUsersToggle = function(e) {\n var newValue = $(e.target).prop('checked');\n var newState = StateManager.setDeleteMessagesForAllUsers(viewState, newValue);\n render(newState);\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View group info handler.\n */\n var generateHandleViewGroupInfo = function(namespace) {\n return function(e, data) {\n MessageDrawerRouter.go(\n namespace,\n MessageDrawerRoutes.VIEW_GROUP_INFO,\n {\n id: viewState.id,\n name: viewState.name,\n subname: viewState.subname,\n imageUrl: viewState.imageUrl,\n totalMemberCount: viewState.totalMemberCount\n },\n viewState.loggedInUserId\n );\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Handle clicking on the emoji toggle button.\n *\n * @param {Object} e The event\n * @param {Object} data The custom interaction event data\n */\n var handleToggleEmojiPicker = function(e, data) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking outside the emoji picker to close it.\n *\n * @param {Object} e The event\n */\n var handleCloseEmojiPicker = function(e) {\n var target = $(e.target);\n\n if (\n viewState.showEmojiPicker &&\n !target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length &&\n !target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length\n ) {\n var newState = StateManager.setShowEmojiPicker(viewState, false);\n render(newState);\n }\n };\n\n /**\n * Listen to, and handle events for conversations.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n */\n var registerEventListeners = function(namespace, header, body, footer) {\n var isLoadingMoreMessages = false;\n var messagesContainer = getMessagesContainer(body);\n var emojiPickerElement = footer.find(SELECTORS.EMOJI_PICKER);\n var emojiAutoCompleteContainer = footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);\n var messageTextArea = footer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var headerActivateHandlers = [\n [SELECTORS.ACTION_REQUEST_BLOCK, generateConfirmActionHandler(requestBlockUser)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_REMOVE_CONTACT, generateConfirmActionHandler(requestRemoveContact)],\n [SELECTORS.ACTION_REQUEST_DELETE_CONVERSATION, generateConfirmActionHandler(requestDeleteConversation)],\n [SELECTORS.ACTION_CANCEL_EDIT_MODE, handleCancelEditMode],\n [SELECTORS.ACTION_VIEW_CONTACT, generateHandleViewContact(namespace)],\n [SELECTORS.ACTION_VIEW_GROUP_INFO, generateHandleViewGroupInfo(namespace)],\n [SELECTORS.ACTION_CONFIRM_FAVOURITE, handleSetFavourite],\n [SELECTORS.ACTION_CONFIRM_MUTE, handleSetMuted],\n [SELECTORS.ACTION_CONFIRM_UNFAVOURITE, handleUnsetFavourite],\n [SELECTORS.ACTION_CONFIRM_UNMUTE, handleUnsetMuted]\n ];\n var bodyActivateHandlers = [\n [SELECTORS.ACTION_CANCEL_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_CONFIRM_BLOCK, generateConfirmActionHandler(blockUser)],\n [SELECTORS.ACTION_CONFIRM_UNBLOCK, generateConfirmActionHandler(unblockUser)],\n [SELECTORS.ACTION_CONFIRM_ADD_CONTACT, generateConfirmActionHandler(addContact)],\n [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT, generateConfirmActionHandler(removeContact)],\n [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(deleteSelectedMessages)],\n [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION, generateConfirmActionHandler(deleteConversation)],\n [SELECTORS.ACTION_OKAY_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST, generateConfirmActionHandler(acceptContactRequest)],\n [SELECTORS.ACTION_DECLINE_CONTACT_REQUEST, generateConfirmActionHandler(declineContactRequest)],\n [SELECTORS.MESSAGE, handleSelectMessage],\n [SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE, handleDeleteMessagesForAllUsersToggle],\n [SELECTORS.RETRY_SEND, handleRetrySendMessage]\n ];\n var footerActivateHandlers = [\n [SELECTORS.SEND_MESSAGE_BUTTON, handleSendMessage],\n [SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON, handleToggleEmojiPicker],\n [SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(requestDeleteSelectedMessages)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n ];\n\n AutoRows.init(footer);\n\n if (emojiAutoCompleteContainer.length) {\n initialiseEmojiAutoComplete(\n emojiAutoCompleteContainer[0],\n messageTextArea[0],\n function(hasSuggestions) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, hasSuggestions);\n render(newState);\n },\n function(emoji) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, false);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos).replace(/\\S*$/, '');\n var textAfter = currentText.substring(cursorPos).replace(/^\\S*/, '');\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', textBefore.length + emoji.length);\n messageTextArea.prop('selectionEnd', textBefore.length + emoji.length);\n }\n );\n }\n\n if (emojiPickerElement.length) {\n initialiseEmojiPicker(emojiPickerElement[0], function(emoji) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos);\n var textAfter = currentText.substring(cursorPos, currentText.length);\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', cursorPos + emoji.length);\n messageTextArea.prop('selectionEnd', cursorPos + emoji.length);\n });\n }\n\n CustomEvents.define(header, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(body, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(footer, [\n CustomEvents.events.activate,\n CustomEvents.events.enter,\n CustomEvents.events.escape\n ]);\n CustomEvents.define(messagesContainer, [\n CustomEvents.events.scrollTop,\n CustomEvents.events.scrollLock\n ]);\n\n messagesContainer.on(CustomEvents.events.scrollTop, function(e, data) {\n var hasMembers = Object.keys(viewState.members).length > 1;\n\n if (!isResetting && !isLoadingMoreMessages && !hasLoadedAllMessages() && hasMembers) {\n isLoadingMoreMessages = true;\n var newState = StateManager.setLoadingMessages(viewState, true);\n render(newState);\n\n loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, [])\n .then(function() {\n isLoadingMoreMessages = false;\n setMessagesOffset(getMessagesOffset() + LOAD_MESSAGE_LIMIT);\n return;\n })\n .catch(function(error) {\n isLoadingMoreMessages = false;\n Notification.exception(error);\n });\n }\n\n data.originalEvent.preventDefault();\n });\n\n headerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n header.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n bodyActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n body.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n footer.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footer.on(CustomEvents.events.enter, SELECTORS.MESSAGE_TEXT_AREA, function(e, data) {\n var enterToSend = footer.attr('data-enter-to-send');\n if (enterToSend && enterToSend != 'false' && enterToSend != '0') {\n handleSendMessage(e, data);\n }\n });\n\n footer.on(CustomEvents.events.escape, SELECTORS.EMOJI_PICKER_CONTAINER, handleToggleEmojiPicker);\n $(document.body).on('click', handleCloseEmojiPicker);\n\n PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED, function(newRouteData) {\n if (newMessagesPollTimer) {\n if (newRouteData.route != MessageDrawerRoutes.VIEW_CONVERSATION) {\n newMessagesPollTimer.stop();\n }\n }\n });\n };\n\n /**\n * Reset the timer that polls for new messages.\n *\n * @param {Number} conversationId The conversation id\n */\n var resetMessagePollTimer = function(conversationId) {\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n newMessagesPollTimer = new BackOffTimer(\n getLoadNewMessagesCallback(conversationId, NEWEST_FIRST),\n BackOffTimer.getIncrementalCallback(\n viewState.messagePollMin * MILLISECONDS_IN_SEC,\n MILLISECONDS_IN_SEC,\n viewState.messagePollMax * MILLISECONDS_IN_SEC,\n viewState.messagePollAfterMax * MILLISECONDS_IN_SEC\n )\n );\n\n newMessagesPollTimer.start();\n };\n\n /**\n * Reset the state to the initial state and render the UI.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number|null} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n */\n var resetState = function(body, conversationId, loggedInUserProfile) {\n // Reset all of the states back to the beginning if we're loading a new\n // conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n loadedAllMessages = false;\n messagesOffset = 0;\n newMessagesPollTimer = null;\n isRendering = false;\n renderBuffer = [];\n isResetting = true;\n isSendingMessage = false;\n isDeletingConversationContent = false;\n sendMessageBuffer = [];\n\n var loggedInUserId = loggedInUserProfile.id;\n var midnight = parseInt(body.attr('data-midnight'), 10);\n var messagePollMin = parseInt(body.attr('data-message-poll-min'), 10);\n var messagePollMax = parseInt(body.attr('data-message-poll-max'), 10);\n var messagePollAfterMax = parseInt(body.attr('data-message-poll-after-max'), 10);\n var initialState = StateManager.buildInitialState(\n midnight,\n loggedInUserId,\n conversationId,\n messagePollMin,\n messagePollMax,\n messagePollAfterMax\n );\n\n if (!viewState) {\n viewState = initialState;\n }\n\n render(initialState);\n };\n\n /**\n * Load a new empty private conversation between two users or self-conversation.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @param {Int} otherUserId The other user's id.\n * @return {Promise} Renderer promise.\n */\n var resetNoConversation = function(body, loggedInUserProfile, otherUserId) {\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, null, loggedInUserProfile);\n\n var resetNoConversationPromise = null;\n\n if (loggedInUserProfile.id != otherUserId) {\n // Private conversation between two different users.\n resetNoConversationPromise = Repository.getConversationBetweenUsers(\n loggedInUserProfile.id,\n otherUserId,\n true,\n true,\n 0,\n 0,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n } else {\n // Self conversation.\n resetNoConversationPromise = Repository.getSelfConversation(\n loggedInUserProfile.id,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return resetNoConversationPromise.then(function(conversation) {\n // Looks like we have a conversation after all! Let's use that.\n return resetByConversation(body, conversation, loggedInUserProfile);\n })\n .catch(function() {\n // Can't find a conversation. Oh well. Just load up a blank one.\n return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetById = function(body, conversationId, loggedInUserProfile) {\n var cache = null;\n if (conversationId in stateCache) {\n cache = stateCache[conversationId];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversationId, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadNewConversation(\n conversationId,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversationId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} conversation The conversation.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetByConversation = function(body, conversation, loggedInUserProfile) {\n var cache = null;\n if (conversation.id in stateCache) {\n cache = stateCache[conversation.id];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversation.id, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadExistingConversation(\n conversation,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversation.id);\n });\n };\n\n /**\n * Setup the conversation page. This is a rather complex function because there are a\n * few combinations of arguments that can be provided to this function to show the\n * conversation.\n *\n * There are:\n * 1.) A conversation object with no action or other user id (e.g. from the overview page)\n * 2.) A conversation id with no action or other user id (e.g. from the contacts page)\n * 3.) No conversation/id with an action and other other user id. (e.g. from contact page)\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n * @param {Object|Number|null} conversationOrId Conversation or id or null\n * @param {String} action An action to take on the conversation\n * @param {Number} otherUserId The other user id for a private conversation\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, conversationOrId, action, otherUserId) {\n var conversation = null;\n var conversationId = null;\n\n // Check what we were given to identify the conversation.\n if (conversationOrId && conversationOrId !== null && typeof conversationOrId == 'object') {\n conversation = conversationOrId;\n conversationId = parseInt(conversation.id, 10);\n } else {\n conversation = null;\n conversationId = parseInt(conversationOrId, 10);\n conversationId = isNaN(conversationId) ? null : conversationId;\n }\n\n if (!conversationId && action && otherUserId) {\n // If we didn't get a conversation id got a user id then let's see if we've\n // previously loaded a private conversation with this user.\n conversationId = getCachedPrivateConversationIdFromUserId(otherUserId);\n }\n\n // Set attributes to aid in the restoration of unsent messages.\n setConversationAttributes(footer, conversationId, otherUserId);\n\n // This is a new conversation if:\n // 1. We don't already have a state\n // 2. The given conversation doesn't match the one currently loaded\n // 3. We have a view state without a conversation id and we weren't given one\n // but we were given a different other user id. This happens when the user\n // goes from viewing a user that they haven't yet initialised a conversation\n // with to viewing a different user that they also haven't initialised a\n // conversation with.\n var isNewConversation = !viewState || (viewState.id != conversationId) || (otherUserId && otherUserId != getOtherUserId());\n\n if (!body.attr('data-init')) {\n // Generate the render function to bind the header, body, and footer\n // elements to it so that we don't need to pass them around this module.\n render = generateRenderFunction(header, body, footer, isNewConversation);\n registerEventListeners(namespace, header, body, footer);\n body.attr('data-init', true);\n }\n\n if (isNewConversation) {\n var renderPromise = null;\n var loggedInUserProfile = getLoggedInUserProfile(body);\n\n if (conversation) {\n renderPromise = resetByConversation(body, conversation, loggedInUserProfile, otherUserId);\n } else if (conversationId) {\n renderPromise = resetById(body, conversationId, loggedInUserProfile, otherUserId);\n } else {\n renderPromise = resetNoConversation(body, loggedInUserProfile, otherUserId);\n }\n\n return renderPromise\n .then(function() {\n isResetting = false;\n // Focus the first element that can receieve it in the header.\n header.find(Constants.SELECTORS.CAN_RECEIVE_FOCUS).first().focus();\n return;\n })\n .catch(function(error) {\n isResetting = false;\n Notification.exception(error);\n });\n }\n\n // We're not loading a new conversation so we should reset the poll timer to try to load\n // new messages.\n resetMessagePollTimer(conversationId);\n\n if (viewState.type == CONVERSATION_TYPES.PRIVATE && action) {\n // There are special actions that the user can perform in a private (aka 1-to-1)\n // conversation.\n var currentOtherUserId = getOtherUserId();\n\n switch (action) {\n case 'block':\n return requestBlockUser(currentOtherUserId);\n case 'unblock':\n return requestUnblockUser(currentOtherUserId);\n case 'add-contact':\n return requestAddContact(currentOtherUserId);\n case 'remove-contact':\n return requestRemoveContact(currentOtherUserId);\n }\n }\n\n // Final fallback to return a promise if we didn't need to do anything.\n return $.Deferred().resolve().promise();\n };\n\n /**\n * String describing this page used for aria-labels.\n *\n * @return {Object} jQuery promise\n */\n var description = function() {\n return Str.get_string('messagedrawerviewconversation', 'core_message', viewState.name);\n };\n\n /**\n * Set some attributes that will help reboot a conversation.\n *\n * These attributes aid in the storage and retrieval of unsent messages.\n * When a conversationid is not present, otheruserid can be used to create a conversation.\n *\n * @param {Object} element The element to target.\n * @param {Number|null} conversationId The conversationid.\n * @param {Number|null} otherUserId The otheruserid.\n */\n const setConversationAttributes = function(element, conversationId, otherUserId) {\n element.removeAttr('data-conversation-id');\n element.removeAttr('data-other-user-id');\n if (conversationId) {\n element.attr('data-conversation-id', conversationId);\n } else if (otherUserId) {\n element.attr('data-other-user-id', otherUserId);\n }\n };\n\n return {\n show: show,\n description: description\n };\n});\n"],"names":["define","$","AutoRows","BackOffTimer","CustomEvents","Notification","Pending","PubSub","Str","Repository","MessageDrawerEvents","Constants","Patcher","Renderer","StateManager","MessageDrawerRouter","MessageDrawerRoutes","initialiseEmojiAutoComplete","initialiseEmojiPicker","stateCache","viewState","loadedAllMessages","messagesOffset","newMessagesPollTimer","isRendering","renderBuffer","isResetting","isSendingMessage","isDeletingConversationContent","sendMessageBuffer","render","renderers","NEWEST_FIRST","NEWEST_MESSAGES_FIRST","LOAD_MESSAGE_LIMIT","MILLISECONDS_IN_SEC","SELECTORS","CONVERSATION_TYPES","getOtherUserId","type","PUBLIC","loggedInUserId","SELF","otherUserIds","Object","keys","members","filter","userId","length","getMessagesOffset","setMessagesOffset","value","id","hasLoadedAllMessages","setLoadedAllMessages","formatConversationForEvent","state","name","subname","imageUrl","isFavourite","isMuted","totalMemberCount","messages","map","message","extend","formattedMember","contactrequests","request","updateStateFromConversation","conversation","otherUser","PRIVATE","otherUsers","member","imageurl","fullname","profileimageurl","newState","addMembers","setName","setSubname","setType","setImageUrl","setTotalMemberCount","membercount","setIsFavourite","isfavourite","setIsMuted","ismuted","addMessages","setCanDeleteMessagesForAllUsers","candeletemessagesforallusers","loadMessages","conversationId","limit","offset","newestFirst","ignoreList","timeFrom","getMessages","then","result","indexOf","parseInt","slice","membersToAdd","setLoadingMessages","catch","error","markConversationAsRead","pendingPromise","markAllConversationMessagesAsRead","markMessagesAsRead","publish","CONVERSATION_READ","resolve","requestBlockUser","cancelRequest","addPendingBlockUsersById","blockUser","setLoadingConfirmAction","profile","removePendingBlockUsersById","CONTACT_BLOCKED","requestUnblockUser","addPendingUnblockUsersById","unblockUser","removePendingUnblockUsersById","CONTACT_UNBLOCKED","requestRemoveContact","addPendingRemoveContactsById","removeContact","deleteContacts","profiles","removePendingRemoveContactsById","CONTACT_REMOVED","requestAddContact","addPendingAddContactsById","addContact","createContactRequest","response","Error","warnings","removePendingAddContactsById","addContactRequests","requestDeleteSelectedMessages","selectedMessageIds","addPendingDeleteMessagesById","deleteSelectedMessages","messageIds","pendingDeleteMessageIds","sentMessages","sendState","deleteMessagesPromise","Deferred","promise","sentMessageIds","deleteMessagesForAllUsers","deleteMessages","stop","removeMessagesById","removePendingDeleteMessagesById","removeSelectedMessagesById","setDeleteMessagesForAllUsers","prevLastMessage","newLastMessage","CONVERSATION_NEW_LAST_MESSAGE","CONVERSATION_DELETED","exception","requestDeleteConversation","setPendingDeleteConversation","deleteConversation","removeMessages","acceptContactRequest","requests","requesteduserid","removeContactRequests","CONTACT_ADDED","CONTACT_REQUEST_ACCEPTED","declineContactRequest","CONTACT_REQUEST_DECLINED","processSendMessageBuffer","messagesToSend","newConversationId","messagesText","text","sendMessagePromise","newCanDeleteMessagesForAllUsers","sendMessagesToConversation","otherUserId","sendMessagesToUser","conversationid","newMessageIds","data","selectedToRemove","selectedToAdd","forEach","oldMessage","index","newMessage","push","updateMessages","setMessagesSendSuccessById","addSelectedMessagesById","setId","resetMessagePollTimer","CONVERSATION_CREATED","e","errorMessage","get_string","handleFailedMessages","setMessagesSendFailById","finalError","previewText","plaintext","replace","processRenderBuffer","header","body","footer","renderable","shift","renderPromises","renderFunc","patch","when","apply","deferred","reject","generateConfirmActionHandler","actionCallback","loadingConfirmAction","originalEvent","preventDefault","handleSendMessage","textArea","target","closest","FOOTER_CONTAINER","find","MESSAGE_TEXT_AREA","val","trim","Date","now","loadingmessage","useridfrom","timecreated","document","querySelector","removeAttribute","sendMessage","focus","handleSelectMessage","selection","window","getSelection","toString","is","messageId","toggleSelectMessage","MESSAGE","attr","handleRetrySendMessage","setMessagesSendPendingById","retrySendMessage","stopPropagation","handleCancelEditMode","cancelEditMode","generateHandleViewContact","namespace","go","VIEW_CONTACT","handleSetFavourite","setFavouriteConversations","CONVERSATION_SET_FAVOURITE","handleUnsetFavourite","unsetFavouriteConversations","CONVERSATION_UNSET_FAVOURITE","handleSetMuted","setMutedConversations","CONVERSATION_SET_MUTED","handleUnsetMuted","unsetMutedConversations","CONVERSATION_UNSET_MUTED","handleDeleteMessagesForAllUsersToggle","newValue","prop","generateHandleViewGroupInfo","VIEW_GROUP_INFO","handleToggleEmojiPicker","setShowEmojiPicker","showEmojiPicker","handleCloseEmojiPicker","EMOJI_PICKER_CONTAINER","TOGGLE_EMOJI_PICKER_BUTTON","registerEventListeners","isLoadingMoreMessages","messagesContainer","MESSAGES_CONTAINER","getMessagesContainer","emojiPickerElement","EMOJI_PICKER","emojiAutoCompleteContainer","EMOJI_AUTO_COMPLETE_CONTAINER","messageTextArea","headerActivateHandlers","ACTION_REQUEST_BLOCK","ACTION_REQUEST_UNBLOCK","ACTION_REQUEST_ADD_CONTACT","ACTION_REQUEST_REMOVE_CONTACT","ACTION_REQUEST_DELETE_CONVERSATION","ACTION_CANCEL_EDIT_MODE","ACTION_VIEW_CONTACT","ACTION_VIEW_GROUP_INFO","ACTION_CONFIRM_FAVOURITE","ACTION_CONFIRM_MUTE","ACTION_CONFIRM_UNFAVOURITE","ACTION_CONFIRM_UNMUTE","bodyActivateHandlers","ACTION_CANCEL_CONFIRM","ACTION_CONFIRM_BLOCK","ACTION_CONFIRM_UNBLOCK","ACTION_CONFIRM_ADD_CONTACT","ACTION_CONFIRM_REMOVE_CONTACT","ACTION_CONFIRM_DELETE_SELECTED_MESSAGES","ACTION_CONFIRM_DELETE_CONVERSATION","ACTION_OKAY_CONFIRM","ACTION_ACCEPT_CONTACT_REQUEST","ACTION_DECLINE_CONTACT_REQUEST","DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE","RETRY_SEND","footerActivateHandlers","SEND_MESSAGE_BUTTON","ACTION_REQUEST_DELETE_SELECTED_MESSAGES","init","hasSuggestions","setShowEmojiAutoComplete","emoji","cursorPos","currentText","textBefore","substring","textAfter","events","activate","enter","escape","scrollTop","scrollLock","on","hasMembers","handler","selector","handlerFunction","enterToSend","subscribe","ROUTE_CHANGED","newRouteData","route","VIEW_CONVERSATION","mostRecentMessage","lastTimeCreated","timeCreated","ignoreMessageIds","i","restart","getLoadNewMessagesCallback","getIncrementalCallback","messagePollMin","messagePollMax","messagePollAfterMax","start","resetState","loggedInUserProfile","midnight","initialState","buildInitialState","resetNoConversation","getConversationBetweenUsers","getSelfConversation","resetByConversation","conversationType","setLoadingMembers","getMemberInfo","loadEmptyPrivateConversation","resetById","cache","messageLimit","messageOffset","getConversation","concat","loadNewConversation","messageCount","hasLoadedEnoughMessages","loadExistingConversation","setConversationAttributes","element","removeAttr","show","conversationOrId","action","isNaN","reduce","carry","isNewConversation","rendererFunc","buildPatch","generateRenderFunction","profileimageurlsmall","isonline","showonlinestatus","isblocked","iscontact","isdeleted","canmessage","canmessageevenifblocked","requirescontact","cancreatecontact","getLoggedInUserProfile","CAN_RECEIVE_FOCUS","first","currentOtherUserId","description"],"mappings":"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"}PKB\5mm9build/message_drawer_view_conversation_patcher.min.js.mapnu[{"version":3,"file":"message_drawer_view_conversation_patcher.min.js","sources":["../src/message_drawer_view_conversation_patcher.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * This module will take 2 view states from the message_drawer_view_conversation\n * module and generate a patch that can be given to the\n * message_drawer_view_conversation_renderer module to update the UI.\n *\n * This module should never modify either state. It's purely a read only\n * module.\n *\n * @module core_message/message_drawer_view_conversation_patcher\n * @copyright 2018 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(\n[\n 'jquery',\n 'core/user_date',\n 'core_message/message_drawer_view_conversation_constants'\n],\nfunction(\n $,\n UserDate,\n Constants\n) {\n /**\n * Sort messages by day.\n *\n * @param {Array} messages The list of messages to sort.\n * @param {Number} midnight User's midnight timestamp.\n * @return {Array} messages sorted by day.\n */\n var sortMessagesByDay = function(messages, midnight) {\n var messagesByDay = messages.reduce(function(carry, message) {\n var timeCreated = message.timeCreated ? message.timeCreated : midnight;\n var dayTimestamp = UserDate.getUserMidnightForTimestamp(timeCreated, midnight);\n\n if (carry.hasOwnProperty(dayTimestamp)) {\n carry[dayTimestamp].push(message);\n } else {\n carry[dayTimestamp] = [message];\n }\n\n return carry;\n }, {});\n\n return Object.keys(messagesByDay).map(function(dayTimestamp) {\n return {\n timestamp: dayTimestamp,\n messages: messagesByDay[dayTimestamp]\n };\n });\n };\n\n /**\n * Diff 2 arrays using a match function\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @param {Function} matchFunction Function used for matching array items.\n * @return {Object} Object containing array items missing from a, array items missing from b\n * and matches\n */\n var diffArrays = function(a, b, matchFunction) {\n // Make copy of it.\n b = b.slice();\n var missingFromA = [];\n var missingFromB = [];\n var matches = [];\n\n a.forEach(function(current) {\n var found = false;\n var index = 0;\n\n for (; index < b.length; index++) {\n var next = b[index];\n\n if (matchFunction(current, next)) {\n found = true;\n matches.push({\n a: current,\n b: next\n });\n break;\n }\n }\n\n if (found) {\n // This day has been processed so removed it from the list.\n b.splice(index, 1);\n } else {\n // If we couldn't find it in the next messages then it means\n // it needs to be added.\n missingFromB.push(current);\n }\n });\n\n missingFromA = b;\n\n return {\n missingFromA: missingFromA,\n missingFromB: missingFromB,\n matches: matches\n };\n };\n\n /**\n * Find an element in a array based on a matching function.\n *\n * @param {array} array Array to search.\n * @param {Function} breakFunction Function to run on array item.\n * @return {*} The array item.\n */\n var findPositionInArray = function(array, breakFunction) {\n var before = null;\n\n for (var i = 0; i < array.length; i++) {\n var candidate = array[i];\n\n if (breakFunction(candidate)) {\n return candidate;\n }\n }\n\n return before;\n };\n\n /**\n * Check if 2 arrays are equal.\n *\n * @param {Array} a The first array.\n * @param {Array} b The second array.\n * @return {Boolean} Are arrays equal.\n */\n var isArrayEqual = function(a, b) {\n // Make shallow copies so that we don't mess with the array sorting.\n a = a.slice();\n b = b.slice();\n a.sort();\n b.sort();\n var aLength = a.length;\n var bLength = b.length;\n\n if (aLength < 1 && bLength < 1) {\n return true;\n }\n\n if (aLength != bLength) {\n return false;\n }\n\n return a.every(function(item, index) {\n return item == b[index];\n });\n };\n\n /**\n * Do a shallow check to see if two objects appear to be equal. This should\n * only be used for pretty basic objects.\n *\n * @param {Object} a First object to compare.\n * @param {Object} b Second object to compare\n * @return {Bool}\n */\n var isObjectEqual = function(a, b) {\n var aKeys = Object.keys(a);\n var bKeys = Object.keys(b);\n\n if (aKeys.length != bKeys.length) {\n return false;\n }\n\n return aKeys.every(function(key) {\n var aVal = a[key];\n var bVal = b[key];\n var aType = typeof aVal;\n var bType = typeof bVal;\n aType = (aVal === null) ? 'null' : aType;\n bType = (aVal === null) ? 'null' : bType;\n aType = (aType === 'object' && Array.isArray(aType)) ? 'array' : aType;\n bType = (bType === 'object' && Array.isArray(bType)) ? 'array' : bType;\n\n if (aType !== bType) {\n return false;\n }\n\n switch (aType) {\n case 'object':\n return isObjectEqual(aVal, bVal);\n case 'array':\n return isArrayEqual(aVal, bVal);\n default:\n return a[key] == b[key];\n }\n });\n };\n\n /**\n * Compare two messages to check if they are equal. This function only checks a subset\n * of the message properties which we know will change rather than all properties.\n *\n * @param {Object} a The first message\n * @param {Object} b The second message\n * @return {Bool}\n */\n var isMessageEqual = function(a, b) {\n return isObjectEqual(\n {\n id: a.id,\n state: a.sendState,\n text: a.text,\n timeCreated: a.timeCreated\n },\n {\n id: b.id,\n state: b.sendState,\n text: b.text,\n timeCreated: b.timeCreated\n }\n );\n };\n\n /**\n * Build a patch based on days.\n *\n * @param {Object} current Current list current items.\n * @param {Array} remove List of days to remove.\n * @param {Array} add List of days to add.\n * @return {Object} Patch with elements to add and remove.\n */\n var buildDaysPatch = function(current, remove, add) {\n return {\n remove: remove,\n add: add.map(function(day) {\n // Any days left over in the \"next\" list weren't in the \"current\" list\n // so they will need to be added.\n var before = findPositionInArray(current, function(candidate) {\n return day.timestamp < candidate.timestamp;\n });\n\n return {\n before: before,\n value: day\n };\n })\n };\n };\n\n /**\n * Build the messages patch for each day.\n *\n * @param {Array} matchingDays Array of old and new messages sorted by day.\n * @return {Object} patch.\n */\n var buildMessagesPatch = function(matchingDays) {\n var remove = [];\n var add = [];\n var update = [];\n\n // Iterate over the list of days and determine which messages in those days\n // have been changed.\n matchingDays.forEach(function(days) {\n var dayCurrent = days.a;\n var dayNext = days.b;\n // Find out which messages have changed in this day. This will return a list of messages\n // from the current state that couldn't be found in the next state and a list of messages in\n // the next state which couldn't be count in the current state.\n var messagesDiff = diffArrays(dayCurrent.messages, dayNext.messages, isMessageEqual);\n // Take the two arrays (list of messages changed from dayNext and list of messages changed\n // from dayCurrent) any work out which messages have been added/removed from the list and\n // which messages were just updated.\n var patch = diffArrays(\n // The messages from dayCurrent.message that weren't in dayNext.messages.\n messagesDiff.missingFromB,\n // The messages from dayNext.message that weren't in dayCurrent.messages.\n messagesDiff.missingFromA,\n function(a, b) {\n // This function is going to determine if the messages were\n // added/removed from either list or if they were simply an updated.\n //\n // If the IDs match or it was a state change (i.e. message with a temp\n // ID goes from pending to sent and receives an actual id) then they are\n // the same message which should be an update not an add/remove.\n return a.id == b.id || (a.sendState != b.sendState && a.timeAdded == b.timeAdded);\n }\n );\n\n // Any messages from the current state for this day which aren't in the next state\n // for this day (i.e. the user deleted the message) means we need to remove them from\n // the UI.\n remove = remove.concat(patch.missingFromB);\n\n // Any messages not in the current state for this day which are in the next state\n // for this day (i.e. it's a new message) means we need to add it to the UI so work\n // out where in the list of messages it should appear (it could be a new message the\n // user has sent or older messages loaded as part of the conversation scroll back).\n patch.missingFromA.forEach(function(message) {\n // By default a null value for before will render the message at the bottom of\n // the message UI (i.e. it's the newest message).\n var before = null;\n\n if (message.timeCreated) {\n // If this message has a time created then find where it sits in the list of\n // message to insert it into the correct position.\n before = findPositionInArray(dayCurrent.messages, function(candidate) {\n if (message.timeCreated == candidate.timeCreated) {\n return message.id < candidate.id;\n } else {\n return message.timeCreated < candidate.timeCreated;\n }\n });\n }\n\n add.push({\n before: before,\n value: message,\n day: dayCurrent\n });\n });\n\n // Any message that appears in both the current state for this day and the next state\n // for this day means something in the message was updated.\n update = update.concat(patch.matches.map(function(message) {\n return {\n before: message.a,\n after: message.b\n };\n }));\n });\n\n return {\n add: add,\n remove: remove,\n update: update\n };\n };\n\n /**\n * Build a patch for this conversation.\n *\n * @param {Object} state The current state of this conversation.\n * @param {Object} newState The new state of this conversation.\n * @returns {Object} Patch with days and messsages for each day.\n */\n var buildConversationPatch = function(state, newState) {\n var diff = diffArrays(state.messages, newState.messages, isMessageEqual);\n\n if (diff.missingFromA.length || diff.missingFromB.length) {\n // Some messages have changed so let's work out which ones by sorting\n // them into their respective days.\n var current = sortMessagesByDay(state.messages, state.midnight);\n var next = sortMessagesByDay(newState.messages, newState.midnight);\n // This diffs the arrays to work out if there are any missing days that need\n // to be added (i.e. we've got some new messages on a new day) or if there\n // are any days that need to be deleted (i.e. the user has deleted some old messages).\n var daysDiff = diffArrays(current, next, function(dayCurrent, dayNext) {\n return dayCurrent.timestamp == dayNext.timestamp;\n });\n\n return {\n // Handle adding or removing whole days.\n days: buildDaysPatch(current, daysDiff.missingFromB, daysDiff.missingFromA),\n // Handle updating messages that don't require adding/removing a whole day.\n messages: buildMessagesPatch(daysDiff.matches)\n };\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePrivate = function(state, newState) {\n var requireAddContact = buildRequireAddContact(state, newState);\n var confirmContactRequest = buildConfirmContactRequest(state, newState);\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var requiresAddContact = requireAddContact && requireAddContact.show && !requireAddContact.hasMessages;\n var requiredAddContact = requireAddContact && !requireAddContact.show;\n // Render the header once we've got a user.\n var shouldRenderHeader = !oldOtherUser && newOtherUser;\n // We should also re-render the header if the other user requires\n // being added as a contact or if they did but no longer do.\n shouldRenderHeader = shouldRenderHeader || requiresAddContact || requiredAddContact;\n // Finally, we should re-render if the other user has sent this user\n // a contact request that is waiting for approval or if it's been approved/declined.\n shouldRenderHeader = shouldRenderHeader || confirmContactRequest !== null;\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.PRIVATE,\n // We can show controls if the other user doesn't require add contact\n // and we aren't waiting for this user to respond to a contact request.\n showControls: !requiresAddContact && !confirmContactRequest,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n userid: newOtherUser.id,\n showonlinestatus: newOtherUser.showonlinestatus,\n isonline: newOtherUser.isonline,\n isblocked: newOtherUser.isblocked,\n iscontact: newOtherUser.iscontact,\n cancreatecontact: newOtherUser.cancreatecontact,\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypeSelf = function(state, newState) {\n var shouldRenderHeader = (state.name === null && newState.name !== null);\n\n if (shouldRenderHeader) {\n return {\n type: Constants.CONVERSATION_TYPES.SELF,\n // Don't display the controls for the self-conversations.\n showControls: false,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null,\n showonlinestatus: true,\n }\n };\n }\n\n return null;\n };\n\n /**\n * Build a patch for the header of this conversation. Check if this conversation\n * is a group conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildHeaderPatchTypePublic = function(state, newState) {\n var oldMemberCount = state.totalMemberCount;\n var newMemberCount = newState.totalMemberCount;\n\n if (oldMemberCount != newMemberCount) {\n return {\n type: Constants.CONVERSATION_TYPES.PUBLIC,\n showControls: true,\n context: {\n id: newState.id,\n name: newState.name,\n subname: newState.subname,\n totalmembercount: newState.totalMemberCount,\n imageurl: newState.imageUrl,\n isfavourite: newState.isFavourite,\n ismuted: newState.isMuted,\n // Don't show favouriting if we don't have a conversation.\n showfavourite: newState.id !== null\n }\n };\n } else {\n return null;\n }\n };\n\n /**\n * Find the newest or oldest message.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Number} Oldest or newest message id.\n */\n var buildScrollToMessagePatch = function(state, newState) {\n var oldMessages = state.messages;\n var newMessages = newState.messages;\n\n if (newMessages.length < 1) {\n return null;\n }\n\n if (oldMessages.length < 1) {\n return newMessages[newMessages.length - 1].id;\n }\n\n var previousNewest = oldMessages[state.messages.length - 1];\n var currentNewest = newMessages[newMessages.length - 1];\n var previousOldest = oldMessages[0];\n var currentOldest = newMessages[0];\n\n if (previousNewest.id != currentNewest.id) {\n return currentNewest.id;\n } else if (previousOldest.id != currentOldest.id) {\n return previousOldest.id;\n }\n\n return null;\n };\n\n /**\n * Check if members should be loaded.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMembersPatch = function(state, newState) {\n if (!state.loadingMembers && newState.loadingMembers) {\n return true;\n } else if (state.loadingMembers && !newState.loadingMembers) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are being loaded for the first time.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingFirstMessages = function(state, newState) {\n if (state.hasTriedToLoadMessages === newState.hasTriedToLoadMessages) {\n return null;\n } else if (!newState.hasTriedToLoadMessages && newState.loadingMessages) {\n return true;\n } else if (newState.hasTriedToLoadMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if the messages are still being loaded\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingMessages = function(state, newState) {\n if (!state.loadingMessages && newState.loadingMessages) {\n return true;\n } else if (state.loadingMessages && !newState.loadingMessages) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji picker.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiPicker = function(state, newState) {\n if (!state.showEmojiPicker && newState.showEmojiPicker) {\n return true;\n } else if (state.showEmojiPicker && !newState.showEmojiPicker) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Determine if we should show the emoji auto complete.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildShowEmojiAutoComplete = function(state, newState) {\n if (!state.showEmojiAutoComplete && newState.showEmojiAutoComplete) {\n return true;\n } else if (state.showEmojiAutoComplete && !newState.showEmojiAutoComplete) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Get the user Object of user to be blocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmBlockUser = function(state, newState) {\n if (newState.pendingBlockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingBlockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingBlockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be unblocked if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmUnblockUser = function(state, newState) {\n if (newState.pendingUnblockUserIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingUnblockUserIds[0];\n return newState.members[userId];\n } else if (state.pendingUnblockUserIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be added as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmAddContact = function(state, newState) {\n if (newState.pendingAddContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingAddContactIds[0];\n return newState.members[userId];\n } else if (state.pendingAddContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Get the user Object of user to be removed as contact if pending.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Bool|Null} User Object if Object.\n */\n var buildConfirmRemoveContact = function(state, newState) {\n if (newState.pendingRemoveContactIds.length) {\n // We currently only support a single user;\n var userId = newState.pendingRemoveContactIds[0];\n return newState.members[userId];\n } else if (state.pendingRemoveContactIds.length) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there are any messages to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object|Null} The conversation type and if the user can delete the messages for all users.\n */\n var buildConfirmDeleteSelectedMessages = function(state, newState) {\n var oldPendingCount = state.pendingDeleteMessageIds.length;\n var newPendingCount = newState.pendingDeleteMessageIds.length;\n\n if (newPendingCount && !oldPendingCount) {\n return {\n show: true,\n type: newState.type,\n canDeleteMessagesForAllUsers: newState.canDeleteMessagesForAllUsers\n };\n } else if (oldPendingCount && !newPendingCount) {\n return {\n show: false\n };\n }\n\n return null;\n };\n\n /**\n * Check if there is a conversation to be deleted.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {int|Null} The conversation type to be deleted.\n */\n var buildConfirmDeleteConversation = function(state, newState) {\n if (!state.pendingDeleteConversation && newState.pendingDeleteConversation) {\n return newState.type;\n } else if (state.pendingDeleteConversation && !newState.pendingDeleteConversation) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if there is a pending contact request to accept or decline.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildConfirmContactRequest = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldReceivedRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == oldOtherUser.id;\n });\n var newReceivedRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId && request.userid == newOtherUser.id;\n });\n var oldRequest = oldReceivedRequests.length ? oldReceivedRequests[0] : null;\n var newRequest = newReceivedRequests.length ? newReceivedRequests[0] : null;\n\n if (!oldRequest && newRequest) {\n return newOtherUser;\n } else if (oldRequest && !newRequest) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in blocked users.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsBlocked = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation favourite state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsFavourite = function(state, newState) {\n var oldIsFavourite = state.isFavourite;\n var newIsFavourite = newState.isFavourite;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the add favourite button.\n return 'show-add';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the favouriting functionality for now.\n return 'hide';\n } else if (oldIsFavourite == newIsFavourite) {\n // No change.\n return null;\n } else if (!oldIsFavourite && newIsFavourite) {\n return 'show-remove';\n } else if (oldIsFavourite && !newIsFavourite) {\n return 'show-add';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes the conversation muted state.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|null}\n */\n var buildIsMuted = function(state, newState) {\n var oldIsMuted = state.isMuted;\n var newIsMuted = newState.isMuted;\n\n if (state.id === null && newState.id === null) {\n // The conversation isn't yet created so don't change anything.\n return null;\n } else if (state.id === null && newState.id !== null) {\n // The conversation was created so we can show the mute button.\n return 'show-mute';\n } else if (state.id !== null && newState.id === null) {\n // We're changing from a created conversation to a new conversation so hide\n // the muting functionality for now.\n return 'hide';\n } else if (oldIsMuted == newIsMuted) {\n // No change.\n return null;\n } else if (!oldIsMuted && newIsMuted) {\n return 'show-unmute';\n } else if (oldIsMuted && !newIsMuted) {\n return 'show-mute';\n } else {\n return null;\n }\n };\n\n /**\n * Check if there are any changes in the contact status of the current user\n * and other user.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildIsContact = function(state, newState) {\n var loggedInUserId = state.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldContactRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == oldOtherUser.id) ||\n (request.userid == oldOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var newContactRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return (request.userid == loggedInUserId && request.requesteduserid == newOtherUser.id) ||\n (request.userid == newOtherUser.id && request.requesteduserid == loggedInUserId);\n });\n var oldHasContactRequests = oldContactRequests.length > 0;\n var newHasContactRequests = newContactRequests.length > 0;\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldHasContactRequests && newHasContactRequests) {\n return null;\n } else if (!oldHasContactRequests && newHasContactRequests && !newOtherUser.iscontact) {\n return 'pending-contact';\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.iscontact ? 'contact' : null;\n } else if (!newOtherUser && oldOtherUser) {\n return oldOtherUser.iscontact ? 'non-contact' : null;\n } else if (oldOtherUser.iscontact && !newOtherUser.iscontact) {\n return newHasContactRequests ? 'pending-contact' : 'non-contact';\n } else if (!oldOtherUser.iscontact && newOtherUser.iscontact) {\n return 'contact';\n } else {\n return null;\n }\n };\n\n /**\n * Check if a confirm action is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildLoadingConfirmationAction = function(state, newState) {\n if (!state.loadingConfirmAction && newState.loadingConfirmAction) {\n return true;\n } else if (state.loadingConfirmAction && !newState.loadingConfirmAction) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Check if a edit mode is active.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildInEditMode = function(state, newState) {\n var oldHasSelectedMessages = state.selectedMessageIds.length > 0;\n var newHasSelectedMessages = newState.selectedMessageIds.length > 0;\n var numberOfMessagesHasChanged = state.messages.length != newState.messages.length;\n\n if (!oldHasSelectedMessages && newHasSelectedMessages) {\n return true;\n } else if (oldHasSelectedMessages && !newHasSelectedMessages) {\n return false;\n } else if (oldHasSelectedMessages && numberOfMessagesHasChanged) {\n return true;\n } else {\n return null;\n }\n };\n\n /**\n * Build a patch for the messages selected.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} patch\n */\n var buildSelectedMessages = function(state, newState) {\n var oldSelectedMessages = state.selectedMessageIds;\n var newSelectedMessages = newState.selectedMessageIds;\n\n if (isArrayEqual(oldSelectedMessages, newSelectedMessages)) {\n return null;\n }\n\n var diff = diffArrays(oldSelectedMessages, newSelectedMessages, function(a, b) {\n return a == b;\n });\n\n return {\n count: newSelectedMessages.length,\n add: diff.missingFromA,\n remove: diff.missingFromB\n };\n };\n\n /**\n * Get a list of users from the state that are not the logged in user. Use to find group\n * message members or the other user in a conversation.\n *\n * @param {Object} state State\n * @return {Array} List of users.\n */\n var getOtherUserFromState = function(state) {\n return Object.keys(state.members).reduce(function(carry, userId) {\n if (userId != state.loggedInUserId && !carry) {\n carry = state.members[userId];\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Check if the given user requires a contact request from the logged in user.\n *\n * @param {Integer} loggedInUserId The logged in user id\n * @param {Object} user User record\n * @return {Bool}\n */\n var requiresContactRequest = function(loggedInUserId, user) {\n // If a user can message then no contact request is required.\n if (user.canmessage) {\n return false;\n }\n\n var contactRequests = user.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId || request.requesteduserid;\n });\n var hasSentContactRequest = contactRequests.length > 0;\n return user.requirescontact && !user.iscontact && !hasSentContactRequest;\n };\n\n /**\n * Check if other users are required to be added as contact.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Object controlling the required to add contact dialog variables.\n */\n var buildRequireAddContact = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var hadMessages = state.messages.length > 0;\n var hasMessages = newState.messages.length > 0;\n var loggedInUserId = newState.loggedInUserId;\n var prevRequiresContactRequest = oldOtherUser && requiresContactRequest(loggedInUserId, oldOtherUser);\n var nextRequiresContactRequest = newOtherUser && requiresContactRequest(loggedInUserId, newOtherUser);\n var confirmAddContact = buildConfirmAddContact(state, newState);\n var finishedAddContact = confirmAddContact === false;\n\n // Still doing first load.\n if (!state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n return null;\n }\n\n // No users yet.\n if (!oldOtherUser && !newOtherUser) {\n return null;\n }\n\n // We've loaded a new user and they require a contact request.\n if (!oldOtherUser && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // The logged in user has completed the confirm contact request dialogue\n // but the other user still requires a contact request which means the logged\n // in user either declined the confirmation or it failed.\n if (finishedAddContact && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n // Everything is loaded.\n if (state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (!prevRequiresContactRequest && nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n\n if (prevRequiresContactRequest && !nextRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hasMessages\n };\n }\n }\n\n // First load just completed.\n if (!state.hasTriedToLoadMessages && newState.hasTriedToLoadMessages) {\n if (nextRequiresContactRequest) {\n return {\n show: true,\n hasMessages: hasMessages,\n user: newOtherUser\n };\n }\n }\n\n // Being reset.\n if (state.hasTriedToLoadMessages && !newState.hasTriedToLoadMessages) {\n if (prevRequiresContactRequest) {\n return {\n show: false,\n hasMessages: hadMessages\n };\n }\n }\n\n return null;\n };\n\n /**\n * Check if other users are required to be unblocked.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildRequireUnblock = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.isblocked ? false : null;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.isblocked ? true : null;\n } else if (!oldOtherUser.isblocked && newOtherUser.isblocked) {\n return true;\n } else if (oldOtherUser.isblocked && !newOtherUser.isblocked) {\n return false;\n }\n\n return null;\n };\n\n /**\n * Check if other users can be messaged.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Bool|Null}\n */\n var buildUnableToMessage = function(state, newState) {\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n\n if (newState.type == Constants.CONVERSATION_TYPES.SELF) {\n // Users always can send message themselves on self-conversations.\n return null;\n }\n\n if (!oldOtherUser && !newOtherUser) {\n return null;\n } else if (oldOtherUser && !newOtherUser) {\n return oldOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser && newOtherUser) {\n return newOtherUser.canmessage ? null : true;\n } else if (!oldOtherUser.canmessage && newOtherUser.canmessage) {\n return false;\n } else if (oldOtherUser.canmessage && !newOtherUser.canmessage) {\n return true;\n }\n\n return null;\n };\n\n /**\n * Build patch for footer information for a private conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePrivate = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n var requireAddContact = buildRequireAddContact(state, newState);\n var requireUnblock = buildRequireUnblock(state, newState);\n var unableToMessage = buildUnableToMessage(state, newState);\n var showRequireAddContact = requireAddContact !== null ? requireAddContact.show && requireAddContact.hasMessages : null;\n var otherUser = getOtherUserFromState(newState);\n var generateReturnValue = function(checkValue, successReturn) {\n if (checkValue) {\n return successReturn;\n } else if (checkValue !== null && !checkValue) {\n if (!otherUser) {\n return {type: 'content'};\n } else if (otherUser.isblocked) {\n return {type: 'unblock'};\n } else if (newState.messages.length && requiresContactRequest(newState.loggedInUserId, otherUser)) {\n return {\n type: 'add-contact',\n user: otherUser\n };\n } else if (!otherUser.canmessage && (otherUser.requirescontact && !otherUser.iscontact)) {\n return {type: 'unable-to-message'};\n }\n }\n\n return null;\n };\n\n if (\n loadingFirstMessages === null &&\n inEditMode === null &&\n requireAddContact === null &&\n requireUnblock === null\n ) {\n return null;\n }\n\n var checks = [\n [loadingFirstMessages, {type: 'placeholder'}],\n [inEditMode, {type: 'edit-mode'}],\n [unableToMessage, {type: 'unable-to-message'}],\n [requireUnblock, {type: 'unblock'}],\n [showRequireAddContact, {type: 'add-contact', user: otherUser}]\n ];\n\n for (var i = 0; i < checks.length; i++) {\n var checkValue = checks[i][0];\n var successReturn = checks[i][1];\n var result = generateReturnValue(checkValue, successReturn);\n\n if (result !== null) {\n return result;\n }\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Build patch for footer information for a public conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} containing footer state type.\n */\n var buildFooterPatchTypePublic = function(state, newState) {\n var loadingFirstMessages = buildLoadingFirstMessages(state, newState);\n var inEditMode = buildInEditMode(state, newState);\n\n if (loadingFirstMessages === null && inEditMode === null) {\n return null;\n }\n\n if (loadingFirstMessages) {\n return {type: 'placeholder'};\n }\n\n if (inEditMode) {\n return {type: 'edit-mode'};\n }\n\n return {\n type: 'content'\n };\n };\n\n /**\n * Check if we're viewing a different conversation. If so then we need to\n * reset the UI.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool|null} If a reset needs to occur\n */\n var buildReset = function(state, newState) {\n var oldType = state.type;\n var newType = newState.type;\n var oldConversationId = state.id;\n var newConversationId = newState.id;\n var oldMemberIds = Object.keys(state.members);\n var newMemberIds = Object.keys(newState.members);\n\n oldMemberIds.sort();\n newMemberIds.sort();\n\n var membersUnchanged = oldMemberIds.every(function(id, index) {\n return id == newMemberIds[index];\n });\n\n if (oldType != newType) {\n // If we've changed conversation type then we need to reset.\n return true;\n } else if (oldConversationId && !newConversationId) {\n // We previously had a conversation id but no longer do. This likely means\n // the user is viewing the conversation with someone they've never spoken to\n // before.\n return true;\n } else if (oldConversationId && newConversationId && oldConversationId != newConversationId) {\n // If we had a conversation id and it's changed then we need to reset.\n return true;\n } else if (!oldConversationId && !newConversationId && !membersUnchanged) {\n // If we never had a conversation id but the members of the conversation have\n // changed then we need to reset. This can happen if the user goes from viewing\n // a user they've never had a conversation with to viewing a different user that\n // they've never had a conversation with.\n return true;\n }\n\n return null;\n };\n\n /**\n * We should show this message always, for all the self-conversations.\n *\n * The message should be hidden when it's not a self-conversation.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {bool}\n */\n var buildSelfConversationMessage = function(state, newState) {\n if (state.type != newState.type) {\n return (newState.type == Constants.CONVERSATION_TYPES.SELF);\n }\n\n return null;\n };\n\n /**\n * We should show the contact request sent message if the user just sent\n * a contact request to the other user\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {string|false|null}\n */\n var buildContactRequestSent = function(state, newState) {\n var loggedInUserId = newState.loggedInUserId;\n var oldOtherUser = getOtherUserFromState(state);\n var newOtherUser = getOtherUserFromState(newState);\n var oldSentRequests = !oldOtherUser ? [] : oldOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var newSentRequests = !newOtherUser ? [] : newOtherUser.contactrequests.filter(function(request) {\n return request.userid == loggedInUserId;\n });\n var oldRequest = oldSentRequests.length > 0;\n var newRequest = newSentRequests.length > 0;\n\n if (!oldRequest && newRequest && !newOtherUser.iscontact) {\n return newOtherUser.fullname;\n } else if (oldOtherUser && !oldOtherUser.iscontact && newRequest && newOtherUser.iscontact) {\n // Contact request accepted.\n return false;\n } else if (oldRequest && !newRequest) {\n return false;\n } else {\n return null;\n }\n };\n\n /**\n * Build the full patch comparing the current state and the new state. This patch is used by\n * the conversation renderer to render the UI on any update.\n *\n * @param {Object} state The current state.\n * @param {Object} newState The new state.\n * @return {Object} Patch containing all information changed.\n */\n var buildPatch = function(state, newState) {\n var config = {\n all: {\n reset: buildReset,\n conversation: buildConversationPatch,\n scrollToMessage: buildScrollToMessagePatch,\n loadingMembers: buildLoadingMembersPatch,\n loadingFirstMessages: buildLoadingFirstMessages,\n loadingMessages: buildLoadingMessages,\n confirmDeleteSelectedMessages: buildConfirmDeleteSelectedMessages,\n inEditMode: buildInEditMode,\n selectedMessages: buildSelectedMessages,\n isFavourite: buildIsFavourite,\n isMuted: buildIsMuted,\n showEmojiPicker: buildShowEmojiPicker,\n showEmojiAutoComplete: buildShowEmojiAutoComplete\n }\n };\n // These build functions are only applicable to private conversations.\n config[Constants.CONVERSATION_TYPES.PRIVATE] = {\n header: buildHeaderPatchTypePrivate,\n footer: buildFooterPatchTypePrivate,\n confirmBlockUser: buildConfirmBlockUser,\n confirmUnblockUser: buildConfirmUnblockUser,\n confirmAddContact: buildConfirmAddContact,\n confirmRemoveContact: buildConfirmRemoveContact,\n confirmContactRequest: buildConfirmContactRequest,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n isBlocked: buildIsBlocked,\n isContact: buildIsContact,\n loadingConfirmAction: buildLoadingConfirmationAction,\n requireAddContact: buildRequireAddContact,\n contactRequestSent: buildContactRequestSent\n };\n // These build functions are only applicable to public (group) conversations.\n config[Constants.CONVERSATION_TYPES.PUBLIC] = {\n header: buildHeaderPatchTypePublic,\n footer: buildFooterPatchTypePublic,\n };\n // These build functions are only applicable to self-conversations.\n config[Constants.CONVERSATION_TYPES.SELF] = {\n header: buildHeaderPatchTypeSelf,\n footer: buildFooterPatchTypePublic,\n confirmDeleteConversation: buildConfirmDeleteConversation,\n selfConversationMessage: buildSelfConversationMessage\n };\n\n var patchConfig = $.extend({}, config.all);\n if (newState.type && newState.type in config) {\n // Add the type specific builders to the patch config.\n patchConfig = $.extend(patchConfig, config[newState.type]);\n }\n\n return Object.keys(patchConfig).reduce(function(patch, key) {\n var buildFunc = patchConfig[key];\n var value = buildFunc(state, newState);\n\n if (value !== null) {\n patch[key] = value;\n }\n\n return patch;\n }, {});\n };\n\n return {\n buildPatch: buildPatch\n };\n});\n"],"names":["define","$","UserDate","Constants","sortMessagesByDay","messages","midnight","messagesByDay","reduce","carry","message","timeCreated","dayTimestamp","getUserMidnightForTimestamp","hasOwnProperty","push","Object","keys","map","timestamp","diffArrays","a","b","matchFunction","slice","missingFromB","matches","forEach","current","found","index","length","next","splice","missingFromA","findPositionInArray","array","breakFunction","i","candidate","isArrayEqual","sort","aLength","bLength","every","item","isObjectEqual","aKeys","bKeys","key","aVal","bVal","aType","bType","Array","isArray","isMessageEqual","id","state","sendState","text","buildDaysPatch","remove","add","day","before","value","buildConversationPatch","newState","matchingDays","update","diff","daysDiff","dayCurrent","dayNext","days","messagesDiff","patch","timeAdded","concat","after","buildHeaderPatchTypePrivate","requireAddContact","buildRequireAddContact","confirmContactRequest","buildConfirmContactRequest","oldOtherUser","getOtherUserFromState","newOtherUser","requiresAddContact","show","hasMessages","requiredAddContact","shouldRenderHeader","type","CONVERSATION_TYPES","PRIVATE","showControls","context","name","subname","totalmembercount","totalMemberCount","imageurl","imageUrl","isfavourite","isFavourite","ismuted","isMuted","showfavourite","userid","showonlinestatus","isonline","isblocked","iscontact","cancreatecontact","buildHeaderPatchTypeSelf","SELF","buildHeaderPatchTypePublic","PUBLIC","buildScrollToMessagePatch","oldMessages","newMessages","previousNewest","currentNewest","previousOldest","currentOldest","buildLoadingMembersPatch","loadingMembers","buildLoadingFirstMessages","hasTriedToLoadMessages","loadingMessages","buildLoadingMessages","buildShowEmojiPicker","showEmojiPicker","buildShowEmojiAutoComplete","showEmojiAutoComplete","buildConfirmBlockUser","pendingBlockUserIds","userId","members","buildConfirmUnblockUser","pendingUnblockUserIds","buildConfirmAddContact","pendingAddContactIds","buildConfirmRemoveContact","pendingRemoveContactIds","buildConfirmDeleteSelectedMessages","oldPendingCount","pendingDeleteMessageIds","newPendingCount","canDeleteMessagesForAllUsers","buildConfirmDeleteConversation","pendingDeleteConversation","loggedInUserId","oldReceivedRequests","contactrequests","filter","request","requesteduserid","newReceivedRequests","oldRequest","newRequest","buildIsBlocked","buildIsFavourite","oldIsFavourite","newIsFavourite","buildIsMuted","oldIsMuted","newIsMuted","buildIsContact","oldContactRequests","newContactRequests","oldHasContactRequests","newHasContactRequests","buildLoadingConfirmationAction","loadingConfirmAction","buildInEditMode","oldHasSelectedMessages","selectedMessageIds","newHasSelectedMessages","numberOfMessagesHasChanged","buildSelectedMessages","oldSelectedMessages","newSelectedMessages","count","requiresContactRequest","user","canmessage","hasSentContactRequest","requirescontact","hadMessages","prevRequiresContactRequest","nextRequiresContactRequest","finishedAddContact","buildFooterPatchTypePrivate","loadingFirstMessages","inEditMode","requireUnblock","buildRequireUnblock","unableToMessage","buildUnableToMessage","showRequireAddContact","otherUser","generateReturnValue","checkValue","successReturn","checks","result","buildFooterPatchTypePublic","buildReset","oldType","newType","oldConversationId","newConversationId","oldMemberIds","newMemberIds","membersUnchanged","buildSelfConversationMessage","buildContactRequestSent","oldSentRequests","newSentRequests","fullname","buildPatch","config","all","reset","conversation","scrollToMessage","confirmDeleteSelectedMessages","selectedMessages","header","footer","confirmBlockUser","confirmUnblockUser","confirmAddContact","confirmRemoveContact","confirmDeleteConversation","isBlocked","isContact","contactRequestSent","selfConversationMessage","patchConfig","extend","buildFunc"],"mappings":"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"}PKB\u"<<,build/message_drawer_view_contact.min.js.mapnu[{"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 .\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 \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"}PKB\}kI )build/message_drawer_view_contacts.min.jsnu[/** * Controls the contacts page of the message drawer. * * @module core_message/message_drawer_view_contacts * @copyright 2018 Ryan Wyllie * @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[0].addEventListener("show.bs.tab",(function(){ContactsSection.show(contactsSection)})),showRequestsAction[0].addEventListener("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.mapPKB\c}}&build/message_drawer_routes.min.js.mapnu[{"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 .\n\n/**\n * Available routes for the message drawer.\n *\n * @module core_message/message_drawer_routes\n * @copyright 2018 Ryan Wyllie \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"}PKB\3::(build/notification_preference.min.js.mapnu[{"version":3,"file":"notification_preference.min.js","sources":["../src/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 .\n\n/**\n * Controls the preference for an individual notification type on the\n * message preference page.\n *\n * @module core_message/notification_preference\n * @copyright 2016 Ryan Wyllie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/ajax', 'core/notification', 'core_message/notification_processor'],\n function($, Ajax, Notification, NotificationProcessor) {\n\n const SELECTORS = {\n PROCESSOR: '[data-processor-name]',\n STATE_INPUTS: '[data-state] input',\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 const NotificationPreference = function(element, userId) {\n this.root = $(element);\n this.userId = userId;\n };\n\n /**\n * Get the unique prefix key that identifies this user preference.\n *\n * @method getPreferenceKey\n * @return {string}\n */\n NotificationPreference.prototype.getPreferenceKey = function() {\n return this.root.attr('data-preference-key');\n };\n\n /**\n * Get the unique key for the enabled preference.\n *\n * @method getEnabledPreferenceKey\n * @return {string}\n */\n NotificationPreference.prototype.getEnabledPreferenceKey = function() {\n return this.getPreferenceKey() + '_enabled';\n };\n\n /**\n * Get the list of Processors available for this preference.\n *\n * @method getProcessors\n * @return {array}\n */\n NotificationPreference.prototype.getProcessors = function() {\n return this.root.find(SELECTORS.PROCESSOR).map(function(index, element) {\n return new NotificationProcessor($(element));\n });\n };\n\n /**\n * Flag the preference as loading.\n *\n * @method startLoading\n */\n NotificationPreference.prototype.startLoading = function() {\n this.root.addClass('loading');\n this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', true);\n };\n\n /**\n * Remove the loading flag for this preference.\n *\n * @method stopLoading\n */\n NotificationPreference.prototype.stopLoading = function() {\n this.root.removeClass('loading');\n this.root.find(SELECTORS.STATE_INPUTS).prop('disabled', false);\n };\n\n /**\n * Check if the preference is loading.\n *\n * @method isLoading\n * @return {Boolean}\n */\n NotificationPreference.prototype.isLoading = function() {\n return this.root.hasClass('loading');\n };\n\n /**\n * Persist the current state of the processors for this preference.\n *\n * @method save\n * @return {object} jQuery promise\n */\n NotificationPreference.prototype.save = function() {\n if (this.isLoading()) {\n return $.Deferred().resolve();\n }\n\n this.startLoading();\n\n let enabledValue = '';\n\n this.getProcessors().each(function(index, processor) {\n if (processor.isEnabled()) {\n if (enabledValue === '') {\n enabledValue = processor.getName();\n } else {\n enabledValue += ',' + processor.getName();\n }\n }\n });\n\n if (enabledValue === '') {\n enabledValue = 'none';\n }\n\n const args = {\n userid: this.userId,\n preferences: [\n {\n type: this.getEnabledPreferenceKey(),\n value: enabledValue,\n }\n ],\n };\n\n const request = {\n methodname: 'core_user_update_user_preferences',\n args: args,\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 NotificationPreference;\n});\n"],"names":["define","$","Ajax","Notification","NotificationProcessor","SELECTORS","NotificationPreference","element","userId","root","prototype","getPreferenceKey","this","attr","getEnabledPreferenceKey","getProcessors","find","map","index","startLoading","addClass","prop","stopLoading","removeClass","isLoading","hasClass","save","Deferred","resolve","enabledValue","each","processor","isEnabled","getName","request","methodname","args","userid","preferences","type","value","call","fail","exception","always","bind"],"mappings":";;;;;;;;AAuBAA,8CAAO,CAAC,SAAU,YAAa,oBAAqB,wCAC5C,SAASC,EAAGC,KAAMC,aAAcC,6BAE9BC,oBACS,wBADTA,uBAEY,qBAUZC,uBAAyB,SAASC,QAASC,aACxCC,KAAOR,EAAEM,cACTC,OAASA,eASlBF,uBAAuBI,UAAUC,iBAAmB,kBACzCC,KAAKH,KAAKI,KAAK,wBAS1BP,uBAAuBI,UAAUI,wBAA0B,kBAChDF,KAAKD,mBAAqB,YASrCL,uBAAuBI,UAAUK,cAAgB,kBACtCH,KAAKH,KAAKO,KAAKX,qBAAqBY,KAAI,SAASC,MAAOX,gBACpD,IAAIH,sBAAsBH,EAAEM,cAS3CD,uBAAuBI,UAAUS,aAAe,gBACvCV,KAAKW,SAAS,gBACdX,KAAKO,KAAKX,wBAAwBgB,KAAK,YAAY,IAQ5Df,uBAAuBI,UAAUY,YAAc,gBACtCb,KAAKc,YAAY,gBACjBd,KAAKO,KAAKX,wBAAwBgB,KAAK,YAAY,IAS5Df,uBAAuBI,UAAUc,UAAY,kBAClCZ,KAAKH,KAAKgB,SAAS,YAS9BnB,uBAAuBI,UAAUgB,KAAO,cAChCd,KAAKY,mBACEvB,EAAE0B,WAAWC,eAGnBT,mBAEDU,aAAe,QAEdd,gBAAgBe,MAAK,SAASZ,MAAOa,WAClCA,UAAUC,cACW,KAAjBH,aACAA,aAAeE,UAAUE,UAEzBJ,cAAgB,IAAME,UAAUE,cAKvB,KAAjBJ,eACAA,aAAe,cAabK,QAAU,CACZC,WAAY,oCACZC,KAZS,CACTC,OAAQzB,KAAKJ,OACb8B,YAAa,CACT,CACIC,KAAM3B,KAAKE,0BACX0B,MAAOX,wBAUZ3B,KAAKuC,KAAK,CAACP,UAAU,GACvBQ,KAAKvC,aAAawC,WAClBC,OAAO,gBACCtB,eACPuB,KAAKjC,QAGRN"}PKB\t  "build/message_drawer_helper.min.jsnu[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 * @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.mapPKB\`j src/admin.jsnu[// 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 . /** * Comments admin management * * @module core_comment/admin * @copyright 2022 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ "use strict"; import {dispatchEvent} from 'core/event_dispatcher'; import Notification from 'core/notification'; import Pending from 'core/pending'; import {prefetchStrings} from 'core/prefetch'; import {getString} from 'core/str'; import {deleteComment, deleteComments} from 'core_comment/repository'; import * as reportEvents from 'core_reportbuilder/local/events'; import * as reportSelectors from 'core_reportbuilder/local/selectors'; const Selectors = { commentDelete: '[data-action="comment-delete"]', commentDeleteChecked: '[data-togglegroup="report-select-all"][data-toggle="slave"]:checked', commentDeleteSelected: '[data-action="comment-delete-selected"]', }; /** * Initialise module */ export const init = () => { prefetchStrings('core_admin', [ 'confirmdeletecomments', ]); prefetchStrings('core', [ 'delete', 'deleteselected' ]); document.addEventListener('click', event => { const commentDelete = event.target.closest(Selectors.commentDelete); if (commentDelete) { event.preventDefault(); // Use triggerElement to return focus to the action menu toggle. const triggerElement = commentDelete.closest('.dropdown').querySelector('.dropdown-toggle'); Notification.saveCancelPromise( getString('delete', 'core'), getString('confirmdeletecomments', 'core_admin'), getString('delete', 'core'), {triggerElement} ).then(() => { const pendingPromise = new Pending('core_comment/comment:delete'); const reportElement = event.target.closest(reportSelectors.regions.report); return deleteComment(commentDelete.dataset.commentId) .then(() => { dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); return pendingPromise.resolve(); }) .catch(Notification.exception); }).catch(() => { return; }); } const commentDeleteSelected = event.target.closest(Selectors.commentDeleteSelected); if (commentDeleteSelected) { event.preventDefault(); const reportElement = document.querySelector(reportSelectors.regions.report); const commentDeleteChecked = reportElement.querySelectorAll(Selectors.commentDeleteChecked); if (commentDeleteChecked.length === 0) { return; } Notification.saveCancelPromise( getString('deleteselected', 'core'), getString('confirmdeletecomments', 'core_admin'), getString('delete', 'core'), {triggerElement: commentDeleteSelected} ).then(() => { const pendingPromise = new Pending('core_comment/comments:delete'); const deleteCommentIds = [...commentDeleteChecked].map(check => check.value); return deleteComments(deleteCommentIds) .then(() => { dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement); return pendingPromise.resolve(); }) .catch(Notification.exception); }).catch(() => { return; }); } }); }; PKB\vgkkbuild/admin.min.js.mapnu[{"version":3,"file":"admin.min.js","sources":["../src/admin.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Comments admin management\n *\n * @module core_comment/admin\n * @copyright 2022 Paul Holden \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n\"use strict\";\n\nimport {dispatchEvent} from 'core/event_dispatcher';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport {prefetchStrings} from 'core/prefetch';\nimport {getString} from 'core/str';\nimport {deleteComment, deleteComments} from 'core_comment/repository';\nimport * as reportEvents from 'core_reportbuilder/local/events';\nimport * as reportSelectors from 'core_reportbuilder/local/selectors';\n\nconst Selectors = {\n commentDelete: '[data-action=\"comment-delete\"]',\n commentDeleteChecked: '[data-togglegroup=\"report-select-all\"][data-toggle=\"slave\"]:checked',\n commentDeleteSelected: '[data-action=\"comment-delete-selected\"]',\n};\n\n/**\n * Initialise module\n */\nexport const init = () => {\n prefetchStrings('core_admin', [\n 'confirmdeletecomments',\n ]);\n\n prefetchStrings('core', [\n 'delete',\n 'deleteselected'\n ]);\n\n document.addEventListener('click', event => {\n const commentDelete = event.target.closest(Selectors.commentDelete);\n if (commentDelete) {\n event.preventDefault();\n\n // Use triggerElement to return focus to the action menu toggle.\n const triggerElement = commentDelete.closest('.dropdown').querySelector('.dropdown-toggle');\n Notification.saveCancelPromise(\n getString('delete', 'core'),\n getString('confirmdeletecomments', 'core_admin'),\n getString('delete', 'core'),\n {triggerElement}\n ).then(() => {\n const pendingPromise = new Pending('core_comment/comment:delete');\n const reportElement = event.target.closest(reportSelectors.regions.report);\n\n return deleteComment(commentDelete.dataset.commentId)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n\n const commentDeleteSelected = event.target.closest(Selectors.commentDeleteSelected);\n if (commentDeleteSelected) {\n event.preventDefault();\n\n const reportElement = document.querySelector(reportSelectors.regions.report);\n const commentDeleteChecked = reportElement.querySelectorAll(Selectors.commentDeleteChecked);\n if (commentDeleteChecked.length === 0) {\n return;\n }\n\n Notification.saveCancelPromise(\n getString('deleteselected', 'core'),\n getString('confirmdeletecomments', 'core_admin'),\n getString('delete', 'core'),\n {triggerElement: commentDeleteSelected}\n ).then(() => {\n const pendingPromise = new Pending('core_comment/comments:delete');\n const deleteCommentIds = [...commentDeleteChecked].map(check => check.value);\n\n return deleteComments(deleteCommentIds)\n .then(() => {\n dispatchEvent(reportEvents.tableReload, {preservePagination: true}, reportElement);\n return pendingPromise.resolve();\n })\n .catch(Notification.exception);\n }).catch(() => {\n return;\n });\n }\n });\n};\n"],"names":["Selectors","document","addEventListener","event","commentDelete","target","closest","preventDefault","triggerElement","querySelector","saveCancelPromise","then","pendingPromise","Pending","reportElement","reportSelectors","regions","report","dataset","commentId","reportEvents","tableReload","preservePagination","resolve","catch","Notification","exception","commentDeleteSelected","commentDeleteChecked","querySelectorAll","length","deleteCommentIds","map","check","value"],"mappings":"0nDAkCMA,wBACa,iCADbA,+BAEoB,sEAFpBA,gCAGqB,wDAMP,mCACA,aAAc,CAC1B,wDAGY,OAAQ,CACpB,SACA,mBAGJC,SAASC,iBAAiB,SAASC,cACzBC,cAAgBD,MAAME,OAAOC,QAAQN,4BACvCI,cAAe,CACfD,MAAMI,uBAGAC,eAAiBJ,cAAcE,QAAQ,aAAaG,cAAc,0CAC3DC,mBACT,kBAAU,SAAU,SACpB,kBAAU,wBAAyB,eACnC,kBAAU,SAAU,QACpB,CAACF,eAAAA,iBACHG,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,+BAC7BC,cAAgBX,MAAME,OAAOC,QAAQS,gBAAgBC,QAAQC,eAE5D,6BAAcb,cAAcc,QAAQC,WACtCR,MAAK,yCACYS,aAAaC,YAAa,CAACC,oBAAoB,GAAOR,eAC7DF,eAAeW,aAEzBC,MAAMC,sBAAaC,cACzBF,OAAM,eAKPG,sBAAwBxB,MAAME,OAAOC,QAAQN,oCAC/C2B,sBAAuB,CACvBxB,MAAMI,uBAEAO,cAAgBb,SAASQ,cAAcM,gBAAgBC,QAAQC,QAC/DW,qBAAuBd,cAAce,iBAAiB7B,mCACxB,IAAhC4B,qBAAqBE,oCAIZpB,mBACT,kBAAU,iBAAkB,SAC5B,kBAAU,wBAAyB,eACnC,kBAAU,SAAU,QACpB,CAACF,eAAgBmB,wBACnBhB,MAAK,WACGC,eAAiB,IAAIC,iBAAQ,gCAC7BkB,iBAAmB,IAAIH,sBAAsBI,KAAIC,OAASA,MAAMC,eAE/D,8BAAeH,kBACjBpB,MAAK,yCACYS,aAAaC,YAAa,CAACC,oBAAoB,GAAOR,eAC7DF,eAAeW,aAEzBC,MAAMC,sBAAaC,cACzBF,OAAM"}PKB\ YBBbuild/admin.min.jsnu[define("core_comment/admin",["exports","core/event_dispatcher","core/notification","core/pending","core/prefetch","core/str","core_comment/repository","core_reportbuilder/local/events","core_reportbuilder/local/selectors"],(function(_exports,_event_dispatcher,_notification,_pending,_prefetch,_str,_repository,reportEvents,reportSelectors){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}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_pending=_interopRequireDefault(_pending),reportEvents=_interopRequireWildcard(reportEvents),reportSelectors=_interopRequireWildcard(reportSelectors);const Selectors_commentDelete='[data-action="comment-delete"]',Selectors_commentDeleteChecked='[data-togglegroup="report-select-all"][data-toggle="slave"]:checked',Selectors_commentDeleteSelected='[data-action="comment-delete-selected"]';_exports.init=()=>{(0,_prefetch.prefetchStrings)("core_admin",["confirmdeletecomments"]),(0,_prefetch.prefetchStrings)("core",["delete","deleteselected"]),document.addEventListener("click",(event=>{const commentDelete=event.target.closest(Selectors_commentDelete);if(commentDelete){event.preventDefault();const triggerElement=commentDelete.closest(".dropdown").querySelector(".dropdown-toggle");_notification.default.saveCancelPromise((0,_str.getString)("delete","core"),(0,_str.getString)("confirmdeletecomments","core_admin"),(0,_str.getString)("delete","core"),{triggerElement:triggerElement}).then((()=>{const pendingPromise=new _pending.default("core_comment/comment:delete"),reportElement=event.target.closest(reportSelectors.regions.report);return(0,_repository.deleteComment)(commentDelete.dataset.commentId).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}const commentDeleteSelected=event.target.closest(Selectors_commentDeleteSelected);if(commentDeleteSelected){event.preventDefault();const reportElement=document.querySelector(reportSelectors.regions.report),commentDeleteChecked=reportElement.querySelectorAll(Selectors_commentDeleteChecked);if(0===commentDeleteChecked.length)return;_notification.default.saveCancelPromise((0,_str.getString)("deleteselected","core"),(0,_str.getString)("confirmdeletecomments","core_admin"),(0,_str.getString)("delete","core"),{triggerElement:commentDeleteSelected}).then((()=>{const pendingPromise=new _pending.default("core_comment/comments:delete"),deleteCommentIds=[...commentDeleteChecked].map((check=>check.value));return(0,_repository.deleteComments)(deleteCommentIds).then((()=>((0,_event_dispatcher.dispatchEvent)(reportEvents.tableReload,{preservePagination:!0},reportElement),pendingPromise.resolve()))).catch(_notification.default.exception)})).catch((()=>{}))}}))}})); //# sourceMappingURL=admin.min.js.mapPK|\>;4 src/events.jsnu[// 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 . /** * Contain the events the payment modal can fire. * * @module core_payment/events * @copyright 2020 Shamim Rezaie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ export default { proceed: 'core_payment-modal_gateways:proceed', }; PK|\$yybuild/events.min.js.mapnu[{"version":3,"file":"events.min.js","sources":["../src/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 .\n\n/**\n * Contain the events the payment modal can fire.\n *\n * @module core_payment/events\n * @copyright 2020 Shamim Rezaie \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n proceed: 'core_payment-modal_gateways:proceed',\n};\n"],"names":["proceed"],"mappings":"qKAuBe,CACXA,QAAS"}PK|\wwibuild/events.min.jsnu[define("core_payment/events",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={proceed:"core_payment-modal_gateways:proceed"},_exports.default})); //# sourceMappingURL=events.min.js.mapPK3|\3src/grading_popup.jsnu[// 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 . /** * Module to enable inline editing of a comptency grade. * * @module report_competency/grading_popup * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'core/templates', 'tool_lp/dialogue'], function($, notification, str, ajax, log, templates, Dialogue) { /** * GradingPopup * * @class report_competency/grading_popup * @param {String} regionSelector The regionSelector * @param {String} userCompetencySelector The userCompetencySelector */ var GradingPopup = function(regionSelector, userCompetencySelector) { this._regionSelector = regionSelector; this._userCompetencySelector = userCompetencySelector; $(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this)); }; /** * Get the data from the clicked cell and open the popup. * * @method _handleClick * @param {Event} e The event */ GradingPopup.prototype._handleClick = function(e) { var cell = $(e.target).closest(this._userCompetencySelector); var competencyId = $(cell).data('competencyid'); var courseId = $(cell).data('courseid'); var userId = $(cell).data('userid'); log.debug('Clicked on cell: competencyId=' + competencyId + ', courseId=' + courseId + ', userId=' + userId); var requests = ajax.call([{ methodname: 'tool_lp_data_for_user_competency_summary_in_course', args: {userid: userId, competencyid: competencyId, courseid: courseId}, }, { methodname: 'core_competency_user_competency_viewed_in_course', args: {userid: userId, competencyid: competencyId, courseid: courseId}, }]); $.when(requests[0], requests[1]) .then(this._contextLoaded.bind(this)) .catch(notification.exception); }; /** * We loaded the context, now render the template. * * @method _contextLoaded * @param {Object} context * @returns {Promise} */ GradingPopup.prototype._contextLoaded = function(context) { // We have to display user info in popup. context.displayuser = true; M.util.js_pending('report_competency/grading_popup:_contextLoaded'); return $.when( str.get_string('usercompetencysummary', 'report_competency'), templates.render('tool_lp/user_competency_summary_in_course', context) ) .then(function(title, templateData) { return new Dialogue( title, templateData[0], function() { templates.runTemplateJS(templateData[1]); M.util.js_complete('report_competency/grading_popup:_contextLoaded'); }, this._refresh.bind(this), true ); }.bind(this)); }; /** * Refresh the page. * * @method _refresh * @returns {Promise} */ GradingPopup.prototype._refresh = function() { var region = $(this._regionSelector); var courseId = region.data('courseid'); var moduleId = region.data('moduleid'); var userId = region.data('userid'); // The module id is expected to be an integer, so don't pass empty string. if (moduleId === '') { moduleId = 0; } return ajax.call([{ methodname: 'report_competency_data_for_report', args: {courseid: courseId, userid: userId, moduleid: moduleId}, done: this._pageContextLoaded.bind(this), fail: notification.exception }]); }; /** * We loaded the context, now render the template. * * @method _pageContextLoaded * @param {Object} context */ GradingPopup.prototype._pageContextLoaded = function(context) { templates.render('report_competency/report', context) .then(function(html, js) { templates.replaceNode(this._regionSelector, html, js); return; }.bind(this)) .catch(notification.exception); }; /** @property {String} The selector for the region with the user competencies */ GradingPopup.prototype._regionSelector = null; /** @property {String} The selector for the region with a single user competencies */ GradingPopup.prototype._userCompetencySelector = null; return GradingPopup; }); PK3|\ATt t src/user_course_navigation.jsnu[// 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 . /** * Module to navigation between users in a course. * * @module report_competency/user_course_navigation * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery'], function($) { /** * UserCourseNavigation * * @class report_competency/user_course_navigation * @param {String} userSelector The selector of the user element. * @param {String} moduleSelector The selector of the module element. * @param {String} baseUrl The base url for the page (no params). * @param {Number} userId The course id * @param {Number} courseId The user id * @param {Number} moduleId The activity module (filter) */ var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) { this._baseUrl = baseUrl; this._userId = userId + ''; this._courseId = courseId; this._moduleId = moduleId; $(userSelector).on('change', this._userChanged.bind(this)); $(moduleSelector).on('change', this._moduleChanged.bind(this)); }; /** * The user was changed in the select list. * * @method _userChanged * @param {Event} e the event */ UserCourseNavigation.prototype._userChanged = function(e) { // Note: This change causes a page reload and is intentionally not paired with a js_complete call. M.util.js_pending('report_competency/user_course_navigation:_userChanged'); var newUserId = $(e.target).val(); var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId; document.location = this._baseUrl + queryStr; }; /** * The module was changed in the select list. * * @method _moduleChanged * @param {Event} e the event */ UserCourseNavigation.prototype._moduleChanged = function(e) { // Note: This change causes a page reload and is intentionally not paired with a js_complete call. M.util.js_pending('report_competency/user_course_navigation:_moduleChanged'); var newModuleId = $(e.target).val(); var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId; document.location = this._baseUrl + queryStr; }; /** @property {Number} The id of the user. */ UserCourseNavigation.prototype._userId = null; /** @property {Number} The id of the module. */ UserCourseNavigation.prototype._moduleId = null; /** @property {Number} The id of the course. */ UserCourseNavigation.prototype._courseId = null; /** @property {String} Plugin base url. */ UserCourseNavigation.prototype._baseUrl = null; return UserCourseNavigation; }); PK3|\ 7^ build/grading_popup.min.jsnu[/** * Module to enable inline editing of a comptency grade. * * @module report_competency/grading_popup * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("report_competency/grading_popup",["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],(function($,notification,str,ajax,log,templates,Dialogue){var GradingPopup=function(regionSelector,userCompetencySelector){this._regionSelector=regionSelector,this._userCompetencySelector=userCompetencySelector,$(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return GradingPopup.prototype._handleClick=function(e){var cell=$(e.target).closest(this._userCompetencySelector),competencyId=$(cell).data("competencyid"),courseId=$(cell).data("courseid"),userId=$(cell).data("userid");log.debug("Clicked on cell: competencyId="+competencyId+", courseId="+courseId+", userId="+userId);var requests=ajax.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}}]);$.when(requests[0],requests[1]).then(this._contextLoaded.bind(this)).catch(notification.exception)},GradingPopup.prototype._contextLoaded=function(context){return context.displayuser=!0,M.util.js_pending("report_competency/grading_popup:_contextLoaded"),$.when(str.get_string("usercompetencysummary","report_competency"),templates.render("tool_lp/user_competency_summary_in_course",context)).then(function(title,templateData){return new Dialogue(title,templateData[0],(function(){templates.runTemplateJS(templateData[1]),M.util.js_complete("report_competency/grading_popup:_contextLoaded")}),this._refresh.bind(this),!0)}.bind(this))},GradingPopup.prototype._refresh=function(){var region=$(this._regionSelector),courseId=region.data("courseid"),moduleId=region.data("moduleid"),userId=region.data("userid");return""===moduleId&&(moduleId=0),ajax.call([{methodname:"report_competency_data_for_report",args:{courseid:courseId,userid:userId,moduleid:moduleId},done:this._pageContextLoaded.bind(this),fail:notification.exception}])},GradingPopup.prototype._pageContextLoaded=function(context){templates.render("report_competency/report",context).then(function(html,js){templates.replaceNode(this._regionSelector,html,js)}.bind(this)).catch(notification.exception)},GradingPopup.prototype._regionSelector=null,GradingPopup.prototype._userCompetencySelector=null,GradingPopup})); //# sourceMappingURL=grading_popup.min.js.mapPK3|\DC%%'build/user_course_navigation.min.js.mapnu[{"version":3,"file":"user_course_navigation.min.js","sources":["../src/user_course_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to navigation between users in a course.\n *\n * @module report_competency/user_course_navigation\n * @copyright 2015 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n /**\n * UserCourseNavigation\n *\n * @class report_competency/user_course_navigation\n * @param {String} userSelector The selector of the user element.\n * @param {String} moduleSelector The selector of the module element.\n * @param {String} baseUrl The base url for the page (no params).\n * @param {Number} userId The course id\n * @param {Number} courseId The user id\n * @param {Number} moduleId The activity module (filter)\n */\n var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) {\n this._baseUrl = baseUrl;\n this._userId = userId + '';\n this._courseId = courseId;\n this._moduleId = moduleId;\n\n $(userSelector).on('change', this._userChanged.bind(this));\n $(moduleSelector).on('change', this._moduleChanged.bind(this));\n };\n\n /**\n * The user was changed in the select list.\n *\n * @method _userChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._userChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_userChanged');\n var newUserId = $(e.target).val();\n var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId;\n document.location = this._baseUrl + queryStr;\n };\n\n /**\n * The module was changed in the select list.\n *\n * @method _moduleChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._moduleChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_moduleChanged');\n var newModuleId = $(e.target).val();\n var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId;\n document.location = this._baseUrl + queryStr;\n };\n\n /** @property {Number} The id of the user. */\n UserCourseNavigation.prototype._userId = null;\n /** @property {Number} The id of the module. */\n UserCourseNavigation.prototype._moduleId = null;\n /** @property {Number} The id of the course. */\n UserCourseNavigation.prototype._courseId = null;\n /** @property {String} Plugin base url. */\n UserCourseNavigation.prototype._baseUrl = null;\n\n return UserCourseNavigation;\n});\n"],"names":["define","$","UserCourseNavigation","userSelector","moduleSelector","baseUrl","userId","courseId","moduleId","_baseUrl","_userId","_courseId","_moduleId","on","this","_userChanged","bind","_moduleChanged","prototype","e","M","util","js_pending","queryStr","target","val","document","location"],"mappings":";;;;;;;AAuBAA,kDAAO,CAAC,WAAW,SAASC,OAapBC,qBAAuB,SAASC,aAAcC,eAAgBC,QAASC,OAAQC,SAAUC,eACpFC,SAAWJ,aACXK,QAAUJ,OAAS,QACnBK,UAAYJ,cACZK,UAAYJ,SAEjBP,EAAEE,cAAcU,GAAG,SAAUC,KAAKC,aAAaC,KAAKF,OACpDb,EAAEG,gBAAgBS,GAAG,SAAUC,KAAKG,eAAeD,KAAKF,eAS5DZ,qBAAqBgB,UAAUH,aAAe,SAASI,GAEnDC,EAAEC,KAAKC,WAAW,6DAEdC,SAAW,SADCtB,EAAEkB,EAAEK,QAAQC,MACU,OAASX,KAAKH,UAAY,QAAUG,KAAKF,UAC/Ec,SAASC,SAAWb,KAAKL,SAAWc,UASxCrB,qBAAqBgB,UAAUD,eAAiB,SAASE,GAErDC,EAAEC,KAAKC,WAAW,+DAEdC,SAAW,QADGtB,EAAEkB,EAAEK,QAAQC,MACS,OAASX,KAAKH,UAAY,SAAWG,KAAKJ,QACjFgB,SAASC,SAAWb,KAAKL,SAAWc,UAIxCrB,qBAAqBgB,UAAUR,QAAU,KAEzCR,qBAAqBgB,UAAUN,UAAY,KAE3CV,qBAAqBgB,UAAUP,UAAY,KAE3CT,qBAAqBgB,UAAUT,SAAW,KAEnCP"}PK3|\2Qmm#build/user_course_navigation.min.jsnu[/** * Module to navigation between users in a course. * * @module report_competency/user_course_navigation * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("report_competency/user_course_navigation",["jquery"],(function($){var UserCourseNavigation=function(userSelector,moduleSelector,baseUrl,userId,courseId,moduleId){this._baseUrl=baseUrl,this._userId=userId+"",this._courseId=courseId,this._moduleId=moduleId,$(userSelector).on("change",this._userChanged.bind(this)),$(moduleSelector).on("change",this._moduleChanged.bind(this))};return UserCourseNavigation.prototype._userChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_userChanged");var queryStr="?user="+$(e.target).val()+"&id="+this._courseId+"&mod="+this._moduleId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._moduleChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_moduleChanged");var queryStr="?mod="+$(e.target).val()+"&id="+this._courseId+"&user="+this._userId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._userId=null,UserCourseNavigation.prototype._moduleId=null,UserCourseNavigation.prototype._courseId=null,UserCourseNavigation.prototype._baseUrl=null,UserCourseNavigation})); //# sourceMappingURL=user_course_navigation.min.js.mapPK3|\_build/grading_popup.min.js.mapnu[{"version":3,"file":"grading_popup.min.js","sources":["../src/grading_popup.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to enable inline editing of a comptency grade.\n *\n * @module report_competency/grading_popup\n * @copyright 2015 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'core/templates', 'tool_lp/dialogue'],\n function($, notification, str, ajax, log, templates, Dialogue) {\n\n /**\n * GradingPopup\n *\n * @class report_competency/grading_popup\n * @param {String} regionSelector The regionSelector\n * @param {String} userCompetencySelector The userCompetencySelector\n */\n var GradingPopup = function(regionSelector, userCompetencySelector) {\n this._regionSelector = regionSelector;\n this._userCompetencySelector = userCompetencySelector;\n\n $(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this));\n };\n\n /**\n * Get the data from the clicked cell and open the popup.\n *\n * @method _handleClick\n * @param {Event} e The event\n */\n GradingPopup.prototype._handleClick = function(e) {\n var cell = $(e.target).closest(this._userCompetencySelector);\n var competencyId = $(cell).data('competencyid');\n var courseId = $(cell).data('courseid');\n var userId = $(cell).data('userid');\n\n log.debug('Clicked on cell: competencyId=' + competencyId + ', courseId=' + courseId + ', userId=' + userId);\n\n var requests = ajax.call([{\n methodname: 'tool_lp_data_for_user_competency_summary_in_course',\n args: {userid: userId, competencyid: competencyId, courseid: courseId},\n }, {\n methodname: 'core_competency_user_competency_viewed_in_course',\n args: {userid: userId, competencyid: competencyId, courseid: courseId},\n }]);\n\n $.when(requests[0], requests[1])\n .then(this._contextLoaded.bind(this))\n .catch(notification.exception);\n };\n\n /**\n * We loaded the context, now render the template.\n *\n * @method _contextLoaded\n * @param {Object} context\n * @returns {Promise}\n */\n GradingPopup.prototype._contextLoaded = function(context) {\n // We have to display user info in popup.\n context.displayuser = true;\n\n M.util.js_pending('report_competency/grading_popup:_contextLoaded');\n\n return $.when(\n str.get_string('usercompetencysummary', 'report_competency'),\n templates.render('tool_lp/user_competency_summary_in_course', context)\n )\n .then(function(title, templateData) {\n return new Dialogue(\n title,\n templateData[0],\n function() {\n templates.runTemplateJS(templateData[1]);\n M.util.js_complete('report_competency/grading_popup:_contextLoaded');\n },\n this._refresh.bind(this),\n true\n );\n }.bind(this));\n };\n\n /**\n * Refresh the page.\n *\n * @method _refresh\n * @returns {Promise}\n */\n GradingPopup.prototype._refresh = function() {\n var region = $(this._regionSelector);\n var courseId = region.data('courseid');\n var moduleId = region.data('moduleid');\n var userId = region.data('userid');\n\n // The module id is expected to be an integer, so don't pass empty string.\n if (moduleId === '') {\n moduleId = 0;\n }\n\n return ajax.call([{\n methodname: 'report_competency_data_for_report',\n args: {courseid: courseId, userid: userId, moduleid: moduleId},\n done: this._pageContextLoaded.bind(this),\n fail: notification.exception\n }]);\n };\n\n /**\n * We loaded the context, now render the template.\n *\n * @method _pageContextLoaded\n * @param {Object} context\n */\n GradingPopup.prototype._pageContextLoaded = function(context) {\n templates.render('report_competency/report', context)\n .then(function(html, js) {\n templates.replaceNode(this._regionSelector, html, js);\n\n return;\n }.bind(this))\n .catch(notification.exception);\n };\n\n /** @property {String} The selector for the region with the user competencies */\n GradingPopup.prototype._regionSelector = null;\n /** @property {String} The selector for the region with a single user competencies */\n GradingPopup.prototype._userCompetencySelector = null;\n\n return GradingPopup;\n});\n"],"names":["define","$","notification","str","ajax","log","templates","Dialogue","GradingPopup","regionSelector","userCompetencySelector","_regionSelector","_userCompetencySelector","this","on","_handleClick","bind","prototype","e","cell","target","closest","competencyId","data","courseId","userId","debug","requests","call","methodname","args","userid","competencyid","courseid","when","then","_contextLoaded","catch","exception","context","displayuser","M","util","js_pending","get_string","render","title","templateData","runTemplateJS","js_complete","_refresh","region","moduleId","moduleid","done","_pageContextLoaded","fail","html","js","replaceNode"],"mappings":";;;;;;;AAuBAA,yCAAO,CAAC,SAAU,oBAAqB,WAAY,YAAa,WAAY,iBAAkB,qBACvF,SAASC,EAAGC,aAAcC,IAAKC,KAAMC,IAAKC,UAAWC,cASpDC,aAAe,SAASC,eAAgBC,6BACnCC,gBAAkBF,oBAClBG,wBAA0BF,uBAE/BT,EAAEY,KAAKF,iBAAiBG,GAAG,QAASD,KAAKD,wBAAyBC,KAAKE,aAAaC,KAAKH,eAS7FL,aAAaS,UAAUF,aAAe,SAASG,OACvCC,KAAOlB,EAAEiB,EAAEE,QAAQC,QAAQR,KAAKD,yBAChCU,aAAerB,EAAEkB,MAAMI,KAAK,gBAC5BC,SAAWvB,EAAEkB,MAAMI,KAAK,YACxBE,OAASxB,EAAEkB,MAAMI,KAAK,UAE1BlB,IAAIqB,MAAM,iCAAmCJ,aAAe,cAAgBE,SAAW,YAAcC,YAEjGE,SAAWvB,KAAKwB,KAAK,CAAC,CACtBC,WAAY,qDACZC,KAAM,CAACC,OAAQN,OAAQO,aAAcV,aAAcW,SAAUT,WAC9D,CACCK,WAAY,mDACZC,KAAM,CAACC,OAAQN,OAAQO,aAAcV,aAAcW,SAAUT,aAGjEvB,EAAEiC,KAAKP,SAAS,GAAIA,SAAS,IAC5BQ,KAAKtB,KAAKuB,eAAepB,KAAKH,OAC9BwB,MAAMnC,aAAaoC,YAUxB9B,aAAaS,UAAUmB,eAAiB,SAASG,gBAE7CA,QAAQC,aAAc,EAEtBC,EAAEC,KAAKC,WAAW,kDAEX1C,EAAEiC,KACL/B,IAAIyC,WAAW,wBAAyB,qBACxCtC,UAAUuC,OAAO,4CAA6CN,UAEjEJ,KAAK,SAASW,MAAOC,qBACX,IAAIxC,SACPuC,MACAC,aAAa,IACb,WACIzC,UAAU0C,cAAcD,aAAa,IACrCN,EAAEC,KAAKO,YAAY,oDAEvBpC,KAAKqC,SAASlC,KAAKH,OACnB,IAENG,KAAKH,QASXL,aAAaS,UAAUiC,SAAW,eAC1BC,OAASlD,EAAEY,KAAKF,iBAChBa,SAAW2B,OAAO5B,KAAK,YACvB6B,SAAWD,OAAO5B,KAAK,YACvBE,OAAS0B,OAAO5B,KAAK,gBAGR,KAAb6B,WACAA,SAAW,GAGRhD,KAAKwB,KAAK,CAAC,CACdC,WAAY,oCACZC,KAAM,CAACG,SAAUT,SAAUO,OAAQN,OAAQ4B,SAAUD,UACrDE,KAAMzC,KAAK0C,mBAAmBvC,KAAKH,MACnC2C,KAAMtD,aAAaoC,cAU3B9B,aAAaS,UAAUsC,mBAAqB,SAAShB,SACjDjC,UAAUuC,OAAO,2BAA4BN,SAC5CJ,KAAK,SAASsB,KAAMC,IACjBpD,UAAUqD,YAAY9C,KAAKF,gBAAiB8C,KAAMC,KAGpD1C,KAAKH,OACNwB,MAAMnC,aAAaoC,YAIxB9B,aAAaS,UAAUN,gBAAkB,KAEzCH,aAAaS,UAAUL,wBAA0B,KAE1CJ"}PKC~\{*!!src/bootstrap/scrollspy.jsnu[/** * -------------------------------------------------------------------------- * Bootstrap scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ import BaseComponent from './base-component' import EventHandler from './dom/event-handler' import SelectorEngine from './dom/selector-engine' import { defineJQueryPlugin, getElement, isDisabled, isVisible } from './util/index' /** * Constants */ const NAME = 'scrollspy' const DATA_KEY = 'bs.scrollspy' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' const EVENT_ACTIVATE = `activate${EVENT_KEY}` const EVENT_CLICK = `click${EVENT_KEY}` const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}` const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item' const CLASS_NAME_ACTIVE = 'active' const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]' const SELECTOR_TARGET_LINKS = '[href]' const SELECTOR_NAV_LIST_GROUP = '.nav, .list-group' const SELECTOR_NAV_LINKS = '.nav-link' const SELECTOR_NAV_ITEMS = '.nav-item' const SELECTOR_LIST_ITEMS = '.list-group-item' const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_NAV_ITEMS} > ${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}` const SELECTOR_DROPDOWN = '.dropdown' const SELECTOR_DROPDOWN_TOGGLE = '.dropdown-toggle' const Default = { offset: null, // TODO: v6 @deprecated, keep it for backwards compatibility reasons rootMargin: '0px 0px -25%', smoothScroll: false, target: null, threshold: [0.1, 0.5, 1] } const DefaultType = { offset: '(number|null)', // TODO v6 @deprecated, keep it for backwards compatibility reasons rootMargin: 'string', smoothScroll: 'boolean', target: 'element', threshold: 'array' } /** * Class definition */ class ScrollSpy extends BaseComponent { constructor(element, config) { super(element, config) // this._element is the observablesContainer and config.target the menu links wrapper this._targetLinks = new Map() this._observableSections = new Map() this._rootElement = getComputedStyle(this._element).overflowY === 'visible' ? null : this._element this._activeTarget = null this._observer = null this._previousScrollData = { visibleEntryTop: 0, parentScrollTop: 0 } this.refresh() // initialize } // Getters static get Default() { return Default } static get DefaultType() { return DefaultType } static get NAME() { return NAME } // Public refresh() { this._initializeTargetsAndObservables() this._maybeEnableSmoothScroll() if (this._observer) { this._observer.disconnect() } else { this._observer = this._getNewObserver() } for (const section of this._observableSections.values()) { this._observer.observe(section) } } dispose() { this._observer.disconnect() super.dispose() } // Private _configAfterMerge(config) { // TODO: on v6 target should be given explicitly & remove the {target: 'ss-target'} case config.target = getElement(config.target) || document.body // TODO: v6 Only for backwards compatibility reasons. Use rootMargin only config.rootMargin = config.offset ? `${config.offset}px 0px -30%` : config.rootMargin if (typeof config.threshold === 'string') { config.threshold = config.threshold.split(',').map(value => Number.parseFloat(value)) } return config } _maybeEnableSmoothScroll() { if (!this._config.smoothScroll) { return } // unregister any previous listeners EventHandler.off(this._config.target, EVENT_CLICK) EventHandler.on(this._config.target, EVENT_CLICK, SELECTOR_TARGET_LINKS, event => { const observableSection = this._observableSections.get(event.target.hash) if (observableSection) { event.preventDefault() const root = this._rootElement || window const height = observableSection.offsetTop - this._element.offsetTop if (root.scrollTo) { root.scrollTo({ top: height, behavior: 'smooth' }) return } // Chrome 60 doesn't support `scrollTo` root.scrollTop = height } }) } _getNewObserver() { const options = { root: this._rootElement, threshold: this._config.threshold, rootMargin: this._config.rootMargin } return new IntersectionObserver(entries => this._observerCallback(entries), options) } // The logic of selection _observerCallback(entries) { const targetElement = entry => this._targetLinks.get(`#${entry.target.id}`) const activate = entry => { this._previousScrollData.visibleEntryTop = entry.target.offsetTop this._process(targetElement(entry)) } const parentScrollTop = (this._rootElement || document.documentElement).scrollTop const userScrollsDown = parentScrollTop >= this._previousScrollData.parentScrollTop this._previousScrollData.parentScrollTop = parentScrollTop for (const entry of entries) { if (!entry.isIntersecting) { this._activeTarget = null this._clearActiveClass(targetElement(entry)) continue } const entryIsLowerThanPrevious = entry.target.offsetTop >= this._previousScrollData.visibleEntryTop // if we are scrolling down, pick the bigger offsetTop if (userScrollsDown && entryIsLowerThanPrevious) { activate(entry) // if parent isn't scrolled, let's keep the first visible item, breaking the iteration if (!parentScrollTop) { return } continue } // if we are scrolling up, pick the smallest offsetTop if (!userScrollsDown && !entryIsLowerThanPrevious) { activate(entry) } } } _initializeTargetsAndObservables() { this._targetLinks = new Map() this._observableSections = new Map() const targetLinks = SelectorEngine.find(SELECTOR_TARGET_LINKS, this._config.target) for (const anchor of targetLinks) { // ensure that the anchor has an id and is not disabled if (!anchor.hash || isDisabled(anchor)) { continue } const observableSection = SelectorEngine.findOne(decodeURI(anchor.hash), this._element) // ensure that the observableSection exists & is visible if (isVisible(observableSection)) { this._targetLinks.set(decodeURI(anchor.hash), anchor) this._observableSections.set(anchor.hash, observableSection) } } } _process(target) { if (this._activeTarget === target) { return } this._clearActiveClass(this._config.target) this._activeTarget = target target.classList.add(CLASS_NAME_ACTIVE) this._activateParents(target) EventHandler.trigger(this._element, EVENT_ACTIVATE, { relatedTarget: target }) } _activateParents(target) { // Activate dropdown parents if (target.classList.contains(CLASS_NAME_DROPDOWN_ITEM)) { SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE, target.closest(SELECTOR_DROPDOWN)) .classList.add(CLASS_NAME_ACTIVE) return } for (const listGroup of SelectorEngine.parents(target, SELECTOR_NAV_LIST_GROUP)) { // Set triggered links parents as active // With both