diff --git a/admin/settings/subsystems.php b/admin/settings/subsystems.php
index 597e36b08cb..e31b0438fac 100644
--- a/admin/settings/subsystems.php
+++ b/admin/settings/subsystems.php
@@ -51,4 +51,31 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$optionalsubsystems->add(new admin_setting_configcheckbox('enableanalytics', new lang_string('enableanalytics', 'admin'),
new lang_string('configenableanalytics', 'admin'), 1, 1, 0));
+
+
+ $fullunicodesupport = true;
+ if ($DB->get_dbfamily() == 'mysql') {
+ $collation = $DB->get_dbcollation();
+ $collationinfo = explode('_', $collation);
+ $charset = reset($collationinfo);
+
+ if ($charset !== 'utf8') {
+ $fullunicodesupport = false;
+ }
+ }
+
+ if ($fullunicodesupport) {
+ $optionalsubsystems->add(new admin_setting_configcheckbox(
+ 'allowemojipicker',
+ new lang_string('allowemojipicker', 'admin'),
+ new lang_string('configallowemojipicker', 'admin'),
+ 1
+ ));
+ } else {
+ $optionalsubsystems->add(new admin_setting_description(
+ 'allowemojipicker',
+ new lang_string('allowemojipicker', 'admin'),
+ new lang_string('configallowemojipickerincompatible', 'admin')
+ ));
+ }
}
diff --git a/lang/en/admin.php b/lang/en/admin.php
index 3a512497795..5711680e38d 100644
--- a/lang/en/admin.php
+++ b/lang/en/admin.php
@@ -53,6 +53,7 @@ $string['allowcoursethemes'] = 'Allow course themes';
$string['allowediplist'] = 'Allowed IP list';
$string['allowedemaildomains'] = 'Allowed email domains';
$string['allowemailaddresses'] = 'Allowed email domains';
+$string['allowemojipicker'] = 'Enable emoji picker';
$string['allowindexing'] = 'Allow indexing by search engines';
$string['allowindexing_desc'] = 'This determines whether to allow search engines to index your site. "Everywhere" will allow the search engines to search everywhere including login and signup pages, which means sites with Force Login turned on are still indexed. To avoid the risk of spam involved with the signup page being searchable, use "Everywhere except login and signup pages". "Nowhere" will tell search engines not to index any page. Note this is only a tag in the header of the site. It is up to the search engine to respect the tag.';
$string['allowindexingeverywhere'] = 'Everywhere';
@@ -153,6 +154,8 @@ $string['configallowcohortthemes'] = 'If you enable this, then themes can be set
$string['configallowcoursethemes'] = 'If you enable this, then courses will be allowed to set their own themes. Course themes override all other theme choices (site, user, or session themes)';
$string['configallowedemaildomains'] = 'List email domains that are allowed to be disclosed in the "From" section of outgoing email. The default of "Empty" will use the No-reply address for all outgoing email. The use of wildcards is allowed e.g. *.example.com will allow emails sent from any subdomain of example.com, but not example.com itself. This will require separate entry.';
$string['configallowemailaddresses'] = 'To restrict new email addresses to particular domains, list them here separated by spaces. All other domains will be rejected. To allow subdomains, add the domain with a preceding \'.\'. To allow a root domain together with its subdomains, add the domain twice - once with a preceding \'.\' and once without e.g. .ourcollege.edu.au ourcollege.edu.au.';
+$string['configallowemojipicker'] = 'If enabled, the emoji picker will be available within the site';
+$string['configallowemojipickerincompatible'] = 'Your current database configuration does not properly support emojis. In order to enable the emoji picker you will need to upgrade your database for full unicode support.';
$string['configallowguestmymoodle'] = 'If enabled guests can access Dashboard, otherwise guests are redirected to the site front page.';
$string['configallowobjectembed'] = 'As a default security measure, normal users are not allowed to embed multimedia (like Flash) within texts using explicit EMBED and OBJECT tags in their HTML (although it can still be done safely using the mediaplugins filter). If you wish to allow these tags then enable this option.';
$string['configallowoverride'] = 'You can allow people with the roles on the left side to override some of the column roles';
diff --git a/message/amd/build/message_drawer_view_conversation.min.js b/message/amd/build/message_drawer_view_conversation.min.js
index f3bcd8c1f73..dddf26491a9 100644
--- a/message/amd/build/message_drawer_view_conversation.min.js
+++ b/message/amd/build/message_drawer_view_conversation.min.js
@@ -1,2 +1,2 @@
-function _typeof(e){if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(e){return typeof e}}else{_typeof=function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e}}return _typeof(e)}define ("core_message/message_drawer_view_conversation",["jquery","core/auto_rows","core/backoff_timer","core/custom_interaction_events","core/notification","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(s,e,t,n,a,d,i,o,r,g,l,m,c,E,I,C,_){var u={},T=null,A=!1,f=0,h=null,O=!1,M=[],p=!0,N=!1,S=[],v=null,b=[],U=g.NEWEST_MESSAGES_FIRST,R=g.LOAD_MESSAGE_LIMIT,L=g.MILLISECONDS_IN_SEC,D=g.SELECTORS,y=g.CONVERSATION_TYPES,P=function(){if(!T||T.type==y.PUBLIC){return null}var e=T.loggedInUserId;if(T.type==y.SELF){return e}var s=Object.keys(T.members).filter(function(s){return e!=s});return s.length?s[0]:null},B=function(e){return Object.keys(u).reduce(function(s,t){if(!s){var n=u[t].state;if(n.type!=y.PUBLIC){if(e in n.members){s=n.id}}}return s},null)},F=function(e){return{id:parseInt(e.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:[]}},w=function(){return f},k=function(e){f=e;u[T.id].messagesOffset=e},V=function(){return A},x=function(e){A=e;u[T.id].loadedAllMessages=e},G=function(e){return e.find(D.MESSAGES_CONTAINER)},q=function(e){return{id:e.id,name:e.name,subname:e.subname,imageUrl:e.imageUrl,isFavourite:e.isFavourite,isMuted:e.isMuted,type:e.type,totalMemberCount:e.totalMemberCount,loggedInUserId:e.loggedInUserId,messages:e.messages.map(function(e){return s.extend({},e)}),members:Object.keys(e.members).map(function(t){var n=s.extend({},e.members[t]);n.contactrequests=e.members[t].contactrequests.map(function(e){return s.extend({},e)});return n})}},Q=function(e,s){var t=e.id,n=t==s?y.SELF:y.PRIVATE,d=c.setLoadingMembers(T,!0);d=c.setLoadingMessages(d,!0);v(d);return o.getMemberInfo(t,[s],!0,!0).then(function(e){if(e.length){return e[0]}else{throw new Error("Unable to load other user profile")}}).then(function(s){var t=n==y.SELF?[s]:[s,e],a=c.addMembers(T,t);a=c.setLoadingMembers(a,!1);a=c.setLoadingMessages(a,!1);a=c.setName(a,s.fullname);a=c.setType(a,n);a=c.setImageUrl(a,s.profileimageurl);a=c.setTotalMemberCount(a,t.length);v(a);return s}).catch(function(e){var s=c.setLoadingMembers(T,!1);v(s);a.exception(e)})},K=function(e,s){var t=null;if(e.type==y.PRIVATE){var n=e.members.filter(function(e){return e.id!=s});t=n.length?n[0]:null}else if(e.type==y.SELF){t=e.members[0]}var a=e.name,d=e.imageurl;if(e.type!=y.PUBLIC){a=a||t?t.fullname:"";d=d||t?t.profileimageurl:""}var i=c.addMembers(T,e.members);i=c.setName(i,a);i=c.setSubname(i,e.subname);i=c.setType(i,e.type);i=c.setImageUrl(i,d);i=c.setTotalMemberCount(i,e.membercount);i=c.setIsFavourite(i,e.isfavourite);i=c.setIsMuted(i,e.ismuted);i=c.addMessages(i,e.messages);i=c.setCanDeleteMessagesForAllUsers(i,e.candeletemessagesforallusers);return i},j=function(e,s,t,n,d){var i=s.id,r=c.setLoadingMembers(T,!0);r=c.setLoadingMessages(r,!0);v(r);return o.getConversation(i,e,!0,!0,0,0,t+1,n,d).then(function(e){if(e.messages.length>t){e.messages=e.messages.slice(1)}else{x(!0)}k(n+t);return e}).then(function(e){var t=e.members.filter(function(e){return e.id==s.id});if(1>t.length){e.members=e.members.concat([s])}var n=K(e,s.id);n=c.setLoadingMembers(n,!1);n=c.setLoadingMessages(n,!1);return v(n).then(function(){return e})}).then(function(){return Y(e)}).catch(function(e){var s=c.setLoadingMembers(T,!1);s=c.setLoadingMessages(s,!1);v(s);a.exception(e)})},W=function(e,s,t,n){var d=e.members.filter(function(e){return e.id==s.id});if(1>d.length){e.members=e.members.concat([s])}var i=e.messages.length,o=i>=t,r=K(e,s.id);r=c.setLoadingMembers(r,!1);r=c.setLoadingMessages(r,!o);var g=v(r);return g.then(function(){if(!o){return J(e.id,t,i,n,[])}else{return{messages:e.messages}}}).then(function(){var e=T.messages;k(e.length);Y(T.id);return e}).catch(a.exception)},J=function(e,s,t,n,a,d){return o.getMessages(T.loggedInUserId,e,s?s+1:s,t,n,d).then(function(e){if(e.messages.length&&a.length){e.messages=e.messages.filter(function(e){return 0>a.indexOf(parseInt(e.id,10))})}return e}).then(function(e){if(!s){return e}else if(e.messages.length>s){e.messages=e.messages.slice(0,-1)}else{x(!0)}return e}).then(function(e){var s=e.members.filter(function(e){return!(e.id in T.members)}),t=c.addMembers(T,s);t=c.addMessages(t,e.messages);t=c.setLoadingMessages(t,!1);return v(t).then(function(){return e})}).catch(function(e){var s=c.setLoadingMessages(T,!1);v(s);throw e})},X=function(e,t){return function(){var n=T.messages,a=n.length?n[n.length-1]:null,o=a?a.timeCreated:null;if(o&&!p&&!N){for(var g=[],l=n.length-1,m;0<=l;l--){m=n[l];if(m.timeCreated===o){g.push(m.id)}else{break}}return J(e,0,0,t,g,o).then(function(s){if(s.messages.length){h.restart();var t=q(T);d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,t);return Y(e)}else{return s}})}return s.Deferred().resolve().promise()}},Y=function(e){var s=T.loggedInUserId;return o.markAllConversationMessagesAsRead(s,e).then(function(){var s=c.markMessagesAsRead(T,T.messages);d.publish(r.CONVERSATION_READ,e);return v(s)})},H=function(e){ce(e);var s=c.addPendingBlockUsersById(T,[e]);v(s)},z=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.blockUser(T.loggedInUserId,e).then(function(s){var t=c.addMembers(T,[s]);t=c.removePendingBlockUsersById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_BLOCKED,e);return v(t)})},Z=function(e){ce(e);var s=c.addPendingUnblockUsersById(T,[e]);v(s)},$=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.unblockUser(T.loggedInUserId,e).then(function(s){var t=c.addMembers(T,[s]);t=c.removePendingUnblockUsersById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_UNBLOCKED,e);return v(t)})},ee=function(e){ce(e);var s=c.addPendingRemoveContactsById(T,[e]);v(s)},se=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.deleteContacts(T.loggedInUserId,[e]).then(function(s){var t=c.addMembers(T,s);t=c.removePendingRemoveContactsById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_REMOVED,e);return v(t)})},te=function(e){ce(e);var s=c.addPendingAddContactsById(T,[e]);v(s)},ne=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.createContactRequest(T.loggedInUserId,e).then(function(e){if(!e.request){throw new Error(e.warnings[0].message)}return e.request}).then(function(s){var t=c.removePendingAddContactsById(T,[e]);t=c.addContactRequests(t,[s]);t=c.setLoadingConfirmAction(t,!1);return v(t)})},ae=function(){var e=T.loggedInUserId,s=T.id;return o.setFavouriteConversations(e,[s]).then(function(){var e=c.setIsFavourite(T,!0);return v(e)}).then(function(){return d.publish(r.CONVERSATION_SET_FAVOURITE,q(T))})},de=function(){var e=T.loggedInUserId,s=T.id;return o.unsetFavouriteConversations(e,[s]).then(function(){var e=c.setIsFavourite(T,!1);return v(e)}).then(function(){return d.publish(r.CONVERSATION_UNSET_FAVOURITE,q(T))})},ie=function(){var e=T.loggedInUserId,s=T.id;return o.setMutedConversations(e,[s]).then(function(){var e=c.setIsMuted(T,!0);return v(e)}).then(function(){return d.publish(r.CONVERSATION_SET_MUTED,q(T))})},oe=function(){var e=T.loggedInUserId,s=T.id;return o.unsetMutedConversations(e,[s]).then(function(){var e=c.setIsMuted(T,!1);return v(e)}).then(function(){return d.publish(r.CONVERSATION_UNSET_MUTED,q(T))})},re=function(e){var s=T.selectedMessageIds;ce(e);var t=c.addPendingDeleteMessagesById(T,s);v(t)},ge=function(){var e=T.pendingDeleteMessageIds,t=T.messages.filter(function(s){return 0<=e.indexOf(s.id)&&("sent"==s.sendState||null===s.sendState)}),n=c.setLoadingConfirmAction(T,!0);v(n);var i=s.Deferred().resolve().promise();if(t.length){var g=t.map(function(e){return e.id});if(n.deleteMessagesForAllUsers){i=o.deleteMessagesForAllUsers(T.loggedInUserId,g)}else{i=o.deleteMessages(T.loggedInUserId,g)}}return i.then(function(){var s=c.removeMessagesById(T,e);s=c.removePendingDeleteMessagesById(s,e);s=c.removeSelectedMessagesById(s,e);s=c.setLoadingConfirmAction(s,!1);s=c.setDeleteMessagesForAllUsers(s,!1);var t=T.messages[T.messages.length-1],n=s.messages.length?s.messages[s.messages.length-1]:null;if(n&&n.id!=t.id){var a=q(s);d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,a)}else if(!s.messages.length){d.publish(r.CONVERSATION_DELETED,s.id)}return v(s)}).catch(a.exception)},le=function(e){ce(e);var s=c.setPendingDeleteConversation(T,!0);v(s)},me=function(){var e=c.setLoadingConfirmAction(T,!0);v(e);return o.deleteConversation(T.loggedInUserId,T.id).then(function(){var e=c.removeMessages(T,T.messages);e=c.removeSelectedMessagesById(e,T.selectedMessageIds);e=c.setPendingDeleteConversation(e,!1);e=c.setLoadingConfirmAction(e,!1);d.publish(r.CONVERSATION_DELETED,e.id);return v(e)})},ce=function(e){var s=T.pendingDeleteMessageIds,t=c.removePendingAddContactsById(T,[e]);t=c.removePendingRemoveContactsById(t,[e]);t=c.removePendingUnblockUsersById(t,[e]);t=c.removePendingBlockUsersById(t,[e]);t=c.removePendingDeleteMessagesById(t,s);t=c.setPendingDeleteConversation(t,!1);t=c.setDeleteMessagesForAllUsers(t,!1);v(t)},Ee=function(e){var s=T.loggedInUserId,t=T.members[e].contactrequests.filter(function(e){return e.requesteduserid==s}),n=t[0],a=c.setLoadingConfirmAction(T,!0);v(a);return o.acceptContactRequest(e,s).then(function(e){var s=c.removeContactRequests(T,[n]);s=c.addMembers(T,[e]);s=c.setLoadingConfirmAction(s,!1);return v(s)}).then(function(){d.publish(r.CONTACT_ADDED,T.members[e]);d.publish(r.CONTACT_REQUEST_ACCEPTED,n)})},Ie=function(e){var s=T.loggedInUserId,t=T.members[e].contactrequests.filter(function(e){return e.requesteduserid==s}),n=t[0],a=c.setLoadingConfirmAction(T,!0);v(a);return o.declineContactRequest(e,s).then(function(e){var s=c.removeContactRequests(T,[n]);s=c.addMembers(T,[e]);s=c.setLoadingConfirmAction(s,!1);return v(s)}).then(function(){d.publish(r.CONTACT_REQUEST_DECLINED,n)})},Ce=function(){if(N){return}if(!S.length){return}N=!0;var e=S.slice();S=[];var t=T.id,n=null,a=e.map(function(e){return e.text}),g=e.map(function(e){return e.id}),l=null,m=null;if(!t&&T.type!=y.PUBLIC){var E=P();l=o.sendMessagesToUser(E,a).then(function(e){if(e.length){n=parseInt(e[0].conversationid,10);m=e[0].candeletemessagesforallusers}return e})}else{l=o.sendMessagesToConversation(t,a)}l.then(function(s){var t=s.map(function(e){return e.id}),a=[],i=[],o=[];e.forEach(function(e,t){var n=s[t];a.push([e,n]);if(0<=T.selectedMessageIds.indexOf(e.id)){i.push(e.id);o.push(n.id)}});var g=c.updateMessages(T,a);g=c.setMessagesSendSuccessById(g,t);if(i.length){g=c.removeSelectedMessagesById(g,i)}if(o.length){g=c.addSelectedMessagesById(g,o)}var l=q(g);if(!g.id){g=c.setId(g,n);l.id=n;we(n);d.publish(r.CONVERSATION_CREATED,l);g=c.setCanDeleteMessagesForAllUsers(g,m)}v(g);N=!1;Ce();d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,l)}).catch(function(t){if(t.message){var e=s.Deferred().resolve(t.message).promise()}else{var e=i.get_string("unknownerror","core")}var n=function(e){var s=c.setMessagesSendFailById(T,g,e);v(s);N=!1;Ce()};e.then(n).catch(function(s){var e=s.message||"Something went wrong!";n(e)})})},_e=function(e){var s="temp"+Date.now(),t={id:s,useridfrom:T.loggedInUserId,text:e,timecreated:null},n=c.addMessages(T,[t]);v(n);S.push(t);Ce()},ue=function(e){var s=c.setMessagesSendPendingById(T,[e.id]);v(s);S.push(e);Ce()},Te=function(e){var s=T;if(-1t){e.messages=e.messages.slice(1)}else{x(!0)}k(n+t);return e}).then(function(e){var t=e.members.filter(function(e){return e.id==s.id});if(1>t.length){e.members=e.members.concat([s])}var n=K(e,s.id);n=c.setLoadingMembers(n,!1);n=c.setLoadingMessages(n,!1);return v(n).then(function(){return e})}).then(function(){return Y(e)}).catch(function(e){var s=c.setLoadingMembers(T,!1);s=c.setLoadingMessages(s,!1);v(s);a.exception(e)})},W=function(e,s,t,n){var d=e.members.filter(function(e){return e.id==s.id});if(1>d.length){e.members=e.members.concat([s])}var i=e.messages.length,o=i>=t,r=K(e,s.id);r=c.setLoadingMembers(r,!1);r=c.setLoadingMessages(r,!o);var g=v(r);return g.then(function(){if(!o){return J(e.id,t,i,n,[])}else{return{messages:e.messages}}}).then(function(){var e=T.messages;k(e.length);Y(T.id);return e}).catch(a.exception)},J=function(e,s,t,n,a,d){return o.getMessages(T.loggedInUserId,e,s?s+1:s,t,n,d).then(function(e){if(e.messages.length&&a.length){e.messages=e.messages.filter(function(e){return 0>a.indexOf(parseInt(e.id,10))})}return e}).then(function(e){if(!s){return e}else if(e.messages.length>s){e.messages=e.messages.slice(0,-1)}else{x(!0)}return e}).then(function(e){var s=e.members.filter(function(e){return!(e.id in T.members)}),t=c.addMembers(T,s);t=c.addMessages(t,e.messages);t=c.setLoadingMessages(t,!1);return v(t).then(function(){return e})}).catch(function(e){var s=c.setLoadingMessages(T,!1);v(s);throw e})},X=function(e,t){return function(){var n=T.messages,a=n.length?n[n.length-1]:null,o=a?a.timeCreated:null;if(o&&!p&&!N){for(var g=[],l=n.length-1,m;0<=l;l--){m=n[l];if(m.timeCreated===o){g.push(m.id)}else{break}}return J(e,0,0,t,g,o).then(function(s){if(s.messages.length){f.restart();var t=q(T);d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,t);return Y(e)}else{return s}})}return s.Deferred().resolve().promise()}},Y=function(e){var s=T.loggedInUserId;return o.markAllConversationMessagesAsRead(s,e).then(function(){var s=c.markMessagesAsRead(T,T.messages);d.publish(r.CONVERSATION_READ,e);return v(s)})},H=function(e){ce(e);var s=c.addPendingBlockUsersById(T,[e]);v(s)},z=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.blockUser(T.loggedInUserId,e).then(function(s){var t=c.addMembers(T,[s]);t=c.removePendingBlockUsersById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_BLOCKED,e);return v(t)})},Z=function(e){ce(e);var s=c.addPendingUnblockUsersById(T,[e]);v(s)},$=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.unblockUser(T.loggedInUserId,e).then(function(s){var t=c.addMembers(T,[s]);t=c.removePendingUnblockUsersById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_UNBLOCKED,e);return v(t)})},ee=function(e){ce(e);var s=c.addPendingRemoveContactsById(T,[e]);v(s)},se=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.deleteContacts(T.loggedInUserId,[e]).then(function(s){var t=c.addMembers(T,s);t=c.removePendingRemoveContactsById(t,[e]);t=c.setLoadingConfirmAction(t,!1);d.publish(r.CONTACT_REMOVED,e);return v(t)})},te=function(e){ce(e);var s=c.addPendingAddContactsById(T,[e]);v(s)},ne=function(e){var s=c.setLoadingConfirmAction(T,!0);v(s);return o.createContactRequest(T.loggedInUserId,e).then(function(e){if(!e.request){throw new Error(e.warnings[0].message)}return e.request}).then(function(s){var t=c.removePendingAddContactsById(T,[e]);t=c.addContactRequests(t,[s]);t=c.setLoadingConfirmAction(t,!1);return v(t)})},ae=function(){var e=T.loggedInUserId,s=T.id;return o.setFavouriteConversations(e,[s]).then(function(){var e=c.setIsFavourite(T,!0);return v(e)}).then(function(){return d.publish(r.CONVERSATION_SET_FAVOURITE,q(T))})},de=function(){var e=T.loggedInUserId,s=T.id;return o.unsetFavouriteConversations(e,[s]).then(function(){var e=c.setIsFavourite(T,!1);return v(e)}).then(function(){return d.publish(r.CONVERSATION_UNSET_FAVOURITE,q(T))})},ie=function(){var e=T.loggedInUserId,s=T.id;return o.setMutedConversations(e,[s]).then(function(){var e=c.setIsMuted(T,!0);return v(e)}).then(function(){return d.publish(r.CONVERSATION_SET_MUTED,q(T))})},oe=function(){var e=T.loggedInUserId,s=T.id;return o.unsetMutedConversations(e,[s]).then(function(){var e=c.setIsMuted(T,!1);return v(e)}).then(function(){return d.publish(r.CONVERSATION_UNSET_MUTED,q(T))})},re=function(e){var s=T.selectedMessageIds;ce(e);var t=c.addPendingDeleteMessagesById(T,s);v(t)},ge=function(){var e=T.pendingDeleteMessageIds,t=T.messages.filter(function(s){return 0<=e.indexOf(s.id)&&("sent"==s.sendState||null===s.sendState)}),n=c.setLoadingConfirmAction(T,!0);v(n);var i=s.Deferred().resolve().promise();if(t.length){var g=t.map(function(e){return e.id});if(n.deleteMessagesForAllUsers){i=o.deleteMessagesForAllUsers(T.loggedInUserId,g)}else{i=o.deleteMessages(T.loggedInUserId,g)}}return i.then(function(){var s=c.removeMessagesById(T,e);s=c.removePendingDeleteMessagesById(s,e);s=c.removeSelectedMessagesById(s,e);s=c.setLoadingConfirmAction(s,!1);s=c.setDeleteMessagesForAllUsers(s,!1);var t=T.messages[T.messages.length-1],n=s.messages.length?s.messages[s.messages.length-1]:null;if(n&&n.id!=t.id){var a=q(s);d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,a)}else if(!s.messages.length){d.publish(r.CONVERSATION_DELETED,s.id)}return v(s)}).catch(a.exception)},le=function(e){ce(e);var s=c.setPendingDeleteConversation(T,!0);v(s)},me=function(){var e=c.setLoadingConfirmAction(T,!0);v(e);return o.deleteConversation(T.loggedInUserId,T.id).then(function(){var e=c.removeMessages(T,T.messages);e=c.removeSelectedMessagesById(e,T.selectedMessageIds);e=c.setPendingDeleteConversation(e,!1);e=c.setLoadingConfirmAction(e,!1);d.publish(r.CONVERSATION_DELETED,e.id);return v(e)})},ce=function(e){var s=T.pendingDeleteMessageIds,t=c.removePendingAddContactsById(T,[e]);t=c.removePendingRemoveContactsById(t,[e]);t=c.removePendingUnblockUsersById(t,[e]);t=c.removePendingBlockUsersById(t,[e]);t=c.removePendingDeleteMessagesById(t,s);t=c.setPendingDeleteConversation(t,!1);t=c.setDeleteMessagesForAllUsers(t,!1);v(t)},Ee=function(e){var s=T.loggedInUserId,t=T.members[e].contactrequests.filter(function(e){return e.requesteduserid==s}),n=t[0],a=c.setLoadingConfirmAction(T,!0);v(a);return o.acceptContactRequest(e,s).then(function(e){var s=c.removeContactRequests(T,[n]);s=c.addMembers(T,[e]);s=c.setLoadingConfirmAction(s,!1);return v(s)}).then(function(){d.publish(r.CONTACT_ADDED,T.members[e]);d.publish(r.CONTACT_REQUEST_ACCEPTED,n)})},Ie=function(e){var s=T.loggedInUserId,t=T.members[e].contactrequests.filter(function(e){return e.requesteduserid==s}),n=t[0],a=c.setLoadingConfirmAction(T,!0);v(a);return o.declineContactRequest(e,s).then(function(e){var s=c.removeContactRequests(T,[n]);s=c.addMembers(T,[e]);s=c.setLoadingConfirmAction(s,!1);return v(s)}).then(function(){d.publish(r.CONTACT_REQUEST_DECLINED,n)})},Ce=function(){if(N){return}if(!S.length){return}N=!0;var e=S.slice();S=[];var t=T.id,n=null,a=e.map(function(e){return e.text}),g=e.map(function(e){return e.id}),l=null,m=null;if(!t&&T.type!=y.PUBLIC){var E=P();l=o.sendMessagesToUser(E,a).then(function(e){if(e.length){n=parseInt(e[0].conversationid,10);m=e[0].candeletemessagesforallusers}return e})}else{l=o.sendMessagesToConversation(t,a)}l.then(function(s){var t=s.map(function(e){return e.id}),a=[],i=[],o=[];e.forEach(function(e,t){var n=s[t];a.push([e,n]);if(0<=T.selectedMessageIds.indexOf(e.id)){i.push(e.id);o.push(n.id)}});var g=c.updateMessages(T,a);g=c.setMessagesSendSuccessById(g,t);if(i.length){g=c.removeSelectedMessagesById(g,i)}if(o.length){g=c.addSelectedMessagesById(g,o)}var l=q(g);if(!g.id){g=c.setId(g,n);l.id=n;we(n);d.publish(r.CONVERSATION_CREATED,l);g=c.setCanDeleteMessagesForAllUsers(g,m)}v(g);N=!1;Ce();d.publish(r.CONVERSATION_NEW_LAST_MESSAGE,l)}).catch(function(t){if(t.message){var e=s.Deferred().resolve(t.message).promise()}else{var e=i.get_string("unknownerror","core")}var n=function(e){var s=c.setMessagesSendFailById(T,g,e);v(s);N=!1;Ce()};e.then(n).catch(function(s){var e=s.message||"Something went wrong!";n(e)})})},_e=function(e){var s="temp"+Date.now(),t={id:s,useridfrom:T.loggedInUserId,text:e,timecreated:null},n=c.addMessages(T,[t]);v(n);S.push(t);Ce()},ue=function(e){var s=c.setMessagesSendPendingById(T,[e.id]);v(s);S.push(e);Ce()},Te=function(e){var s=T;if(-1.\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/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 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 // 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 };\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 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) {\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\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 };\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 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 };\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 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 };\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 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 };\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 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 };\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\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 };\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\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 };\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\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 };\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 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 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 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 return render(newState);\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 newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\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 return render(newState);\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 // 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 };\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 // 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 };\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 // 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 .catch(function(e) {\n if (e.message) {\n var errorMessage = $.Deferred().resolve(e.message).promise();\n } else {\n var 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 .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 * 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 var message = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: text,\n timecreated: null\n };\n var newState = StateManager.addMessages(viewState, [message]);\n render(newState);\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\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 * @return {Promise} Renderer promise.\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 .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 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 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 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 loadedAllMessages = false;\n messagesOffset = 0;\n newMessagesPollTimer = null;\n isRendering = false;\n renderBuffer = [];\n isResetting = true;\n isSendingMessage = 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 if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\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 // 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 return {\n show: show,\n description: description\n };\n});\n"],"file":"message_drawer_view_conversation.min.js"}
\ No newline at end of file
+{"version":3,"sources":["../src/message_drawer_view_conversation.js"],"names":["define","$","AutoRows","BackOffTimer","CustomEvents","Notification","PubSub","Str","Repository","MessageDrawerEvents","Constants","Patcher","Renderer","StateManager","MessageDrawerRouter","MessageDrawerRoutes","initialiseEmojiAutoComplete","initialiseEmojiPicker","stateCache","viewState","loadedAllMessages","messagesOffset","newMessagesPollTimer","isRendering","renderBuffer","isResetting","isSendingMessage","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","getCachedPrivateConversationIdFromUserId","reduce","carry","id","state","getLoggedInUserProfile","body","parseInt","attr","fullname","profileimageurl","profileimageurlsmall","isonline","showonlinestatus","isblocked","iscontact","isdeleted","canmessage","canmessageevenifblocked","requirescontact","contactrequests","getMessagesOffset","setMessagesOffset","value","hasLoadedAllMessages","setLoadedAllMessages","getMessagesContainer","find","MESSAGES_CONTAINER","formatConversationForEvent","name","subname","imageUrl","isFavourite","isMuted","totalMemberCount","messages","map","message","extend","formattedMember","request","loadEmptyPrivateConversation","loggedInUserProfile","otherUserId","conversationType","PRIVATE","newState","setLoadingMembers","setLoadingMessages","getMemberInfo","then","profiles","Error","profile","addMembers","setName","setType","setImageUrl","setTotalMemberCount","catch","error","exception","updateStateFromConversation","conversation","otherUser","otherUsers","member","imageurl","setSubname","membercount","setIsFavourite","isfavourite","setIsMuted","ismuted","addMessages","setCanDeleteMessagesForAllUsers","candeletemessagesforallusers","loadNewConversation","conversationId","messageLimit","messageOffset","newestFirst","getConversation","slice","hasLoggedInUser","concat","markConversationAsRead","loadExistingConversation","messageCount","hasLoadedEnoughMessages","renderPromise","loadMessages","limit","offset","ignoreList","timeFrom","getMessages","result","indexOf","membersToAdd","getLoadNewMessagesCallback","mostRecentMessage","lastTimeCreated","timeCreated","ignoreMessageIds","i","push","restart","publish","CONVERSATION_NEW_LAST_MESSAGE","Deferred","resolve","promise","markAllConversationMessagesAsRead","markMessagesAsRead","CONVERSATION_READ","requestBlockUser","cancelRequest","addPendingBlockUsersById","blockUser","setLoadingConfirmAction","removePendingBlockUsersById","CONTACT_BLOCKED","requestUnblockUser","addPendingUnblockUsersById","unblockUser","removePendingUnblockUsersById","CONTACT_UNBLOCKED","requestRemoveContact","addPendingRemoveContactsById","removeContact","deleteContacts","removePendingRemoveContactsById","CONTACT_REMOVED","requestAddContact","addPendingAddContactsById","addContact","createContactRequest","response","warnings","removePendingAddContactsById","addContactRequests","setFavourite","setFavouriteConversations","CONVERSATION_SET_FAVOURITE","unsetFavourite","unsetFavouriteConversations","CONVERSATION_UNSET_FAVOURITE","setMuted","setMutedConversations","CONVERSATION_SET_MUTED","unsetMuted","unsetMutedConversations","CONVERSATION_UNSET_MUTED","requestDeleteSelectedMessages","selectedMessageIds","addPendingDeleteMessagesById","deleteSelectedMessages","messageIds","pendingDeleteMessageIds","sentMessages","sendState","deleteMessagesPromise","sentMessageIds","deleteMessagesForAllUsers","deleteMessages","removeMessagesById","removePendingDeleteMessagesById","removeSelectedMessagesById","setDeleteMessagesForAllUsers","prevLastMessage","newLastMessage","CONVERSATION_DELETED","requestDeleteConversation","setPendingDeleteConversation","deleteConversation","removeMessages","acceptContactRequest","requests","requesteduserid","removeContactRequests","CONTACT_ADDED","CONTACT_REQUEST_ACCEPTED","declineContactRequest","CONTACT_REQUEST_DECLINED","processSendMessageBuffer","messagesToSend","newConversationId","messagesText","text","sendMessagePromise","newCanDeleteMessagesForAllUsers","sendMessagesToUser","conversationid","sendMessagesToConversation","newMessageIds","data","selectedToRemove","selectedToAdd","forEach","oldMessage","index","newMessage","updateMessages","setMessagesSendSuccessById","addSelectedMessagesById","setId","resetMessagePollTimer","CONVERSATION_CREATED","e","errorMessage","get_string","handleFailedMessages","setMessagesSendFailById","finalError","sendMessage","Date","now","useridfrom","timecreated","retrySendMessage","setMessagesSendPendingById","toggleSelectMessage","messageId","cancelEditMode","processRenderBuffer","header","footer","renderable","shift","renderPromises","renderFunc","patch","when","apply","deferred","reject","generateRenderFunction","isNewConversation","rendererFunc","initialState","buildInitialState","midnight","syncPatch","buildPatch","generateConfirmActionHandler","actionCallback","loadingConfirmAction","originalEvent","preventDefault","handleSendMessage","target","footerContainer","closest","FOOTER_CONTAINER","textArea","MESSAGE_TEXT_AREA","val","trim","focus","handleSelectMessage","selection","window","getSelection","toString","is","element","MESSAGE","handleRetrySendMessage","stopPropagation","handleCancelEditMode","generateHandleViewContact","namespace","go","VIEW_CONTACT","handleSetFavourite","handleUnsetFavourite","handleSetMuted","handleUnsetMuted","handleDeleteMessagesForAllUsersToggle","newValue","prop","generateHandleViewGroupInfo","VIEW_GROUP_INFO","handleToggleEmojiPicker","setShowEmojiPicker","showEmojiPicker","handleCloseEmojiPicker","EMOJI_PICKER_CONTAINER","TOGGLE_EMOJI_PICKER_BUTTON","registerEventListeners","isLoadingMoreMessages","messagesContainer","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","replace","textAfter","events","activate","enter","escape","scrollTop","scrollLock","on","hasMembers","handler","selector","handlerFunction","enterToSend","document","subscribe","ROUTE_CHANGED","newRouteData","route","VIEW_CONVERSATION","stop","getIncrementalCallback","messagePollMin","messagePollMax","messagePollAfterMax","start","resetState","resetNoConversation","resetNoConversationPromise","getConversationBetweenUsers","getSelfConversation","resetByConversation","resetById","cache","show","conversationOrId","action","isNaN","CAN_RECEIVE_FOCUS","first","currentOtherUserId","description"],"mappings":"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","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/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 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 // 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 };\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 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) {\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\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 };\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 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 };\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 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 };\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 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 };\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 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 };\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\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 };\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\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 };\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\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 };\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 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 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 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 return render(newState);\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 newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\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 return render(newState);\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 // 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 };\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 // 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 };\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 // 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 .catch(function(e) {\n if (e.message) {\n var errorMessage = $.Deferred().resolve(e.message).promise();\n } else {\n var 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 .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 * 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 var message = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: text,\n timecreated: null\n };\n var newState = StateManager.addMessages(viewState, [message]);\n render(newState);\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\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 * @return {Promise} Renderer promise.\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 .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 loadedAllMessages = false;\n messagesOffset = 0;\n newMessagesPollTimer = null;\n isRendering = false;\n renderBuffer = [];\n isResetting = true;\n isSendingMessage = 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 if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\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 // 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 return {\n show: show,\n description: description\n };\n});\n"],"file":"message_drawer_view_conversation.min.js"}
\ No newline at end of file
diff --git a/message/amd/src/message_drawer_view_conversation.js b/message/amd/src/message_drawer_view_conversation.js
index 7e3c0da59f1..7efc94ffcbd 100644
--- a/message/amd/src/message_drawer_view_conversation.js
+++ b/message/amd/src/message_drawer_view_conversation.js
@@ -1600,45 +1600,49 @@ function(
AutoRows.init(footer);
- initialiseEmojiAutoComplete(
- emojiAutoCompleteContainer[0],
- messageTextArea[0],
- function(hasSuggestions) {
- var newState = StateManager.setShowEmojiAutoComplete(viewState, hasSuggestions);
- render(newState);
- },
- function(emoji) {
- var newState = StateManager.setShowEmojiAutoComplete(viewState, false);
+ 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).replace(/\S*$/, '');
- var textAfter = currentText.substring(cursorPos).replace(/^\S*/, '');
+ 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', textBefore.length + emoji.length);
- messageTextArea.prop('selectionEnd', textBefore.length + emoji.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);
- });
+ messageTextArea.prop('selectionStart', cursorPos + emoji.length);
+ messageTextArea.prop('selectionEnd', cursorPos + emoji.length);
+ });
+ }
CustomEvents.define(header, [
CustomEvents.events.activate
diff --git a/message/classes/helper.php b/message/classes/helper.php
index 2116ba39ef5..c6472a4de8b 100644
--- a/message/classes/helper.php
+++ b/message/classes/helper.php
@@ -772,7 +772,8 @@ class helper {
'messageurl' => $messageurl,
'notification' => $notification
],
- 'isdrawer' => $isdrawer
+ 'isdrawer' => $isdrawer,
+ 'showemojipicker' => !empty($CFG->allowemojipicker)
];
if ($sendtouser || $conversationid) {
diff --git a/message/templates/message_drawer_view_conversation_footer_content.mustache b/message/templates/message_drawer_view_conversation_footer_content.mustache
index 683d65a91e5..d786bb926fc 100644
--- a/message/templates/message_drawer_view_conversation_footer_content.mustache
+++ b/message/templates/message_drawer_view_conversation_footer_content.mustache
@@ -35,13 +35,15 @@
}}
-