diff --git a/message/amd/build/message_drawer_view_conversation.min.js b/message/amd/build/message_drawer_view_conversation.min.js index 53c1f623fcb75..daba44105e80e 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){"@babel/helpers - typeof";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/pending","core/pubsub","core/str","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_view_conversation_constants","core_message/message_drawer_view_conversation_patcher","core_message/message_drawer_view_conversation_renderer","core_message/message_drawer_view_conversation_state_manager","core_message/message_drawer_router","core_message/message_drawer_routes","core/emoji/auto_complete","core/emoji/picker"],function(s,e,t,n,a,o,r,d,i,g,l,m,c,_,E,C,I,u){var v={},h=null,T=!1,A=0,f=null,O=!1,p=[],M=!0,S=!1,N=!1,b=[],R=null,U=[],L=l.NEWEST_MESSAGES_FIRST,D=l.LOAD_MESSAGE_LIMIT,w=l.MILLISECONDS_IN_SEC,y=l.SELECTORS,P=l.CONVERSATION_TYPES,B=function(){if(!h||h.type==P.PUBLIC){return null}var e=h.loggedInUserId;if(h.type==P.SELF){return e}var s=Object.keys(h.members).filter(function(s){return e!=s});return s.length?s[0]:null},F=function(e){return Object.keys(v).reduce(function(s,t){if(!s){var n=v[t].state;if(n.type!=P.PUBLIC){if(e in n.members){s=n.id}}}return s},null)},k=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:[]}},V=function(){return A},x=function(e){A=e;v[h.id].messagesOffset=e},G=function(){return T},q=function(e){T=e;v[h.id].loadedAllMessages=e},j=function(e){return e.find(y.MESSAGES_CONTAINER)},K=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?P.SELF:P.PRIVATE,o=_.setLoadingMembers(h,!0);o=_.setLoadingMessages(o,!0);R(o);return i.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==P.SELF?[s]:[s,e],a=_.addMembers(h,t);a=_.setLoadingMembers(a,!1);a=_.setLoadingMessages(a,!1);a=_.setName(a,s.fullname);a=_.setType(a,n);a=_.setImageUrl(a,s.profileimageurl);a=_.setTotalMemberCount(a,t.length);R(a);return s}).catch(function(e){var s=_.setLoadingMembers(h,!1);R(s);a.exception(e)})},W=function(e,s){var t=null;if(e.type==P.PRIVATE){var n=e.members.filter(function(e){return e.id!=s});t=n.length?n[0]:null}else if(e.type==P.SELF){t=e.members[0]}var a=e.name,o=e.imageurl;if(e.type!=P.PUBLIC){a=a||t?t.fullname:"";o=o||t?t.profileimageurl:""}var r=_.addMembers(h,e.members);r=_.setName(r,a);r=_.setSubname(r,e.subname);r=_.setType(r,e.type);r=_.setImageUrl(r,o);r=_.setTotalMemberCount(r,e.membercount);r=_.setIsFavourite(r,e.isfavourite);r=_.setIsMuted(r,e.ismuted);r=_.addMessages(r,e.messages);r=_.setCanDeleteMessagesForAllUsers(r,e.candeletemessagesforallusers);return r},J=function(e,s,t,n,o){var r=s.id,d=_.setLoadingMembers(h,!0);d=_.setLoadingMessages(d,!0);R(d);return i.getConversation(r,e,!0,!0,0,0,t+1,n,o).then(function(e){if(e.messages.length>t){e.messages=e.messages.slice(1)}else{q(!0)}x(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=W(e,s.id);n=_.setLoadingMembers(n,!1);n=_.setLoadingMessages(n,!1);return R(n).then(function(){return e})}).then(function(){return z(e)}).catch(function(e){var s=_.setLoadingMembers(h,!1);s=_.setLoadingMessages(s,!1);R(s);a.exception(e)})},X=function(e,s,t,n){var o=e.members.filter(function(e){return e.id==s.id});if(1>o.length){e.members=e.members.concat([s])}var r=e.messages.length,d=r>=t,i=W(e,s.id);i=_.setLoadingMembers(i,!1);i=_.setLoadingMessages(i,!d);var g=R(i);return g.then(function(){if(!d){return Y(e.id,t,r,n,[])}else{return{messages:e.messages}}}).then(function(){var e=h.messages;x(e.length);z(h.id);return e}).catch(a.exception)},Y=function(e,s,t,n,a,o){return i.getMessages(h.loggedInUserId,e,s?s+1:s,t,n,o).then(function(e){if(e.id!=h.id){e.messages=[];if(e.id in v){delete v[e.id]}}return e}).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{q(!0)}return e}).then(function(e){var s=e.members.filter(function(e){return!(e.id in h.members)}),t=_.addMembers(h,s);t=_.addMessages(t,e.messages);t=_.setLoadingMessages(t,!1);return R(t).then(function(){return e})}).catch(function(e){var s=_.setLoadingMessages(h,!1);R(s);throw e})},H=function(e,t){return function(){var n=h.messages,a=n.length?n[n.length-1]:null,o=a?a.timeCreated:null;if(o&&!M&&!S&&!N){for(var d=[],l=n.length-1,m;0<=l;l--){m=n[l];if(m.timeCreated===o){d.push(m.id)}else{break}}return Y(e,0,0,t,d,o).then(function(s){if(s.messages.length){f.restart();var t=K(h);r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,t);return z(e)}else{return s}})}return s.Deferred().resolve().promise()}},z=function(e){var s=h.loggedInUserId,t=new o("core_message/message_drawer_view_conversation:markConversationAsRead");return i.markAllConversationMessagesAsRead(s,e).then(function(){var s=_.markMessagesAsRead(h,h.messages);r.publish(g.CONVERSATION_READ,e);return R(s)}).then(function(e){t.resolve();return e})},Z=function(e){Ee(e);var s=_.addPendingBlockUsersById(h,[e]);R(s)},$=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new o("core_message/message_drawer_view_conversation:blockUser");R(s);return i.blockUser(h.loggedInUserId,e).then(function(s){var t=_.addMembers(h,[s]);t=_.removePendingBlockUsersById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_BLOCKED,e);return R(t)}).then(function(e){t.resolve();return e})},ee=function(e){Ee(e);var s=_.addPendingUnblockUsersById(h,[e]);R(s)},se=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new o("core_message/message_drawer_view_conversation:unblockUser");R(s);return i.unblockUser(h.loggedInUserId,e).then(function(s){var t=_.addMembers(h,[s]);t=_.removePendingUnblockUsersById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_UNBLOCKED,e);return R(t)}).then(function(e){t.resolve();return e})},te=function(e){Ee(e);var s=_.addPendingRemoveContactsById(h,[e]);R(s)},ne=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new o("core_message/message_drawer_view_conversation:removeContact");R(s);return i.deleteContacts(h.loggedInUserId,[e]).then(function(s){var t=_.addMembers(h,s);t=_.removePendingRemoveContactsById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_REMOVED,e);return R(t)}).then(function(e){t.resolve();return e})},ae=function(e){Ee(e);var s=_.addPendingAddContactsById(h,[e]);R(s)},oe=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new o("core_message/message_drawer_view_conversation:addContactRequests");R(s);return i.createContactRequest(h.loggedInUserId,e).then(function(e){if(!e.request){throw new Error(e.warnings[0].message)}return e.request}).then(function(s){var t=_.removePendingAddContactsById(h,[e]);t=_.addContactRequests(t,[s]);t=_.setLoadingConfirmAction(t,!1);return R(t)}).then(function(e){t.resolve();return e})},re=function(){var e=h.loggedInUserId,s=h.id,t=new o("core_message/message_drawer_view_conversation:setFavourite");return i.setFavouriteConversations(e,[s]).then(function(){var e=_.setIsFavourite(h,!0);return R(e)}).then(function(){return r.publish(g.CONVERSATION_SET_FAVOURITE,K(h))}).then(function(e){t.resolve();return e})},de=function(){var e=h.loggedInUserId,s=h.id,t=new o("core_message/message_drawer_view_conversation:unsetFavourite");return i.unsetFavouriteConversations(e,[s]).then(function(){var e=_.setIsFavourite(h,!1);return R(e)}).then(function(){return r.publish(g.CONVERSATION_UNSET_FAVOURITE,K(h))}).then(function(e){t.resolve();return e})},ie=function(){var e=h.loggedInUserId,s=h.id,t=new o("core_message/message_drawer_view_conversation:markConversationAsRead");return i.setMutedConversations(e,[s]).then(function(){var e=_.setIsMuted(h,!0);return R(e)}).then(function(){return r.publish(g.CONVERSATION_SET_MUTED,K(h))}).then(function(e){t.resolve();return e})},ge=function(){var e=h.loggedInUserId,s=h.id;return i.unsetMutedConversations(e,[s]).then(function(){var e=_.setIsMuted(h,!1);return R(e)}).then(function(){return r.publish(g.CONVERSATION_UNSET_MUTED,K(h))})},le=function(e){var s=h.selectedMessageIds;Ee(e);var t=_.addPendingDeleteMessagesById(h,s);R(t)},me=function(){var e=new o("core_message/message_drawer_view_conversation:deleteSelectedMessages"),t=h.pendingDeleteMessageIds,n=h.messages.filter(function(e){return 0<=t.indexOf(e.id)&&("sent"==e.sendState||null===e.sendState)}),d=_.setLoadingConfirmAction(h,!0);R(d);var l=s.Deferred().resolve().promise();if(n.length){var m=n.map(function(e){return e.id});if(d.deleteMessagesForAllUsers){l=i.deleteMessagesForAllUsers(h.loggedInUserId,m)}else{l=i.deleteMessages(h.loggedInUserId,m)}}N=!0;if(f){f.stop()}return l.then(function(){var e=_.removeMessagesById(h,t);e=_.removePendingDeleteMessagesById(e,t);e=_.removeSelectedMessagesById(e,t);e=_.setLoadingConfirmAction(e,!1);e=_.setDeleteMessagesForAllUsers(e,!1);var s=h.messages[h.messages.length-1],n=e.messages.length?e.messages[e.messages.length-1]:null;if(n&&n.id!=s.id){var a=K(e);r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,a)}else if(!e.messages.length){r.publish(g.CONVERSATION_DELETED,e.id)}N=!1;return R(e)}).then(function(s){e.resolve();return s}).catch(a.exception)},ce=function(e){Ee(e);var s=_.setPendingDeleteConversation(h,!0);R(s)},_e=function(){var e=new o("core_message/message_drawer_view_conversation:markConversationAsRead"),s=_.setLoadingConfirmAction(h,!0);R(s);N=!0;if(f){f.stop()}return i.deleteConversation(h.loggedInUserId,h.id).then(function(){var e=_.removeMessages(h,h.messages);e=_.removeSelectedMessagesById(e,h.selectedMessageIds);e=_.setPendingDeleteConversation(e,!1);e=_.setLoadingConfirmAction(e,!1);r.publish(g.CONVERSATION_DELETED,e.id);N=!1;return R(e)}).then(function(s){e.resolve();return s})},Ee=function(e){var s=h.pendingDeleteMessageIds,t=_.removePendingAddContactsById(h,[e]);t=_.removePendingRemoveContactsById(t,[e]);t=_.removePendingUnblockUsersById(t,[e]);t=_.removePendingBlockUsersById(t,[e]);t=_.removePendingDeleteMessagesById(t,s);t=_.setPendingDeleteConversation(t,!1);t=_.setDeleteMessagesForAllUsers(t,!1);R(t)},Ce=function(e){var s=new o("core_message/message_drawer_view_conversation:acceptContactRequest"),t=h.loggedInUserId,n=h.members[e].contactrequests.filter(function(e){return e.requesteduserid==t}),a=n[0],d=_.setLoadingConfirmAction(h,!0);R(d);return i.acceptContactRequest(e,t).then(function(e){var s=_.removeContactRequests(h,[a]);s=_.addMembers(h,[e]);s=_.setLoadingConfirmAction(s,!1);return R(s)}).then(function(){r.publish(g.CONTACT_ADDED,h.members[e]);r.publish(g.CONTACT_REQUEST_ACCEPTED,a)}).then(function(e){s.resolve();return e})},Ie=function(e){var s=new o("core_message/message_drawer_view_conversation:declineContactRequest"),t=h.loggedInUserId,n=h.members[e].contactrequests.filter(function(e){return e.requesteduserid==t}),a=n[0],d=_.setLoadingConfirmAction(h,!0);R(d);return i.declineContactRequest(e,t).then(function(e){var s=_.removeContactRequests(h,[a]);s=_.addMembers(h,[e]);s=_.setLoadingConfirmAction(s,!1);return R(s)}).then(function(){r.publish(g.CONTACT_REQUEST_DECLINED,a)}).then(function(e){s.resolve();return e})},ue=function(){if(S){return}if(!b.length){return}var e=new o("core_message/message_drawer_view_conversation:processSendMessageBuffer");S=!0;var t=b.slice();b=[];var n=h.id,a=null,l=t.map(function(e){return e.text}),m=t.map(function(e){return e.id}),c=null,E=null;if(!n&&h.type!=P.PUBLIC){var C=B();c=i.sendMessagesToUser(C,l).then(function(e){if(e.length){a=parseInt(e[0].conversationid,10);E=e[0].candeletemessagesforallusers}return e})}else{c=i.sendMessagesToConversation(n,l)}c.then(function(e){var s=e.map(function(e){return e.id}),n=[],o=[],d=[];t.forEach(function(s,t){var a=e[t];n.push([s,a]);if(0<=h.selectedMessageIds.indexOf(s.id)){o.push(s.id);d.push(a.id)}});var i=_.updateMessages(h,n);i=_.setMessagesSendSuccessById(i,s);if(o.length){i=_.removeSelectedMessagesById(i,o)}if(d.length){i=_.addSelectedMessagesById(i,d)}var l=K(i);if(!i.id){i=_.setId(i,a);l.id=a;Ve(a);r.publish(g.CONVERSATION_CREATED,l);i=_.setCanDeleteMessagesForAllUsers(i,E)}R(i);S=!1;ue();r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,l)}).then(function(s){e.resolve();return s}).catch(function(t){var n;if(t.message){n=s.Deferred().resolve(t.message).promise()}else{n=d.get_string("unknownerror","core")}var a=function(e){var s=_.setMessagesSendFailById(h,m,e);R(s);S=!1;ue()};n.then(a).then(function(s){e.resolve();return s}).catch(function(s){var e=s.message||"Something went wrong!";a(e)})})},ve=function(e){var s="temp"+Date.now(),t={id:s,useridfrom:h.loggedInUserId,text:e,timecreated:null},n=_.addMessages(h,[t]);R(n);b.push(t);ue()},he=function(e){var s=_.setMessagesSendPendingById(h,[e.id]);R(s);b.push(e);ue()},Te=function(e){var s=h;if(-1t){e.messages=e.messages.slice(1)}else{q(!0)}x(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=W(e,s.id);n=_.setLoadingMembers(n,!1);n=_.setLoadingMessages(n,!1);return R(n).then(function(){return e})}).then(function(){return z(e)}).catch(function(e){var s=_.setLoadingMembers(h,!1);s=_.setLoadingMessages(s,!1);R(s);a.exception(e)})},X=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 r=e.messages.length,i=r>=t,o=W(e,s.id);o=_.setLoadingMembers(o,!1);o=_.setLoadingMessages(o,!i);var g=R(o);return g.then(function(){if(!i){return Y(e.id,t,r,n,[])}else{return{messages:e.messages}}}).then(function(){var e=h.messages;x(e.length);z(h.id);return e}).catch(a.exception)},Y=function(e,s,t,n,a,d){return o.getMessages(h.loggedInUserId,e,s?s+1:s,t,n,d).then(function(e){if(e.id!=h.id){e.messages=[];if(e.id in v){delete v[e.id]}}return e}).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{q(!0)}return e}).then(function(e){var s=e.members.filter(function(e){return!(e.id in h.members)}),t=_.addMembers(h,s);t=_.addMessages(t,e.messages);t=_.setLoadingMessages(t,!1);return R(t).then(function(){return e})}).catch(function(e){var s=_.setLoadingMessages(h,!1);R(s);throw e})},H=function(e,t){return function(){var n=h.messages,a=n.length?n[n.length-1]:null,d=a?a.timeCreated:null;if(d&&!M&&!S&&!N){for(var o=[],l=n.length-1,m;0<=l;l--){m=n[l];if(m.timeCreated===d){o.push(m.id)}else{break}}return Y(e,0,0,t,o,d).then(function(s){if(s.messages.length){A.restart();var t=K(h);r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,t);return z(e)}else{return s}})}return s.Deferred().resolve().promise()}},z=function(e){var s=h.loggedInUserId,t=new d("core_message/message_drawer_view_conversation:markConversationAsRead");return o.markAllConversationMessagesAsRead(s,e).then(function(){var s=_.markMessagesAsRead(h,h.messages);r.publish(g.CONVERSATION_READ,e);return R(s)}).then(function(e){t.resolve();return e})},Z=function(e){Ee(e);var s=_.addPendingBlockUsersById(h,[e]);R(s)},$=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new d("core_message/message_drawer_view_conversation:blockUser");R(s);return o.blockUser(h.loggedInUserId,e).then(function(s){var t=_.addMembers(h,[s]);t=_.removePendingBlockUsersById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_BLOCKED,e);return R(t)}).then(function(e){t.resolve();return e})},ee=function(e){Ee(e);var s=_.addPendingUnblockUsersById(h,[e]);R(s)},se=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new d("core_message/message_drawer_view_conversation:unblockUser");R(s);return o.unblockUser(h.loggedInUserId,e).then(function(s){var t=_.addMembers(h,[s]);t=_.removePendingUnblockUsersById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_UNBLOCKED,e);return R(t)}).then(function(e){t.resolve();return e})},te=function(e){Ee(e);var s=_.addPendingRemoveContactsById(h,[e]);R(s)},ne=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new d("core_message/message_drawer_view_conversation:removeContact");R(s);return o.deleteContacts(h.loggedInUserId,[e]).then(function(s){var t=_.addMembers(h,s);t=_.removePendingRemoveContactsById(t,[e]);t=_.setLoadingConfirmAction(t,!1);r.publish(g.CONTACT_REMOVED,e);return R(t)}).then(function(e){t.resolve();return e})},ae=function(e){Ee(e);var s=_.addPendingAddContactsById(h,[e]);R(s)},de=function(e){var s=_.setLoadingConfirmAction(h,!0),t=new d("core_message/message_drawer_view_conversation:addContactRequests");R(s);return o.createContactRequest(h.loggedInUserId,e).then(function(e){if(!e.request){throw new Error(e.warnings[0].message)}return e.request}).then(function(s){var t=_.removePendingAddContactsById(h,[e]);t=_.addContactRequests(t,[s]);t=_.setLoadingConfirmAction(t,!1);return R(t)}).then(function(e){t.resolve();return e})},re=function(){var e=h.loggedInUserId,s=h.id,t=new d("core_message/message_drawer_view_conversation:setFavourite");return o.setFavouriteConversations(e,[s]).then(function(){var e=_.setIsFavourite(h,!0);return R(e)}).then(function(){return r.publish(g.CONVERSATION_SET_FAVOURITE,K(h))}).then(function(e){t.resolve();return e})},ie=function(){var e=h.loggedInUserId,s=h.id,t=new d("core_message/message_drawer_view_conversation:unsetFavourite");return o.unsetFavouriteConversations(e,[s]).then(function(){var e=_.setIsFavourite(h,!1);return R(e)}).then(function(){return r.publish(g.CONVERSATION_UNSET_FAVOURITE,K(h))}).then(function(e){t.resolve();return e})},oe=function(){var e=h.loggedInUserId,s=h.id,t=new d("core_message/message_drawer_view_conversation:markConversationAsRead");return o.setMutedConversations(e,[s]).then(function(){var e=_.setIsMuted(h,!0);return R(e)}).then(function(){return r.publish(g.CONVERSATION_SET_MUTED,K(h))}).then(function(e){t.resolve();return e})},ge=function(){var e=h.loggedInUserId,s=h.id;return o.unsetMutedConversations(e,[s]).then(function(){var e=_.setIsMuted(h,!1);return R(e)}).then(function(){return r.publish(g.CONVERSATION_UNSET_MUTED,K(h))})},le=function(e){var s=h.selectedMessageIds;Ee(e);var t=_.addPendingDeleteMessagesById(h,s);R(t)},me=function(){var e=new d("core_message/message_drawer_view_conversation:deleteSelectedMessages"),t=h.pendingDeleteMessageIds,n=h.messages.filter(function(e){return 0<=t.indexOf(e.id)&&("sent"==e.sendState||null===e.sendState)}),i=_.setLoadingConfirmAction(h,!0);R(i);var l=s.Deferred().resolve().promise();if(n.length){var m=n.map(function(e){return e.id});if(i.deleteMessagesForAllUsers){l=o.deleteMessagesForAllUsers(h.loggedInUserId,m)}else{l=o.deleteMessages(h.loggedInUserId,m)}}N=!0;if(A){A.stop()}return l.then(function(){var e=_.removeMessagesById(h,t);e=_.removePendingDeleteMessagesById(e,t);e=_.removeSelectedMessagesById(e,t);e=_.setLoadingConfirmAction(e,!1);e=_.setDeleteMessagesForAllUsers(e,!1);var s=h.messages[h.messages.length-1],n=e.messages.length?e.messages[e.messages.length-1]:null;if(n&&n.id!=s.id){var a=K(e);r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,a)}else if(!e.messages.length){r.publish(g.CONVERSATION_DELETED,e.id)}N=!1;return R(e)}).then(function(s){e.resolve();return s}).catch(a.exception)},ce=function(e){Ee(e);var s=_.setPendingDeleteConversation(h,!0);R(s)},_e=function(){var e=new d("core_message/message_drawer_view_conversation:markConversationAsRead"),s=_.setLoadingConfirmAction(h,!0);R(s);N=!0;if(A){A.stop()}return o.deleteConversation(h.loggedInUserId,h.id).then(function(){var e=_.removeMessages(h,h.messages);e=_.removeSelectedMessagesById(e,h.selectedMessageIds);e=_.setPendingDeleteConversation(e,!1);e=_.setLoadingConfirmAction(e,!1);r.publish(g.CONVERSATION_DELETED,e.id);N=!1;return R(e)}).then(function(s){e.resolve();return s})},Ee=function(e){var s=h.pendingDeleteMessageIds,t=_.removePendingAddContactsById(h,[e]);t=_.removePendingRemoveContactsById(t,[e]);t=_.removePendingUnblockUsersById(t,[e]);t=_.removePendingBlockUsersById(t,[e]);t=_.removePendingDeleteMessagesById(t,s);t=_.setPendingDeleteConversation(t,!1);t=_.setDeleteMessagesForAllUsers(t,!1);R(t)},ue=function(e){var s=new d("core_message/message_drawer_view_conversation:acceptContactRequest"),t=h.loggedInUserId,n=h.members[e].contactrequests.filter(function(e){return e.requesteduserid==t}),a=n[0],i=_.setLoadingConfirmAction(h,!0);R(i);return o.acceptContactRequest(e,t).then(function(e){var s=_.removeContactRequests(h,[a]);s=_.addMembers(h,[e]);s=_.setLoadingConfirmAction(s,!1);return R(s)}).then(function(){r.publish(g.CONTACT_ADDED,h.members[e]);r.publish(g.CONTACT_REQUEST_ACCEPTED,a)}).then(function(e){s.resolve();return e})},Ce=function(e){var s=new d("core_message/message_drawer_view_conversation:declineContactRequest"),t=h.loggedInUserId,n=h.members[e].contactrequests.filter(function(e){return e.requesteduserid==t}),a=n[0],i=_.setLoadingConfirmAction(h,!0);R(i);return o.declineContactRequest(e,t).then(function(e){var s=_.removeContactRequests(h,[a]);s=_.addMembers(h,[e]);s=_.setLoadingConfirmAction(s,!1);return R(s)}).then(function(){r.publish(g.CONTACT_REQUEST_DECLINED,a)}).then(function(e){s.resolve();return e})},Ie=function(){if(S){return}if(!b.length){return}var e=new d("core_message/message_drawer_view_conversation:processSendMessageBuffer");S=!0;var t=b.slice();b=[];var n=h.id,a=null,l=t.map(function(e){return e.text}),m=t.map(function(e){return e.id}),c=null,E=null;if(!n&&h.type!=P.PUBLIC){var u=B();c=o.sendMessagesToUser(u,l).then(function(e){if(e.length){a=parseInt(e[0].conversationid,10);E=e[0].candeletemessagesforallusers}return e})}else{c=o.sendMessagesToConversation(n,l)}c.then(function(e){var s=e.map(function(e){return e.id}),n=[],d=[],i=[];t.forEach(function(s,t){var a=e[t];n.push([s,a]);if(0<=h.selectedMessageIds.indexOf(s.id)){d.push(s.id);i.push(a.id)}});var o=_.updateMessages(h,n);o=_.setMessagesSendSuccessById(o,s);if(d.length){o=_.removeSelectedMessagesById(o,d)}if(i.length){o=_.addSelectedMessagesById(o,i)}var l=K(o);if(!o.id){o=_.setId(o,a);l.id=a;xe(a);r.publish(g.CONVERSATION_CREATED,l);o=_.setCanDeleteMessagesForAllUsers(o,E)}R(o);S=!1;Ie();r.publish(g.CONVERSATION_NEW_LAST_MESSAGE,l)}).then(function(s){e.resolve();return s}).catch(function(t){var n;if(t.message){n=s.Deferred().resolve(t.message).promise()}else{n=i.get_string("unknownerror","core")}var a=function(e){var s=_.setMessagesSendFailById(h,m,e);R(s);S=!1;Ie()};n.then(a).then(function(s){e.resolve();return s}).catch(function(s){var e=s.message||"Something went wrong!";a(e)})})},ve=function(e){var s=e.replace(//gi,"");s=s.replace(//gi,"");s=s.replace(/<\/div>/ig,"\n");s=s.replace(/<\/li>/ig,"\n");s=s.replace(/
  • /ig," * ");s=s.replace(/<\/ul>/ig,"\n");s=s.replace(/<\/p>/ig,"\n");s=s.replace(/]*>/gi,"\n");s=s.replace(/<[^>]+>/ig,"");s=s.replace(/\n+/ig,"\n");return s.replace(/\n/ig,"
    ")},he=function(e){var s="temp"+Date.now(),t={id:s,useridfrom:h.loggedInUserId,text:ve(e),timecreated:null},n=_.addMessages(h,[t]);R(n);var a={id:s,useridfrom:h.loggedInUserId,text:e,timecreated:null};b.push(a);Ie()},fe=function(e){var s=_.setMessagesSendPendingById(h,[e.id]);R(s);b.push(e);Ie()},Te=function(e){var s=h;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/pending',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_view_conversation_constants',\n 'core_message/message_drawer_view_conversation_patcher',\n 'core_message/message_drawer_view_conversation_renderer',\n 'core_message/message_drawer_view_conversation_state_manager',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core/emoji/auto_complete',\n 'core/emoji/picker'\n],\nfunction(\n $,\n AutoRows,\n BackOffTimer,\n CustomEvents,\n Notification,\n Pending,\n PubSub,\n Str,\n Repository,\n MessageDrawerEvents,\n Constants,\n Patcher,\n Renderer,\n StateManager,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n initialiseEmojiAutoComplete,\n initialiseEmojiPicker\n) {\n\n // Contains a cache of all view states that have been loaded so far\n // which saves us having to reload stuff with network requests when\n // switching between conversations.\n var stateCache = {};\n // The current data representation of the view.\n var viewState = null;\n var loadedAllMessages = false;\n var messagesOffset = 0;\n var newMessagesPollTimer = null;\n var isRendering = false;\n var renderBuffer = [];\n // If the UI is currently resetting.\n var isResetting = true;\n // If the UI is currently sending a message.\n var isSendingMessage = false;\n // If the UI is currently deleting a conversation.\n var isDeletingConversationContent = false;\n // A buffer of messages to send.\n var sendMessageBuffer = [];\n // These functions which will be generated when this module is\n // first called. See generateRenderFunction for details.\n var render = null;\n // The list of renderers that have been registered to render\n // this conversation. See generateRenderFunction for details.\n var renderers = [];\n\n var NEWEST_FIRST = Constants.NEWEST_MESSAGES_FIRST;\n var LOAD_MESSAGE_LIMIT = Constants.LOAD_MESSAGE_LIMIT;\n var MILLISECONDS_IN_SEC = Constants.MILLISECONDS_IN_SEC;\n var SELECTORS = Constants.SELECTORS;\n var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;\n\n /**\n * Get the other user userid.\n *\n * @return {Number} Userid.\n */\n var getOtherUserId = function() {\n if (!viewState || viewState.type == CONVERSATION_TYPES.PUBLIC) {\n return null;\n }\n\n var loggedInUserId = viewState.loggedInUserId;\n if (viewState.type == CONVERSATION_TYPES.SELF) {\n // It's a self-conversation, so the other user is the one logged in.\n return loggedInUserId;\n }\n\n var otherUserIds = Object.keys(viewState.members).filter(function(userId) {\n return loggedInUserId != userId;\n });\n\n return otherUserIds.length ? otherUserIds[0] : null;\n };\n\n /**\n * Search the cache to see if we've already loaded a private conversation\n * with the given user id.\n *\n * @param {Number} userId The id of the other user.\n * @return {Number|null} Conversation id.\n */\n var getCachedPrivateConversationIdFromUserId = function(userId) {\n return Object.keys(stateCache).reduce(function(carry, id) {\n if (!carry) {\n var state = stateCache[id].state;\n\n if (state.type != CONVERSATION_TYPES.PUBLIC) {\n if (userId in state.members) {\n // We've found a cached conversation for this user!\n carry = state.id;\n }\n }\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Get profile info for logged in user.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object}\n */\n var getLoggedInUserProfile = function(body) {\n return {\n id: parseInt(body.attr('data-user-id'), 10),\n fullname: null,\n profileimageurl: null,\n profileimageurlsmall: null,\n isonline: null,\n showonlinestatus: null,\n isblocked: null,\n iscontact: null,\n isdeleted: null,\n canmessage: null,\n canmessageevenifblocked: null,\n requirescontact: null,\n contactrequests: []\n };\n };\n\n /**\n * Get the messages offset value to load more messages.\n *\n * @return {Number}\n */\n var getMessagesOffset = function() {\n return messagesOffset;\n };\n\n /**\n * Set the messages offset value for loading more messages.\n *\n * @param {Number} value The offset value\n */\n var setMessagesOffset = function(value) {\n messagesOffset = value;\n stateCache[viewState.id].messagesOffset = value;\n };\n\n /**\n * Check if all messages have been loaded.\n *\n * @return {Bool}\n */\n var hasLoadedAllMessages = function() {\n return loadedAllMessages;\n };\n\n /**\n * Set whether all messages have been loaded or not.\n *\n * @param {Bool} value If all messages have been loaded.\n */\n var setLoadedAllMessages = function(value) {\n loadedAllMessages = value;\n stateCache[viewState.id].loadedAllMessages = value;\n };\n\n /**\n * Get the messages container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getMessagesContainer = function(body) {\n return body.find(SELECTORS.MESSAGES_CONTAINER);\n };\n\n /**\n * Reformat the conversation for an event payload.\n *\n * @param {Object} state The view state.\n * @return {Object} New formatted conversation.\n */\n var formatConversationForEvent = function(state) {\n return {\n id: state.id,\n name: state.name,\n subname: state.subname,\n imageUrl: state.imageUrl,\n isFavourite: state.isFavourite,\n isMuted: state.isMuted,\n type: state.type,\n totalMemberCount: state.totalMemberCount,\n loggedInUserId: state.loggedInUserId,\n messages: state.messages.map(function(message) {\n return $.extend({}, message);\n }),\n members: Object.keys(state.members).map(function(id) {\n var formattedMember = $.extend({}, state.members[id]);\n formattedMember.contactrequests = state.members[id].contactrequests.map(function(request) {\n return $.extend({}, request);\n });\n return formattedMember;\n })\n };\n };\n\n /**\n * Load up an empty private conversation between the logged in user and the\n * other user. Sets all of the conversation details based on the other user.\n *\n * A conversation isn't created until the user sends the first message.\n *\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} otherUserId The other user id.\n * @return {Object} Profile returned from repository.\n */\n var loadEmptyPrivateConversation = function(loggedInUserProfile, otherUserId) {\n var loggedInUserId = loggedInUserProfile.id;\n // If the other user id is the same as the logged in user then this is a self\n // conversation.\n var conversationType = loggedInUserId == otherUserId ? CONVERSATION_TYPES.SELF : CONVERSATION_TYPES.PRIVATE;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true)\n .then(function(profiles) {\n if (profiles.length) {\n return profiles[0];\n } else {\n throw new Error('Unable to load other user profile');\n }\n })\n .then(function(profile) {\n // If the conversation is a self conversation then the profile loaded is the\n // logged in user so only add that to the members array.\n var members = conversationType == CONVERSATION_TYPES.SELF ? [profile] : [profile, loggedInUserProfile];\n var newState = StateManager.addMembers(viewState, members);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setName(newState, profile.fullname);\n newState = StateManager.setType(newState, conversationType);\n newState = StateManager.setImageUrl(newState, profile.profileimageurl);\n newState = StateManager.setTotalMemberCount(newState, members.length);\n render(newState);\n return profile;\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a new state from a conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Number} loggedInUserId The logged in user id.\n * @return {Object} new state.\n */\n var updateStateFromConversation = function(conversation, loggedInUserId) {\n var otherUser = null;\n if (conversation.type == CONVERSATION_TYPES.PRIVATE) {\n // For private conversations, remove current logged in user from the members list to get the other user.\n var otherUsers = conversation.members.filter(function(member) {\n return member.id != loggedInUserId;\n });\n otherUser = otherUsers.length ? otherUsers[0] : null;\n } else if (conversation.type == CONVERSATION_TYPES.SELF) {\n // Self-conversations have only one member.\n otherUser = conversation.members[0];\n }\n\n var name = conversation.name;\n var imageUrl = conversation.imageurl;\n\n if (conversation.type != CONVERSATION_TYPES.PUBLIC) {\n name = name || otherUser ? otherUser.fullname : '';\n imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : '';\n }\n\n var newState = StateManager.addMembers(viewState, conversation.members);\n newState = StateManager.setName(newState, name);\n newState = StateManager.setSubname(newState, conversation.subname);\n newState = StateManager.setType(newState, conversation.type);\n newState = StateManager.setImageUrl(newState, imageUrl);\n newState = StateManager.setTotalMemberCount(newState, conversation.membercount);\n newState = StateManager.setIsFavourite(newState, conversation.isfavourite);\n newState = StateManager.setIsMuted(newState, conversation.ismuted);\n newState = StateManager.addMessages(newState, conversation.messages);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, conversation.candeletemessagesforallusers);\n return newState;\n };\n\n /**\n * Get the details for a conversation from the conversation id.\n *\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Number} messageOffset The number of messages to skip.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadNewConversation = function(\n conversationId,\n loggedInUserProfile,\n messageLimit,\n messageOffset,\n newestFirst\n ) {\n var loggedInUserId = loggedInUserProfile.id;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getConversation(\n loggedInUserId,\n conversationId,\n true,\n true,\n 0,\n 0,\n messageLimit + 1,\n messageOffset,\n newestFirst\n )\n .then(function(conversation) {\n if (conversation.messages.length > messageLimit) {\n conversation.messages = conversation.messages.slice(1);\n } else {\n setLoadedAllMessages(true);\n }\n\n setMessagesOffset(messageOffset + messageLimit);\n\n return conversation;\n })\n .then(function(conversation) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return conversation;\n });\n })\n .then(function() {\n return markConversationAsRead(conversationId);\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Get the details for a conversation from and existing conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadExistingConversation = function(\n conversation,\n loggedInUserProfile,\n messageLimit,\n newestFirst\n ) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var messageCount = conversation.messages.length;\n var hasLoadedEnoughMessages = messageCount >= messageLimit;\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, !hasLoadedEnoughMessages);\n var renderPromise = render(newState);\n\n return renderPromise.then(function() {\n if (!hasLoadedEnoughMessages) {\n // We haven't got enough messages so let's load some more.\n return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []);\n } else {\n // We've got enough messages. No need to load any more for now.\n return {messages: conversation.messages};\n }\n })\n .then(function() {\n var messages = viewState.messages;\n // Update the offset to reflect the number of messages we've loaded.\n setMessagesOffset(messages.length);\n markConversationAsRead(viewState.id);\n\n return messages;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Load messages for this conversation and pass them to the renderer.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Number} limit Number of messages to load.\n * @param {Number} offset Get messages from offset.\n * @param {Bool} newestFirst Get newest messages first.\n * @param {Array} ignoreList Ignore any messages with ids in this list.\n * @param {Number|null} timeFrom Only get messages from this time onwards.\n * @return {Promise} renderer promise.\n */\n var loadMessages = function(conversationId, limit, offset, newestFirst, ignoreList, timeFrom) {\n return Repository.getMessages(\n viewState.loggedInUserId,\n conversationId,\n limit ? limit + 1 : limit,\n offset,\n newestFirst,\n timeFrom\n )\n .then(function(result) {\n // Prevent older requests from contaminating the current view.\n if (result.id != viewState.id) {\n result.messages = [];\n // Purge old conversation cache to prevent messages lose.\n if (result.id in stateCache) {\n delete stateCache[result.id];\n }\n }\n\n return result;\n })\n .then(function(result) {\n if (result.messages.length && ignoreList.length) {\n result.messages = result.messages.filter(function(message) {\n // Skip any messages in our ignore list.\n return ignoreList.indexOf(parseInt(message.id, 10)) < 0;\n });\n }\n\n return result;\n })\n .then(function(result) {\n if (!limit) {\n return result;\n } else if (result.messages.length > limit) {\n // Ignore the last result which was just to test if there are more\n // to load.\n result.messages = result.messages.slice(0, -1);\n } else {\n setLoadedAllMessages(true);\n }\n\n return result;\n })\n .then(function(result) {\n var membersToAdd = result.members.filter(function(member) {\n return !(member.id in viewState.members);\n });\n var newState = StateManager.addMembers(viewState, membersToAdd);\n newState = StateManager.addMessages(newState, result.messages);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return result;\n });\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMessages(viewState, false);\n render(newState);\n // Re-throw the error for other error handlers.\n throw error;\n });\n };\n\n /**\n * Create a callback function for getting new messages for this conversation.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Bool} newestFirst Show newest messages first\n * @return {Function} Callback function that returns a renderer promise.\n */\n var getLoadNewMessagesCallback = function(conversationId, newestFirst) {\n return function() {\n var messages = viewState.messages;\n var mostRecentMessage = messages.length ? messages[messages.length - 1] : null;\n var lastTimeCreated = mostRecentMessage ? mostRecentMessage.timeCreated : null;\n\n if (lastTimeCreated && !isResetting && !isSendingMessage && !isDeletingConversationContent) {\n // There may be multiple messages with the same time created value since\n // the accuracy is only down to the second. The server will include these\n // messages in the result (since it does a >= comparison on time from) so\n // we need to filter them back out of the result so that we're left only\n // with the new messages.\n var ignoreMessageIds = [];\n for (var i = messages.length - 1; i >= 0; i--) {\n var message = messages[i];\n if (message.timeCreated === lastTimeCreated) {\n ignoreMessageIds.push(message.id);\n } else {\n // Since the messages are ordered in ascending order of time created\n // we can break as soon as we hit a message with a different time created\n // because we know all other messages will have lower values.\n break;\n }\n }\n\n return loadMessages(\n conversationId,\n 0,\n 0,\n newestFirst,\n ignoreMessageIds,\n lastTimeCreated\n )\n .then(function(result) {\n if (result.messages.length) {\n // If we found some results then restart the polling timer\n // because the other user might be sending messages.\n newMessagesPollTimer.restart();\n // We've also got a new last message so publish that for other\n // components to update.\n var conversation = formatConversationForEvent(viewState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return markConversationAsRead(conversationId);\n } else {\n return result;\n }\n });\n }\n\n return $.Deferred().resolve().promise();\n };\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Number} conversationId The conversation id.\n * @return {Promise} The renderer promise.\n */\n var markConversationAsRead = function(conversationId) {\n var loggedInUserId = viewState.loggedInUserId;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.markAllConversationMessagesAsRead(loggedInUserId, conversationId)\n .then(function() {\n var newState = StateManager.markMessagesAsRead(viewState, viewState.messages);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_READ, conversationId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is request to block a user and run the renderer\n * to show the block user dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestBlockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingBlockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to block a user, update the statemanager and publish\n * a contact has been blocked.\n *\n * @param {Number} userId User id of user to block.\n * @return {Promise} Renderer promise.\n */\n var blockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:blockUser');\n\n render(newState);\n\n return Repository.blockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_BLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to unblock a user and run the renderer\n * to show the unblock user dialogue.\n *\n * @param {Number} userId User id of user to unblock.\n */\n var requestUnblockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to unblock a user, update the statemanager and publish\n * a contact has been unblocked.\n *\n * @param {Number} userId User id of user to unblock.\n * @return {Promise} Renderer promise.\n */\n var unblockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unblockUser');\n render(newState);\n\n return Repository.unblockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_UNBLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to remove a user from the contact list\n * and run the renderer to show the remove user from contacts dialogue.\n *\n * @param {Number} userId User id of user to remove from contacts.\n */\n var requestRemoveContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to remove a user from the contacts list. update the statemanager\n * and publish a contact has been removed.\n *\n * @param {Number} userId User id of user to remove from contacts.\n * @return {Promise} Renderer promise.\n */\n var removeContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:removeContact');\n render(newState);\n\n return Repository.deleteContacts(viewState.loggedInUserId, [userId])\n .then(function(profiles) {\n var newState = StateManager.addMembers(viewState, profiles);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_REMOVED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to add a user to the contact list\n * and run the renderer to show the add user to contacts dialogue.\n *\n * @param {Number} userId User id of user to add to contacts.\n */\n var requestAddContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingAddContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to add a user to the contacts list. update the statemanager\n * and publish a contact has been added.\n *\n * @param {Number} userId User id of user to add to contacts.\n * @return {Promise} Renderer promise.\n */\n var addContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:addContactRequests');\n render(newState);\n\n return Repository.createContactRequest(viewState.loggedInUserId, userId)\n .then(function(response) {\n if (!response.request) {\n throw new Error(response.warnings[0].message);\n }\n\n return response.request;\n })\n .then(function(request) {\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.addContactRequests(newState, [request]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:setFavourite');\n\n return Repository.setFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unsetFavourite');\n\n return Repository.unsetFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.setMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_MUTED,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n\n return Repository.unsetMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_MUTED,\n formatConversationForEvent(viewState)\n );\n });\n };\n\n /**\n * Tell the statemanager there is a request to delete the selected messages\n * and run the renderer to show confirm delete messages dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestDeleteSelectedMessages = function(userId) {\n var selectedMessageIds = viewState.selectedMessageIds;\n cancelRequest(userId);\n var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete the messages pending deletion. Update the statemanager\n * and publish a message deletion event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteSelectedMessages = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:deleteSelectedMessages');\n var messageIds = viewState.pendingDeleteMessageIds;\n var sentMessages = viewState.messages.filter(function(message) {\n // If a message sendState is null then it means it was loaded from the server or if it's\n // set to sent then it means the user has successfully sent it in this page load.\n return messageIds.indexOf(message.id) >= 0 && (message.sendState == 'sent' || message.sendState === null);\n });\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n\n render(newState);\n\n var deleteMessagesPromise = $.Deferred().resolve().promise();\n\n\n if (sentMessages.length) {\n // We only need to send a request to the server if we're trying to delete messages that\n // have successfully been sent.\n var sentMessageIds = sentMessages.map(function(message) {\n return message.id;\n });\n if (newState.deleteMessagesForAllUsers) {\n deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, sentMessageIds);\n } else {\n deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, sentMessageIds);\n }\n }\n\n // Mark that we are deleting content from the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return deleteMessagesPromise.then(function() {\n var newState = StateManager.removeMessagesById(viewState, messageIds);\n newState = StateManager.removePendingDeleteMessagesById(newState, messageIds);\n newState = StateManager.removeSelectedMessagesById(newState, messageIds);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n\n var prevLastMessage = viewState.messages[viewState.messages.length - 1];\n var newLastMessage = newState.messages.length ? newState.messages[newState.messages.length - 1] : null;\n\n if (newLastMessage && newLastMessage.id != prevLastMessage.id) {\n var conversation = formatConversationForEvent(newState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n } else if (!newState.messages.length) {\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n }\n\n isDeletingConversationContent = false;\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Tell the statemanager there is a request to delete a conversation\n * and run the renderer to show confirm delete conversation dialogue.\n *\n * @param {Number} userId User id of other user.\n */\n var requestDeleteConversation = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.setPendingDeleteConversation(viewState, true);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete a conversation. Update the statemanager\n * and publish a conversation deleted event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteConversation = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n // Mark that we are deleting the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return Repository.deleteConversation(viewState.loggedInUserId, viewState.id)\n .then(function() {\n var newState = StateManager.removeMessages(viewState, viewState.messages);\n newState = StateManager.removeSelectedMessagesById(newState, viewState.selectedMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n\n isDeletingConversationContent = false;\n\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager to cancel all pending actions.\n *\n * @param {Number} userId User id.\n */\n var cancelRequest = function(userId) {\n var pendingDeleteMessageIds = viewState.pendingDeleteMessageIds;\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.removePendingDeleteMessagesById(newState, pendingDeleteMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n render(newState);\n };\n\n /**\n * Accept the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var acceptContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:acceptContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.acceptContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_ADDED, viewState.members[userId]);\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Decline the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var declineContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:declineContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.declineContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Send all of the messages in the buffer to the server to be created. Update the\n * UI with the newly created message information.\n *\n * This function will recursively call itself in order to make sure the buffer is\n * always being processed.\n */\n var processSendMessageBuffer = function() {\n if (isSendingMessage) {\n // We're already sending messages so nothing to do.\n return;\n }\n if (!sendMessageBuffer.length) {\n // No messages waiting to send. Nothing to do.\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:processSendMessageBuffer');\n\n // Flag that we're processing the queue.\n isSendingMessage = true;\n // Grab all of the messages in the buffer.\n var messagesToSend = sendMessageBuffer.slice();\n // Empty the buffer since we're processing it.\n sendMessageBuffer = [];\n var conversationId = viewState.id;\n var newConversationId = null;\n var messagesText = messagesToSend.map(function(message) {\n return message.text;\n });\n var messageIds = messagesToSend.map(function(message) {\n return message.id;\n });\n var sendMessagePromise = null;\n var newCanDeleteMessagesForAllUsers = null;\n if (!conversationId && (viewState.type != CONVERSATION_TYPES.PUBLIC)) {\n // If it's a new private conversation then we need to use the old\n // web service function to create the conversation.\n var otherUserId = getOtherUserId();\n sendMessagePromise = Repository.sendMessagesToUser(otherUserId, messagesText)\n .then(function(messages) {\n if (messages.length) {\n newConversationId = parseInt(messages[0].conversationid, 10);\n newCanDeleteMessagesForAllUsers = messages[0].candeletemessagesforallusers;\n }\n return messages;\n });\n } else {\n sendMessagePromise = Repository.sendMessagesToConversation(conversationId, messagesText);\n }\n\n sendMessagePromise\n .then(function(messages) {\n var newMessageIds = messages.map(function(message) {\n return message.id;\n });\n var data = [];\n var selectedToRemove = [];\n var selectedToAdd = [];\n\n messagesToSend.forEach(function(oldMessage, index) {\n var newMessage = messages[index];\n // Update messages expects and array of arrays where the first value\n // is the old message to update and the second value is the new values\n // to set.\n data.push([oldMessage, newMessage]);\n\n if (viewState.selectedMessageIds.indexOf(oldMessage.id) >= 0) {\n // If the message was added to the \"selected messages\" list while it was still\n // being sent then we should update it's id in that list now to make sure future\n // actions work.\n selectedToRemove.push(oldMessage.id);\n selectedToAdd.push(newMessage.id);\n }\n });\n var newState = StateManager.updateMessages(viewState, data);\n newState = StateManager.setMessagesSendSuccessById(newState, newMessageIds);\n\n if (selectedToRemove.length) {\n newState = StateManager.removeSelectedMessagesById(newState, selectedToRemove);\n }\n\n if (selectedToAdd.length) {\n newState = StateManager.addSelectedMessagesById(newState, selectedToAdd);\n }\n\n var conversation = formatConversationForEvent(newState);\n\n if (!newState.id) {\n // If this message created the conversation then save the conversation\n // id.\n newState = StateManager.setId(newState, newConversationId);\n conversation.id = newConversationId;\n resetMessagePollTimer(newConversationId);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED, conversation);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, newCanDeleteMessagesForAllUsers);\n }\n\n // Update the UI with the new message values from the server.\n render(newState);\n // Recurse just in case there has been more messages added to the buffer.\n isSendingMessage = false;\n processSendMessageBuffer();\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n var errorMessage;\n if (e.message) {\n errorMessage = $.Deferred().resolve(e.message).promise();\n } else {\n errorMessage = Str.get_string('unknownerror', 'core');\n }\n\n var handleFailedMessages = function(errorMessage) {\n // We failed to create messages so remove the old messages from the pending queue\n // and update the UI to indicate that the message failed.\n var newState = StateManager.setMessagesSendFailById(viewState, messageIds, errorMessage);\n render(newState);\n isSendingMessage = false;\n processSendMessageBuffer();\n };\n\n errorMessage.then(handleFailedMessages)\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n // Hrmm, we can't even load the error messages string! We'll have to\n // hard code something in English here if we still haven't got a message\n // to show.\n var finalError = e.message || 'Something went wrong!';\n handleFailedMessages(finalError);\n });\n });\n };\n\n /**\n * 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 var cancelEditMode = function() {\n cancelRequest(getOtherUserId());\n var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds);\n render(newState);\n };\n\n /**\n * Process the patches in the render buffer one at a time in order until the\n * buffer is empty.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n */\n var processRenderBuffer = function(header, body, footer) {\n if (isRendering) {\n return;\n }\n\n if (!renderBuffer.length) {\n return;\n }\n\n isRendering = true;\n var renderable = renderBuffer.shift();\n var renderPromises = renderers.map(function(renderFunc) {\n return renderFunc(renderable.patch);\n });\n\n $.when.apply(null, renderPromises)\n .then(function() {\n isRendering = false;\n renderable.deferred.resolve(true);\n // Keep processing the buffer until it's empty.\n processRenderBuffer(header, body, footer);\n\n return;\n })\n .catch(function(error) {\n isRendering = false;\n renderable.deferred.reject(error);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a function to render the Conversation.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n * @param {Bool} isNewConversation Has someone else already initialised a conversation?\n * @return {Promise} Renderer promise.\n */\n var generateRenderFunction = function(header, body, footer, isNewConversation) {\n var rendererFunc = function(patch) {\n return Renderer.render(header, body, footer, patch);\n };\n\n if (!isNewConversation) {\n // Looks like someone got here before us! We'd better update our\n // UI to make sure it matches.\n var initialState = StateManager.buildInitialState(viewState.midnight, viewState.loggedInUserId, viewState.id);\n var syncPatch = Patcher.buildPatch(initialState, viewState);\n rendererFunc(syncPatch);\n }\n\n renderers.push(rendererFunc);\n\n return function(newState) {\n var patch = Patcher.buildPatch(viewState, newState);\n var deferred = $.Deferred();\n\n // Check if the patch has any data. Ignore empty patches.\n if (Object.keys(patch).length) {\n // Add the patch to the render buffer which gets processed in order.\n renderBuffer.push({\n patch: patch,\n deferred: deferred\n });\n } else {\n deferred.resolve(true);\n }\n // This is a great place to add in some console logging if you need\n // to debug something. You can log the current state, the next state,\n // and the generated patch and see exactly what will be updated.\n\n // Optimistically update the state. We're going to assume that the rendering\n // will always succeed. The rendering is asynchronous (annoyingly) so it's buffered\n // but it'll reach eventual consistency with the current state.\n viewState = newState;\n if (newState.id) {\n // Only cache created conversations.\n stateCache[newState.id] = {\n state: newState,\n messagesOffset: getMessagesOffset(),\n loadedAllMessages: hasLoadedAllMessages()\n };\n }\n\n // Start processing the buffer.\n processRenderBuffer(header, body, footer);\n\n return deferred.promise();\n };\n };\n\n /**\n * Create a confirm action function.\n *\n * @param {Function} actionCallback The callback function.\n * @return {Function} Confirm action handler.\n */\n var generateConfirmActionHandler = function(actionCallback) {\n return function(e, data) {\n if (!viewState.loadingConfirmAction) {\n actionCallback(getOtherUserId());\n var newState = StateManager.setLoadingConfirmAction(viewState, false);\n render(newState);\n }\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Send message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSendMessage = function(e, data) {\n var target = $(e.target);\n var footerContainer = target.closest(SELECTORS.FOOTER_CONTAINER);\n var textArea = footerContainer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var text = textArea.val().trim();\n\n if (text !== '') {\n sendMessage(text);\n textArea.val('');\n textArea.focus();\n }\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Select message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSelectMessage = function(e, data) {\n var selection = window.getSelection();\n var target = $(e.target);\n\n if (selection.toString() != '') {\n // Bail if we're selecting.\n return;\n }\n\n if (target.is('a')) {\n // Clicking on a link in the message so ignore it.\n return;\n }\n\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n\n toggleSelectMessage(messageId);\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle retry sending of message.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleRetrySendMessage = function(e, data) {\n var target = $(e.target);\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n var messages = viewState.messages.filter(function(message) {\n return message.id == messageId;\n });\n var message = messages.length ? messages[0] : null;\n\n if (message) {\n retrySendMessage(message);\n }\n\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n e.stopPropagation();\n };\n\n /**\n * Cancel edit mode event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleCancelEditMode = function(e, data) {\n cancelEditMode();\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View contact handler.\n */\n var generateHandleViewContact = function(namespace) {\n return function(e, data) {\n var otherUserId = getOtherUserId();\n var otherUser = viewState.members[otherUserId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONTACT, otherUser);\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Set this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetFavourite = function(e, data) {\n setFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetFavourite = function(e, data) {\n unsetFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view group info page.\n * Set this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetMuted = function(e, data) {\n setMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetMuted = function(e, data) {\n unsetMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking on the checkbox that toggles deleting messages for\n * all users.\n *\n * @param {Object} e Element this event handler is called on.\n */\n var handleDeleteMessagesForAllUsersToggle = function(e) {\n var newValue = $(e.target).prop('checked');\n var newState = StateManager.setDeleteMessagesForAllUsers(viewState, newValue);\n render(newState);\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View group info handler.\n */\n var generateHandleViewGroupInfo = function(namespace) {\n return function(e, data) {\n MessageDrawerRouter.go(\n namespace,\n MessageDrawerRoutes.VIEW_GROUP_INFO,\n {\n id: viewState.id,\n name: viewState.name,\n subname: viewState.subname,\n imageUrl: viewState.imageUrl,\n totalMemberCount: viewState.totalMemberCount\n },\n viewState.loggedInUserId\n );\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Handle clicking on the emoji toggle button.\n *\n * @param {Object} e The event\n * @param {Object} data The custom interaction event data\n */\n var handleToggleEmojiPicker = function(e, data) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking outside the emoji picker to close it.\n *\n * @param {Object} e The event\n */\n var handleCloseEmojiPicker = function(e) {\n var target = $(e.target);\n\n if (\n viewState.showEmojiPicker &&\n !target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length &&\n !target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length\n ) {\n var newState = StateManager.setShowEmojiPicker(viewState, false);\n render(newState);\n }\n };\n\n /**\n * Listen to, and handle events for conversations.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n */\n var registerEventListeners = function(namespace, header, body, footer) {\n var isLoadingMoreMessages = false;\n var messagesContainer = getMessagesContainer(body);\n var emojiPickerElement = footer.find(SELECTORS.EMOJI_PICKER);\n var emojiAutoCompleteContainer = footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);\n var messageTextArea = footer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var headerActivateHandlers = [\n [SELECTORS.ACTION_REQUEST_BLOCK, generateConfirmActionHandler(requestBlockUser)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_REMOVE_CONTACT, generateConfirmActionHandler(requestRemoveContact)],\n [SELECTORS.ACTION_REQUEST_DELETE_CONVERSATION, generateConfirmActionHandler(requestDeleteConversation)],\n [SELECTORS.ACTION_CANCEL_EDIT_MODE, handleCancelEditMode],\n [SELECTORS.ACTION_VIEW_CONTACT, generateHandleViewContact(namespace)],\n [SELECTORS.ACTION_VIEW_GROUP_INFO, generateHandleViewGroupInfo(namespace)],\n [SELECTORS.ACTION_CONFIRM_FAVOURITE, handleSetFavourite],\n [SELECTORS.ACTION_CONFIRM_MUTE, handleSetMuted],\n [SELECTORS.ACTION_CONFIRM_UNFAVOURITE, handleUnsetFavourite],\n [SELECTORS.ACTION_CONFIRM_UNMUTE, handleUnsetMuted]\n ];\n var bodyActivateHandlers = [\n [SELECTORS.ACTION_CANCEL_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_CONFIRM_BLOCK, generateConfirmActionHandler(blockUser)],\n [SELECTORS.ACTION_CONFIRM_UNBLOCK, generateConfirmActionHandler(unblockUser)],\n [SELECTORS.ACTION_CONFIRM_ADD_CONTACT, generateConfirmActionHandler(addContact)],\n [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT, generateConfirmActionHandler(removeContact)],\n [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(deleteSelectedMessages)],\n [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION, generateConfirmActionHandler(deleteConversation)],\n [SELECTORS.ACTION_OKAY_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST, generateConfirmActionHandler(acceptContactRequest)],\n [SELECTORS.ACTION_DECLINE_CONTACT_REQUEST, generateConfirmActionHandler(declineContactRequest)],\n [SELECTORS.MESSAGE, handleSelectMessage],\n [SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE, handleDeleteMessagesForAllUsersToggle],\n [SELECTORS.RETRY_SEND, handleRetrySendMessage]\n ];\n var footerActivateHandlers = [\n [SELECTORS.SEND_MESSAGE_BUTTON, handleSendMessage],\n [SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON, handleToggleEmojiPicker],\n [SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(requestDeleteSelectedMessages)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n ];\n\n AutoRows.init(footer);\n\n if (emojiAutoCompleteContainer.length) {\n initialiseEmojiAutoComplete(\n emojiAutoCompleteContainer[0],\n messageTextArea[0],\n function(hasSuggestions) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, hasSuggestions);\n render(newState);\n },\n function(emoji) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, false);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos).replace(/\\S*$/, '');\n var textAfter = currentText.substring(cursorPos).replace(/^\\S*/, '');\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', textBefore.length + emoji.length);\n messageTextArea.prop('selectionEnd', textBefore.length + emoji.length);\n }\n );\n }\n\n if (emojiPickerElement.length) {\n initialiseEmojiPicker(emojiPickerElement[0], function(emoji) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos);\n var textAfter = currentText.substring(cursorPos, currentText.length);\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', cursorPos + emoji.length);\n messageTextArea.prop('selectionEnd', cursorPos + emoji.length);\n });\n }\n\n CustomEvents.define(header, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(body, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(footer, [\n CustomEvents.events.activate,\n CustomEvents.events.enter,\n CustomEvents.events.escape\n ]);\n CustomEvents.define(messagesContainer, [\n CustomEvents.events.scrollTop,\n CustomEvents.events.scrollLock\n ]);\n\n messagesContainer.on(CustomEvents.events.scrollTop, function(e, data) {\n var hasMembers = Object.keys(viewState.members).length > 1;\n\n if (!isResetting && !isLoadingMoreMessages && !hasLoadedAllMessages() && hasMembers) {\n isLoadingMoreMessages = true;\n var newState = StateManager.setLoadingMessages(viewState, true);\n render(newState);\n\n loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, [])\n .then(function() {\n isLoadingMoreMessages = false;\n setMessagesOffset(getMessagesOffset() + LOAD_MESSAGE_LIMIT);\n return;\n })\n .catch(function(error) {\n isLoadingMoreMessages = false;\n Notification.exception(error);\n });\n }\n\n data.originalEvent.preventDefault();\n });\n\n headerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n header.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n bodyActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n body.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n footer.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footer.on(CustomEvents.events.enter, SELECTORS.MESSAGE_TEXT_AREA, function(e, data) {\n var enterToSend = footer.attr('data-enter-to-send');\n if (enterToSend && enterToSend != 'false' && enterToSend != '0') {\n handleSendMessage(e, data);\n }\n });\n\n footer.on(CustomEvents.events.escape, SELECTORS.EMOJI_PICKER_CONTAINER, handleToggleEmojiPicker);\n $(document.body).on('click', handleCloseEmojiPicker);\n\n PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED, function(newRouteData) {\n if (newMessagesPollTimer) {\n if (newRouteData.route != MessageDrawerRoutes.VIEW_CONVERSATION) {\n newMessagesPollTimer.stop();\n }\n }\n });\n };\n\n /**\n * Reset the timer that polls for new messages.\n *\n * @param {Number} conversationId The conversation id\n */\n var resetMessagePollTimer = function(conversationId) {\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n newMessagesPollTimer = new BackOffTimer(\n getLoadNewMessagesCallback(conversationId, NEWEST_FIRST),\n BackOffTimer.getIncrementalCallback(\n viewState.messagePollMin * MILLISECONDS_IN_SEC,\n MILLISECONDS_IN_SEC,\n viewState.messagePollMax * MILLISECONDS_IN_SEC,\n viewState.messagePollAfterMax * MILLISECONDS_IN_SEC\n )\n );\n\n newMessagesPollTimer.start();\n };\n\n /**\n * Reset the state to the initial state and render the UI.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number|null} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n */\n var resetState = function(body, conversationId, loggedInUserProfile) {\n // Reset all of the states back to the beginning if we're loading a new\n // conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n loadedAllMessages = false;\n messagesOffset = 0;\n newMessagesPollTimer = null;\n isRendering = false;\n renderBuffer = [];\n isResetting = true;\n isSendingMessage = false;\n isDeletingConversationContent = false;\n sendMessageBuffer = [];\n\n var loggedInUserId = loggedInUserProfile.id;\n var midnight = parseInt(body.attr('data-midnight'), 10);\n var messagePollMin = parseInt(body.attr('data-message-poll-min'), 10);\n var messagePollMax = parseInt(body.attr('data-message-poll-max'), 10);\n var messagePollAfterMax = parseInt(body.attr('data-message-poll-after-max'), 10);\n var initialState = StateManager.buildInitialState(\n midnight,\n loggedInUserId,\n conversationId,\n messagePollMin,\n messagePollMax,\n messagePollAfterMax\n );\n\n if (!viewState) {\n viewState = initialState;\n }\n\n render(initialState);\n };\n\n /**\n * Load a new empty private conversation between two users or self-conversation.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @param {Int} otherUserId The other user's id.\n * @return {Promise} Renderer promise.\n */\n var resetNoConversation = function(body, loggedInUserProfile, otherUserId) {\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, null, loggedInUserProfile);\n\n var resetNoConversationPromise = null;\n\n if (loggedInUserProfile.id != otherUserId) {\n // Private conversation between two different users.\n resetNoConversationPromise = Repository.getConversationBetweenUsers(\n loggedInUserProfile.id,\n otherUserId,\n true,\n true,\n 0,\n 0,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n } else {\n // Self conversation.\n resetNoConversationPromise = Repository.getSelfConversation(\n loggedInUserProfile.id,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return resetNoConversationPromise.then(function(conversation) {\n // Looks like we have a conversation after all! Let's use that.\n return resetByConversation(body, conversation, loggedInUserProfile);\n })\n .catch(function() {\n // Can't find a conversation. Oh well. Just load up a blank one.\n return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetById = function(body, conversationId, loggedInUserProfile) {\n var cache = null;\n if (conversationId in stateCache) {\n cache = stateCache[conversationId];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversationId, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadNewConversation(\n conversationId,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversationId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} conversation The conversation.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetByConversation = function(body, conversation, loggedInUserProfile) {\n var cache = null;\n if (conversation.id in stateCache) {\n cache = stateCache[conversation.id];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversation.id, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadExistingConversation(\n conversation,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversation.id);\n });\n };\n\n /**\n * Setup the conversation page. This is a rather complex function because there are a\n * few combinations of arguments that can be provided to this function to show the\n * conversation.\n *\n * There are:\n * 1.) A conversation object with no action or other user id (e.g. from the overview page)\n * 2.) A conversation id with no action or other user id (e.g. from the contacts page)\n * 3.) No conversation/id with an action and other other user id. (e.g. from contact page)\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n * @param {Object|Number|null} conversationOrId Conversation or id or null\n * @param {String} action An action to take on the conversation\n * @param {Number} otherUserId The other user id for a private conversation\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, conversationOrId, action, otherUserId) {\n var conversation = null;\n var conversationId = null;\n\n // Check what we were given to identify the conversation.\n if (conversationOrId && conversationOrId !== null && typeof conversationOrId == 'object') {\n conversation = conversationOrId;\n conversationId = parseInt(conversation.id, 10);\n } else {\n conversation = null;\n conversationId = parseInt(conversationOrId, 10);\n conversationId = isNaN(conversationId) ? null : conversationId;\n }\n\n if (!conversationId && action && otherUserId) {\n // If we didn't get a conversation id got a user id then let's see if we've\n // previously loaded a private conversation with this user.\n conversationId = getCachedPrivateConversationIdFromUserId(otherUserId);\n }\n\n // 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","Pending","PubSub","Str","Repository","MessageDrawerEvents","Constants","Patcher","Renderer","StateManager","MessageDrawerRouter","MessageDrawerRoutes","initialiseEmojiAutoComplete","initialiseEmojiPicker","stateCache","viewState","loadedAllMessages","messagesOffset","newMessagesPollTimer","isRendering","renderBuffer","isResetting","isSendingMessage","isDeletingConversationContent","sendMessageBuffer","render","renderers","NEWEST_FIRST","NEWEST_MESSAGES_FIRST","LOAD_MESSAGE_LIMIT","MILLISECONDS_IN_SEC","SELECTORS","CONVERSATION_TYPES","getOtherUserId","type","PUBLIC","loggedInUserId","SELF","otherUserIds","Object","keys","members","filter","userId","length","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","pendingPromise","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","stop","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","previewText","plaintext","replace","sendMessage","Date","now","loadingmessage","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","textAfter","events","activate","enter","escape","scrollTop","scrollLock","on","hasMembers","handler","selector","handlerFunction","enterToSend","document","subscribe","ROUTE_CHANGED","newRouteData","route","VIEW_CONVERSATION","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/pending',\n 'core/pubsub',\n 'core/str',\n 'core_message/message_repository',\n 'core_message/message_drawer_events',\n 'core_message/message_drawer_view_conversation_constants',\n 'core_message/message_drawer_view_conversation_patcher',\n 'core_message/message_drawer_view_conversation_renderer',\n 'core_message/message_drawer_view_conversation_state_manager',\n 'core_message/message_drawer_router',\n 'core_message/message_drawer_routes',\n 'core/emoji/auto_complete',\n 'core/emoji/picker'\n],\nfunction(\n $,\n AutoRows,\n BackOffTimer,\n CustomEvents,\n Notification,\n Pending,\n PubSub,\n Str,\n Repository,\n MessageDrawerEvents,\n Constants,\n Patcher,\n Renderer,\n StateManager,\n MessageDrawerRouter,\n MessageDrawerRoutes,\n initialiseEmojiAutoComplete,\n initialiseEmojiPicker\n) {\n\n // Contains a cache of all view states that have been loaded so far\n // which saves us having to reload stuff with network requests when\n // switching between conversations.\n var stateCache = {};\n // The current data representation of the view.\n var viewState = null;\n var loadedAllMessages = false;\n var messagesOffset = 0;\n var newMessagesPollTimer = null;\n var isRendering = false;\n var renderBuffer = [];\n // If the UI is currently resetting.\n var isResetting = true;\n // If the UI is currently sending a message.\n var isSendingMessage = false;\n // If the UI is currently deleting a conversation.\n var isDeletingConversationContent = false;\n // A buffer of messages to send.\n var sendMessageBuffer = [];\n // These functions which will be generated when this module is\n // first called. See generateRenderFunction for details.\n var render = null;\n // The list of renderers that have been registered to render\n // this conversation. See generateRenderFunction for details.\n var renderers = [];\n\n var NEWEST_FIRST = Constants.NEWEST_MESSAGES_FIRST;\n var LOAD_MESSAGE_LIMIT = Constants.LOAD_MESSAGE_LIMIT;\n var MILLISECONDS_IN_SEC = Constants.MILLISECONDS_IN_SEC;\n var SELECTORS = Constants.SELECTORS;\n var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;\n\n /**\n * Get the other user userid.\n *\n * @return {Number} Userid.\n */\n var getOtherUserId = function() {\n if (!viewState || viewState.type == CONVERSATION_TYPES.PUBLIC) {\n return null;\n }\n\n var loggedInUserId = viewState.loggedInUserId;\n if (viewState.type == CONVERSATION_TYPES.SELF) {\n // It's a self-conversation, so the other user is the one logged in.\n return loggedInUserId;\n }\n\n var otherUserIds = Object.keys(viewState.members).filter(function(userId) {\n return loggedInUserId != userId;\n });\n\n return otherUserIds.length ? otherUserIds[0] : null;\n };\n\n /**\n * Search the cache to see if we've already loaded a private conversation\n * with the given user id.\n *\n * @param {Number} userId The id of the other user.\n * @return {Number|null} Conversation id.\n */\n var getCachedPrivateConversationIdFromUserId = function(userId) {\n return Object.keys(stateCache).reduce(function(carry, id) {\n if (!carry) {\n var state = stateCache[id].state;\n\n if (state.type != CONVERSATION_TYPES.PUBLIC) {\n if (userId in state.members) {\n // We've found a cached conversation for this user!\n carry = state.id;\n }\n }\n }\n\n return carry;\n }, null);\n };\n\n /**\n * Get profile info for logged in user.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object}\n */\n var getLoggedInUserProfile = function(body) {\n return {\n id: parseInt(body.attr('data-user-id'), 10),\n fullname: null,\n profileimageurl: null,\n profileimageurlsmall: null,\n isonline: null,\n showonlinestatus: null,\n isblocked: null,\n iscontact: null,\n isdeleted: null,\n canmessage: null,\n canmessageevenifblocked: null,\n requirescontact: null,\n contactrequests: []\n };\n };\n\n /**\n * Get the messages offset value to load more messages.\n *\n * @return {Number}\n */\n var getMessagesOffset = function() {\n return messagesOffset;\n };\n\n /**\n * Set the messages offset value for loading more messages.\n *\n * @param {Number} value The offset value\n */\n var setMessagesOffset = function(value) {\n messagesOffset = value;\n stateCache[viewState.id].messagesOffset = value;\n };\n\n /**\n * Check if all messages have been loaded.\n *\n * @return {Bool}\n */\n var hasLoadedAllMessages = function() {\n return loadedAllMessages;\n };\n\n /**\n * Set whether all messages have been loaded or not.\n *\n * @param {Bool} value If all messages have been loaded.\n */\n var setLoadedAllMessages = function(value) {\n loadedAllMessages = value;\n stateCache[viewState.id].loadedAllMessages = value;\n };\n\n /**\n * Get the messages container element.\n *\n * @param {Object} body Conversation body container element.\n * @return {Object} The messages container element.\n */\n var getMessagesContainer = function(body) {\n return body.find(SELECTORS.MESSAGES_CONTAINER);\n };\n\n /**\n * Reformat the conversation for an event payload.\n *\n * @param {Object} state The view state.\n * @return {Object} New formatted conversation.\n */\n var formatConversationForEvent = function(state) {\n return {\n id: state.id,\n name: state.name,\n subname: state.subname,\n imageUrl: state.imageUrl,\n isFavourite: state.isFavourite,\n isMuted: state.isMuted,\n type: state.type,\n totalMemberCount: state.totalMemberCount,\n loggedInUserId: state.loggedInUserId,\n messages: state.messages.map(function(message) {\n return $.extend({}, message);\n }),\n members: Object.keys(state.members).map(function(id) {\n var formattedMember = $.extend({}, state.members[id]);\n formattedMember.contactrequests = state.members[id].contactrequests.map(function(request) {\n return $.extend({}, request);\n });\n return formattedMember;\n })\n };\n };\n\n /**\n * Load up an empty private conversation between the logged in user and the\n * other user. Sets all of the conversation details based on the other user.\n *\n * A conversation isn't created until the user sends the first message.\n *\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} otherUserId The other user id.\n * @return {Object} Profile returned from repository.\n */\n var loadEmptyPrivateConversation = function(loggedInUserProfile, otherUserId) {\n var loggedInUserId = loggedInUserProfile.id;\n // If the other user id is the same as the logged in user then this is a self\n // conversation.\n var conversationType = loggedInUserId == otherUserId ? CONVERSATION_TYPES.SELF : CONVERSATION_TYPES.PRIVATE;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true)\n .then(function(profiles) {\n if (profiles.length) {\n return profiles[0];\n } else {\n throw new Error('Unable to load other user profile');\n }\n })\n .then(function(profile) {\n // If the conversation is a self conversation then the profile loaded is the\n // logged in user so only add that to the members array.\n var members = conversationType == CONVERSATION_TYPES.SELF ? [profile] : [profile, loggedInUserProfile];\n var newState = StateManager.addMembers(viewState, members);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setName(newState, profile.fullname);\n newState = StateManager.setType(newState, conversationType);\n newState = StateManager.setImageUrl(newState, profile.profileimageurl);\n newState = StateManager.setTotalMemberCount(newState, members.length);\n render(newState);\n return profile;\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a new state from a conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Number} loggedInUserId The logged in user id.\n * @return {Object} new state.\n */\n var updateStateFromConversation = function(conversation, loggedInUserId) {\n var otherUser = null;\n if (conversation.type == CONVERSATION_TYPES.PRIVATE) {\n // For private conversations, remove current logged in user from the members list to get the other user.\n var otherUsers = conversation.members.filter(function(member) {\n return member.id != loggedInUserId;\n });\n otherUser = otherUsers.length ? otherUsers[0] : null;\n } else if (conversation.type == CONVERSATION_TYPES.SELF) {\n // Self-conversations have only one member.\n otherUser = conversation.members[0];\n }\n\n var name = conversation.name;\n var imageUrl = conversation.imageurl;\n\n if (conversation.type != CONVERSATION_TYPES.PUBLIC) {\n name = name || otherUser ? otherUser.fullname : '';\n imageUrl = imageUrl || otherUser ? otherUser.profileimageurl : '';\n }\n\n var newState = StateManager.addMembers(viewState, conversation.members);\n newState = StateManager.setName(newState, name);\n newState = StateManager.setSubname(newState, conversation.subname);\n newState = StateManager.setType(newState, conversation.type);\n newState = StateManager.setImageUrl(newState, imageUrl);\n newState = StateManager.setTotalMemberCount(newState, conversation.membercount);\n newState = StateManager.setIsFavourite(newState, conversation.isfavourite);\n newState = StateManager.setIsMuted(newState, conversation.ismuted);\n newState = StateManager.addMessages(newState, conversation.messages);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, conversation.candeletemessagesforallusers);\n return newState;\n };\n\n /**\n * Get the details for a conversation from the conversation id.\n *\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Number} messageOffset The number of messages to skip.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadNewConversation = function(\n conversationId,\n loggedInUserProfile,\n messageLimit,\n messageOffset,\n newestFirst\n ) {\n var loggedInUserId = loggedInUserProfile.id;\n var newState = StateManager.setLoadingMembers(viewState, true);\n newState = StateManager.setLoadingMessages(newState, true);\n render(newState);\n\n return Repository.getConversation(\n loggedInUserId,\n conversationId,\n true,\n true,\n 0,\n 0,\n messageLimit + 1,\n messageOffset,\n newestFirst\n )\n .then(function(conversation) {\n if (conversation.messages.length > messageLimit) {\n conversation.messages = conversation.messages.slice(1);\n } else {\n setLoadedAllMessages(true);\n }\n\n setMessagesOffset(messageOffset + messageLimit);\n\n return conversation;\n })\n .then(function(conversation) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return conversation;\n });\n })\n .then(function() {\n return markConversationAsRead(conversationId);\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMembers(viewState, false);\n newState = StateManager.setLoadingMessages(newState, false);\n render(newState);\n Notification.exception(error);\n });\n };\n\n /**\n * Get the details for a conversation from and existing conversation object.\n *\n * @param {Object} conversation The conversation object.\n * @param {Object} loggedInUserProfile The logged in user profile.\n * @param {Number} messageLimit The number of messages to include.\n * @param {Bool} newestFirst Order messages newest first.\n * @return {Object} Promise resolved when loaded.\n */\n var loadExistingConversation = function(\n conversation,\n loggedInUserProfile,\n messageLimit,\n newestFirst\n ) {\n var hasLoggedInUser = conversation.members.filter(function(member) {\n return member.id == loggedInUserProfile.id;\n });\n\n if (hasLoggedInUser.length < 1) {\n conversation.members = conversation.members.concat([loggedInUserProfile]);\n }\n\n var messageCount = conversation.messages.length;\n var hasLoadedEnoughMessages = messageCount >= messageLimit;\n var newState = updateStateFromConversation(conversation, loggedInUserProfile.id);\n newState = StateManager.setLoadingMembers(newState, false);\n newState = StateManager.setLoadingMessages(newState, !hasLoadedEnoughMessages);\n var renderPromise = render(newState);\n\n return renderPromise.then(function() {\n if (!hasLoadedEnoughMessages) {\n // We haven't got enough messages so let's load some more.\n return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []);\n } else {\n // We've got enough messages. No need to load any more for now.\n return {messages: conversation.messages};\n }\n })\n .then(function() {\n var messages = viewState.messages;\n // Update the offset to reflect the number of messages we've loaded.\n setMessagesOffset(messages.length);\n markConversationAsRead(viewState.id);\n\n return messages;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Load messages for this conversation and pass them to the renderer.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Number} limit Number of messages to load.\n * @param {Number} offset Get messages from offset.\n * @param {Bool} newestFirst Get newest messages first.\n * @param {Array} ignoreList Ignore any messages with ids in this list.\n * @param {Number|null} timeFrom Only get messages from this time onwards.\n * @return {Promise} renderer promise.\n */\n var loadMessages = function(conversationId, limit, offset, newestFirst, ignoreList, timeFrom) {\n return Repository.getMessages(\n viewState.loggedInUserId,\n conversationId,\n limit ? limit + 1 : limit,\n offset,\n newestFirst,\n timeFrom\n )\n .then(function(result) {\n // Prevent older requests from contaminating the current view.\n if (result.id != viewState.id) {\n result.messages = [];\n // Purge old conversation cache to prevent messages lose.\n if (result.id in stateCache) {\n delete stateCache[result.id];\n }\n }\n\n return result;\n })\n .then(function(result) {\n if (result.messages.length && ignoreList.length) {\n result.messages = result.messages.filter(function(message) {\n // Skip any messages in our ignore list.\n return ignoreList.indexOf(parseInt(message.id, 10)) < 0;\n });\n }\n\n return result;\n })\n .then(function(result) {\n if (!limit) {\n return result;\n } else if (result.messages.length > limit) {\n // Ignore the last result which was just to test if there are more\n // to load.\n result.messages = result.messages.slice(0, -1);\n } else {\n setLoadedAllMessages(true);\n }\n\n return result;\n })\n .then(function(result) {\n var membersToAdd = result.members.filter(function(member) {\n return !(member.id in viewState.members);\n });\n var newState = StateManager.addMembers(viewState, membersToAdd);\n newState = StateManager.addMessages(newState, result.messages);\n newState = StateManager.setLoadingMessages(newState, false);\n return render(newState)\n .then(function() {\n return result;\n });\n })\n .catch(function(error) {\n var newState = StateManager.setLoadingMessages(viewState, false);\n render(newState);\n // Re-throw the error for other error handlers.\n throw error;\n });\n };\n\n /**\n * Create a callback function for getting new messages for this conversation.\n *\n * @param {Number} conversationId Conversation id.\n * @param {Bool} newestFirst Show newest messages first\n * @return {Function} Callback function that returns a renderer promise.\n */\n var getLoadNewMessagesCallback = function(conversationId, newestFirst) {\n return function() {\n var messages = viewState.messages;\n var mostRecentMessage = messages.length ? messages[messages.length - 1] : null;\n var lastTimeCreated = mostRecentMessage ? mostRecentMessage.timeCreated : null;\n\n if (lastTimeCreated && !isResetting && !isSendingMessage && !isDeletingConversationContent) {\n // There may be multiple messages with the same time created value since\n // the accuracy is only down to the second. The server will include these\n // messages in the result (since it does a >= comparison on time from) so\n // we need to filter them back out of the result so that we're left only\n // with the new messages.\n var ignoreMessageIds = [];\n for (var i = messages.length - 1; i >= 0; i--) {\n var message = messages[i];\n if (message.timeCreated === lastTimeCreated) {\n ignoreMessageIds.push(message.id);\n } else {\n // Since the messages are ordered in ascending order of time created\n // we can break as soon as we hit a message with a different time created\n // because we know all other messages will have lower values.\n break;\n }\n }\n\n return loadMessages(\n conversationId,\n 0,\n 0,\n newestFirst,\n ignoreMessageIds,\n lastTimeCreated\n )\n .then(function(result) {\n if (result.messages.length) {\n // If we found some results then restart the polling timer\n // because the other user might be sending messages.\n newMessagesPollTimer.restart();\n // We've also got a new last message so publish that for other\n // components to update.\n var conversation = formatConversationForEvent(viewState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return markConversationAsRead(conversationId);\n } else {\n return result;\n }\n });\n }\n\n return $.Deferred().resolve().promise();\n };\n };\n\n /**\n * Mark a conversation as read.\n *\n * @param {Number} conversationId The conversation id.\n * @return {Promise} The renderer promise.\n */\n var markConversationAsRead = function(conversationId) {\n var loggedInUserId = viewState.loggedInUserId;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.markAllConversationMessagesAsRead(loggedInUserId, conversationId)\n .then(function() {\n var newState = StateManager.markMessagesAsRead(viewState, viewState.messages);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_READ, conversationId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is request to block a user and run the renderer\n * to show the block user dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestBlockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingBlockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to block a user, update the statemanager and publish\n * a contact has been blocked.\n *\n * @param {Number} userId User id of user to block.\n * @return {Promise} Renderer promise.\n */\n var blockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:blockUser');\n\n render(newState);\n\n return Repository.blockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_BLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to unblock a user and run the renderer\n * to show the unblock user dialogue.\n *\n * @param {Number} userId User id of user to unblock.\n */\n var requestUnblockUser = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to unblock a user, update the statemanager and publish\n * a contact has been unblocked.\n *\n * @param {Number} userId User id of user to unblock.\n * @return {Promise} Renderer promise.\n */\n var unblockUser = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unblockUser');\n render(newState);\n\n return Repository.unblockUser(viewState.loggedInUserId, userId)\n .then(function(profile) {\n var newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_UNBLOCKED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to remove a user from the contact list\n * and run the renderer to show the remove user from contacts dialogue.\n *\n * @param {Number} userId User id of user to remove from contacts.\n */\n var requestRemoveContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to remove a user from the contacts list. update the statemanager\n * and publish a contact has been removed.\n *\n * @param {Number} userId User id of user to remove from contacts.\n * @return {Promise} Renderer promise.\n */\n var removeContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:removeContact');\n render(newState);\n\n return Repository.deleteContacts(viewState.loggedInUserId, [userId])\n .then(function(profiles) {\n var newState = StateManager.addMembers(viewState, profiles);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONTACT_REMOVED, userId);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager there is a request to add a user to the contact list\n * and run the renderer to show the add user to contacts dialogue.\n *\n * @param {Number} userId User id of user to add to contacts.\n */\n var requestAddContact = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.addPendingAddContactsById(viewState, [userId]);\n render(newState);\n };\n\n /**\n * Send the repository a request to add a user to the contacts list. update the statemanager\n * and publish a contact has been added.\n *\n * @param {Number} userId User id of user to add to contacts.\n * @return {Promise} Renderer promise.\n */\n var addContact = function(userId) {\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:addContactRequests');\n render(newState);\n\n return Repository.createContactRequest(viewState.loggedInUserId, userId)\n .then(function(response) {\n if (!response.request) {\n throw new Error(response.warnings[0].message);\n }\n\n return response.request;\n })\n .then(function(request) {\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.addContactRequests(newState, [request]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:setFavourite');\n\n return Repository.setFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a favourite conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetFavourite = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:unsetFavourite');\n\n return Repository.unsetFavouriteConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsFavourite(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Set the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var setMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n\n return Repository.setMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, true);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_SET_MUTED,\n formatConversationForEvent(viewState)\n );\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Unset the current conversation as a muted conversation.\n *\n * @return {Promise} Renderer promise.\n */\n var unsetMuted = function() {\n var userId = viewState.loggedInUserId;\n var conversationId = viewState.id;\n\n return Repository.unsetMutedConversations(userId, [conversationId])\n .then(function() {\n var newState = StateManager.setIsMuted(viewState, false);\n return render(newState);\n })\n .then(function() {\n return PubSub.publish(\n MessageDrawerEvents.CONVERSATION_UNSET_MUTED,\n formatConversationForEvent(viewState)\n );\n });\n };\n\n /**\n * Tell the statemanager there is a request to delete the selected messages\n * and run the renderer to show confirm delete messages dialogue.\n *\n * @param {Number} userId User id.\n */\n var requestDeleteSelectedMessages = function(userId) {\n var selectedMessageIds = viewState.selectedMessageIds;\n cancelRequest(userId);\n var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete the messages pending deletion. Update the statemanager\n * and publish a message deletion event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteSelectedMessages = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:deleteSelectedMessages');\n var messageIds = viewState.pendingDeleteMessageIds;\n var sentMessages = viewState.messages.filter(function(message) {\n // If a message sendState is null then it means it was loaded from the server or if it's\n // set to sent then it means the user has successfully sent it in this page load.\n return messageIds.indexOf(message.id) >= 0 && (message.sendState == 'sent' || message.sendState === null);\n });\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n\n render(newState);\n\n var deleteMessagesPromise = $.Deferred().resolve().promise();\n\n\n if (sentMessages.length) {\n // We only need to send a request to the server if we're trying to delete messages that\n // have successfully been sent.\n var sentMessageIds = sentMessages.map(function(message) {\n return message.id;\n });\n if (newState.deleteMessagesForAllUsers) {\n deleteMessagesPromise = Repository.deleteMessagesForAllUsers(viewState.loggedInUserId, sentMessageIds);\n } else {\n deleteMessagesPromise = Repository.deleteMessages(viewState.loggedInUserId, sentMessageIds);\n }\n }\n\n // Mark that we are deleting content from the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return deleteMessagesPromise.then(function() {\n var newState = StateManager.removeMessagesById(viewState, messageIds);\n newState = StateManager.removePendingDeleteMessagesById(newState, messageIds);\n newState = StateManager.removeSelectedMessagesById(newState, messageIds);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n\n var prevLastMessage = viewState.messages[viewState.messages.length - 1];\n var newLastMessage = newState.messages.length ? newState.messages[newState.messages.length - 1] : null;\n\n if (newLastMessage && newLastMessage.id != prevLastMessage.id) {\n var conversation = formatConversationForEvent(newState);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n } else if (!newState.messages.length) {\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n }\n\n isDeletingConversationContent = false;\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(Notification.exception);\n };\n\n /**\n * Tell the statemanager there is a request to delete a conversation\n * and run the renderer to show confirm delete conversation dialogue.\n *\n * @param {Number} userId User id of other user.\n */\n var requestDeleteConversation = function(userId) {\n cancelRequest(userId);\n var newState = StateManager.setPendingDeleteConversation(viewState, true);\n render(newState);\n };\n\n /**\n * Send the repository a request to delete a conversation. Update the statemanager\n * and publish a conversation deleted event.\n *\n * @return {Promise} Renderer promise.\n */\n var deleteConversation = function() {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:markConversationAsRead');\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n // Mark that we are deleting the conversation to prevent updates of it.\n isDeletingConversationContent = true;\n\n // Stop polling for new messages to the open conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n return Repository.deleteConversation(viewState.loggedInUserId, viewState.id)\n .then(function() {\n var newState = StateManager.removeMessages(viewState, viewState.messages);\n newState = StateManager.removeSelectedMessagesById(newState, viewState.selectedMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_DELETED, newState.id);\n\n isDeletingConversationContent = false;\n\n return render(newState);\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Tell the statemanager to cancel all pending actions.\n *\n * @param {Number} userId User id.\n */\n var cancelRequest = function(userId) {\n var pendingDeleteMessageIds = viewState.pendingDeleteMessageIds;\n var newState = StateManager.removePendingAddContactsById(viewState, [userId]);\n newState = StateManager.removePendingRemoveContactsById(newState, [userId]);\n newState = StateManager.removePendingUnblockUsersById(newState, [userId]);\n newState = StateManager.removePendingBlockUsersById(newState, [userId]);\n newState = StateManager.removePendingDeleteMessagesById(newState, pendingDeleteMessageIds);\n newState = StateManager.setPendingDeleteConversation(newState, false);\n newState = StateManager.setDeleteMessagesForAllUsers(newState, false);\n render(newState);\n };\n\n /**\n * Accept the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var acceptContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:acceptContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.acceptContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_ADDED, viewState.members[userId]);\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_ACCEPTED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Decline the contact request from the given user.\n *\n * @param {Number} userId User id of other user.\n * @return {Promise} Renderer promise.\n */\n var declineContactRequest = function(userId) {\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:declineContactRequest');\n\n // Search the list of the logged in user's contact requests to find the\n // one from this user.\n var loggedInUserId = viewState.loggedInUserId;\n var requests = viewState.members[userId].contactrequests.filter(function(request) {\n return request.requesteduserid == loggedInUserId;\n });\n var request = requests[0];\n var newState = StateManager.setLoadingConfirmAction(viewState, true);\n render(newState);\n\n return Repository.declineContactRequest(userId, loggedInUserId)\n .then(function(profile) {\n var newState = StateManager.removeContactRequests(viewState, [request]);\n newState = StateManager.addMembers(viewState, [profile]);\n newState = StateManager.setLoadingConfirmAction(newState, false);\n return render(newState);\n })\n .then(function() {\n PubSub.publish(MessageDrawerEvents.CONTACT_REQUEST_DECLINED, request);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n });\n };\n\n /**\n * Send all of the messages in the buffer to the server to be created. Update the\n * UI with the newly created message information.\n *\n * This function will recursively call itself in order to make sure the buffer is\n * always being processed.\n */\n var processSendMessageBuffer = function() {\n if (isSendingMessage) {\n // We're already sending messages so nothing to do.\n return;\n }\n if (!sendMessageBuffer.length) {\n // No messages waiting to send. Nothing to do.\n return;\n }\n\n var pendingPromise = new Pending('core_message/message_drawer_view_conversation:processSendMessageBuffer');\n\n // Flag that we're processing the queue.\n isSendingMessage = true;\n // Grab all of the messages in the buffer.\n var messagesToSend = sendMessageBuffer.slice();\n // Empty the buffer since we're processing it.\n sendMessageBuffer = [];\n var conversationId = viewState.id;\n var newConversationId = null;\n var messagesText = messagesToSend.map(function(message) {\n return message.text;\n });\n var messageIds = messagesToSend.map(function(message) {\n return message.id;\n });\n var sendMessagePromise = null;\n var newCanDeleteMessagesForAllUsers = null;\n if (!conversationId && (viewState.type != CONVERSATION_TYPES.PUBLIC)) {\n // If it's a new private conversation then we need to use the old\n // web service function to create the conversation.\n var otherUserId = getOtherUserId();\n sendMessagePromise = Repository.sendMessagesToUser(otherUserId, messagesText)\n .then(function(messages) {\n if (messages.length) {\n newConversationId = parseInt(messages[0].conversationid, 10);\n newCanDeleteMessagesForAllUsers = messages[0].candeletemessagesforallusers;\n }\n return messages;\n });\n } else {\n sendMessagePromise = Repository.sendMessagesToConversation(conversationId, messagesText);\n }\n\n sendMessagePromise\n .then(function(messages) {\n var newMessageIds = messages.map(function(message) {\n return message.id;\n });\n var data = [];\n var selectedToRemove = [];\n var selectedToAdd = [];\n\n messagesToSend.forEach(function(oldMessage, index) {\n var newMessage = messages[index];\n // Update messages expects and array of arrays where the first value\n // is the old message to update and the second value is the new values\n // to set.\n data.push([oldMessage, newMessage]);\n\n if (viewState.selectedMessageIds.indexOf(oldMessage.id) >= 0) {\n // If the message was added to the \"selected messages\" list while it was still\n // being sent then we should update it's id in that list now to make sure future\n // actions work.\n selectedToRemove.push(oldMessage.id);\n selectedToAdd.push(newMessage.id);\n }\n });\n var newState = StateManager.updateMessages(viewState, data);\n newState = StateManager.setMessagesSendSuccessById(newState, newMessageIds);\n\n if (selectedToRemove.length) {\n newState = StateManager.removeSelectedMessagesById(newState, selectedToRemove);\n }\n\n if (selectedToAdd.length) {\n newState = StateManager.addSelectedMessagesById(newState, selectedToAdd);\n }\n\n var conversation = formatConversationForEvent(newState);\n\n if (!newState.id) {\n // If this message created the conversation then save the conversation\n // id.\n newState = StateManager.setId(newState, newConversationId);\n conversation.id = newConversationId;\n resetMessagePollTimer(newConversationId);\n PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED, conversation);\n newState = StateManager.setCanDeleteMessagesForAllUsers(newState, newCanDeleteMessagesForAllUsers);\n }\n\n // Update the UI with the new message values from the server.\n render(newState);\n // Recurse just in case there has been more messages added to the buffer.\n isSendingMessage = false;\n processSendMessageBuffer();\n PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation);\n return;\n })\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n var errorMessage;\n if (e.message) {\n errorMessage = $.Deferred().resolve(e.message).promise();\n } else {\n errorMessage = Str.get_string('unknownerror', 'core');\n }\n\n var handleFailedMessages = function(errorMessage) {\n // We failed to create messages so remove the old messages from the pending queue\n // and update the UI to indicate that the message failed.\n var newState = StateManager.setMessagesSendFailById(viewState, messageIds, errorMessage);\n render(newState);\n isSendingMessage = false;\n processSendMessageBuffer();\n };\n\n errorMessage.then(handleFailedMessages)\n .then(function(result) {\n pendingPromise.resolve();\n\n return result;\n })\n .catch(function(e) {\n // Hrmm, we can't even load the error messages string! We'll have to\n // hard code something in English here if we still haven't got a message\n // to show.\n var finalError = e.message || 'Something went wrong!';\n handleFailedMessages(finalError);\n });\n });\n };\n\n /**\n * Create a plain version of an HTML text.\n *\n * This texts is used as a message preview while is sent to the server. This way\n * it is possible to prevent self-xss.\n *\n * @param {String} text Text to send.\n * @return {String} The plain text version of the text.\n */\n const previewText = function(text) {\n // Remove all script and styles from text (we don't want it there).\n let plaintext = text.replace(//gi, '');\n plaintext = plaintext.replace(//gi, '');\n // Beautify a bit the output adding some line breaks.\n plaintext = plaintext.replace(/<\\/div>/ig, '\\n');\n plaintext = plaintext.replace(/<\\/li>/ig, '\\n');\n plaintext = plaintext.replace(/
  • /ig, ' * ');\n plaintext = plaintext.replace(/<\\/ul>/ig, '\\n');\n plaintext = plaintext.replace(/<\\/p>/ig, '\\n');\n plaintext = plaintext.replace(/]*>/gi, '\\n');\n // Remove all remaining tags and convert line breaks into html.\n plaintext = plaintext.replace(/<[^>]+>/ig, '');\n plaintext = plaintext.replace(/\\n+/ig, '\\n');\n return plaintext.replace(/\\n/ig, '
    ');\n };\n\n /**\n * Buffers messages to be sent to the server. We use a buffer here to allow the\n * user to freely input messages without blocking the interface for them.\n *\n * Instead we just queue all of their messages up and send them as fast as we can.\n *\n * @param {String} text Text to send.\n */\n var sendMessage = function(text) {\n var id = 'temp' + Date.now();\n // Render a preview version of the message while sending.\n let loadingmessage = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: previewText(text),\n timecreated: null\n };\n var newState = StateManager.addMessages(viewState, [loadingmessage]);\n render(newState);\n // Send the real message.\n var message = {\n id: id,\n useridfrom: viewState.loggedInUserId,\n text: text,\n timecreated: null\n };\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\n };\n\n /**\n * Retry sending a message that failed.\n *\n * @param {Object} message The message to send.\n */\n var retrySendMessage = function(message) {\n var newState = StateManager.setMessagesSendPendingById(viewState, [message.id]);\n render(newState);\n sendMessageBuffer.push(message);\n processSendMessageBuffer();\n };\n\n /**\n * Toggle the selected messages update the statemanager and render the result.\n *\n * @param {Number} messageId The id of the message to be toggled\n */\n var toggleSelectMessage = function(messageId) {\n var newState = viewState;\n\n if (viewState.selectedMessageIds.indexOf(messageId) > -1) {\n newState = StateManager.removeSelectedMessagesById(viewState, [messageId]);\n } else {\n newState = StateManager.addSelectedMessagesById(viewState, [messageId]);\n }\n\n render(newState);\n };\n\n /**\n * Cancel edit mode (selecting the messages).\n */\n var cancelEditMode = function() {\n cancelRequest(getOtherUserId());\n var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds);\n render(newState);\n };\n\n /**\n * Process the patches in the render buffer one at a time in order until the\n * buffer is empty.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n */\n var processRenderBuffer = function(header, body, footer) {\n if (isRendering) {\n return;\n }\n\n if (!renderBuffer.length) {\n return;\n }\n\n isRendering = true;\n var renderable = renderBuffer.shift();\n var renderPromises = renderers.map(function(renderFunc) {\n return renderFunc(renderable.patch);\n });\n\n $.when.apply(null, renderPromises)\n .then(function() {\n isRendering = false;\n renderable.deferred.resolve(true);\n // Keep processing the buffer until it's empty.\n processRenderBuffer(header, body, footer);\n\n return;\n })\n .catch(function(error) {\n isRendering = false;\n renderable.deferred.reject(error);\n Notification.exception(error);\n });\n };\n\n /**\n * Create a function to render the Conversation.\n *\n * @param {Object} header The conversation header container element.\n * @param {Object} body The conversation body container element.\n * @param {Object} footer The conversation footer container element.\n * @param {Bool} isNewConversation Has someone else already initialised a conversation?\n * @return {Promise} Renderer promise.\n */\n var generateRenderFunction = function(header, body, footer, isNewConversation) {\n var rendererFunc = function(patch) {\n return Renderer.render(header, body, footer, patch);\n };\n\n if (!isNewConversation) {\n // Looks like someone got here before us! We'd better update our\n // UI to make sure it matches.\n var initialState = StateManager.buildInitialState(viewState.midnight, viewState.loggedInUserId, viewState.id);\n var syncPatch = Patcher.buildPatch(initialState, viewState);\n rendererFunc(syncPatch);\n }\n\n renderers.push(rendererFunc);\n\n return function(newState) {\n var patch = Patcher.buildPatch(viewState, newState);\n var deferred = $.Deferred();\n\n // Check if the patch has any data. Ignore empty patches.\n if (Object.keys(patch).length) {\n // Add the patch to the render buffer which gets processed in order.\n renderBuffer.push({\n patch: patch,\n deferred: deferred\n });\n } else {\n deferred.resolve(true);\n }\n // This is a great place to add in some console logging if you need\n // to debug something. You can log the current state, the next state,\n // and the generated patch and see exactly what will be updated.\n\n // Optimistically update the state. We're going to assume that the rendering\n // will always succeed. The rendering is asynchronous (annoyingly) so it's buffered\n // but it'll reach eventual consistency with the current state.\n viewState = newState;\n if (newState.id) {\n // Only cache created conversations.\n stateCache[newState.id] = {\n state: newState,\n messagesOffset: getMessagesOffset(),\n loadedAllMessages: hasLoadedAllMessages()\n };\n }\n\n // Start processing the buffer.\n processRenderBuffer(header, body, footer);\n\n return deferred.promise();\n };\n };\n\n /**\n * Create a confirm action function.\n *\n * @param {Function} actionCallback The callback function.\n * @return {Function} Confirm action handler.\n */\n var generateConfirmActionHandler = function(actionCallback) {\n return function(e, data) {\n if (!viewState.loadingConfirmAction) {\n actionCallback(getOtherUserId());\n var newState = StateManager.setLoadingConfirmAction(viewState, false);\n render(newState);\n }\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Send message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSendMessage = function(e, data) {\n var target = $(e.target);\n var footerContainer = target.closest(SELECTORS.FOOTER_CONTAINER);\n var textArea = footerContainer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var text = textArea.val().trim();\n\n if (text !== '') {\n sendMessage(text);\n textArea.val('');\n textArea.focus();\n }\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Select message event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSelectMessage = function(e, data) {\n var selection = window.getSelection();\n var target = $(e.target);\n\n if (selection.toString() != '') {\n // Bail if we're selecting.\n return;\n }\n\n if (target.is('a')) {\n // Clicking on a link in the message so ignore it.\n return;\n }\n\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n\n toggleSelectMessage(messageId);\n\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle retry sending of message.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleRetrySendMessage = function(e, data) {\n var target = $(e.target);\n var element = target.closest(SELECTORS.MESSAGE);\n var messageId = element.attr('data-message-id');\n var messages = viewState.messages.filter(function(message) {\n return message.id == messageId;\n });\n var message = messages.length ? messages[0] : null;\n\n if (message) {\n retrySendMessage(message);\n }\n\n data.originalEvent.preventDefault();\n data.originalEvent.stopPropagation();\n e.stopPropagation();\n };\n\n /**\n * Cancel edit mode event handler.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleCancelEditMode = function(e, data) {\n cancelEditMode();\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View contact handler.\n */\n var generateHandleViewContact = function(namespace) {\n return function(e, data) {\n var otherUserId = getOtherUserId();\n var otherUser = viewState.members[otherUserId];\n MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONTACT, otherUser);\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Set this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetFavourite = function(e, data) {\n setFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as a favourite.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetFavourite = function(e, data) {\n unsetFavourite().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Show the view group info page.\n * Set this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleSetMuted = function(e, data) {\n setMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Unset this conversation as muted.\n *\n * @param {Object} e Element this event handler is called on.\n * @param {Object} data Data for this event.\n */\n var handleUnsetMuted = function(e, data) {\n unsetMuted().catch(Notification.exception);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking on the checkbox that toggles deleting messages for\n * all users.\n *\n * @param {Object} e Element this event handler is called on.\n */\n var handleDeleteMessagesForAllUsersToggle = function(e) {\n var newValue = $(e.target).prop('checked');\n var newState = StateManager.setDeleteMessagesForAllUsers(viewState, newValue);\n render(newState);\n };\n\n /**\n * Show the view contact page.\n *\n * @param {String} namespace Unique identifier for the Routes\n * @return {Function} View group info handler.\n */\n var generateHandleViewGroupInfo = function(namespace) {\n return function(e, data) {\n MessageDrawerRouter.go(\n namespace,\n MessageDrawerRoutes.VIEW_GROUP_INFO,\n {\n id: viewState.id,\n name: viewState.name,\n subname: viewState.subname,\n imageUrl: viewState.imageUrl,\n totalMemberCount: viewState.totalMemberCount\n },\n viewState.loggedInUserId\n );\n data.originalEvent.preventDefault();\n };\n };\n\n /**\n * Handle clicking on the emoji toggle button.\n *\n * @param {Object} e The event\n * @param {Object} data The custom interaction event data\n */\n var handleToggleEmojiPicker = function(e, data) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n data.originalEvent.preventDefault();\n };\n\n /**\n * Handle clicking outside the emoji picker to close it.\n *\n * @param {Object} e The event\n */\n var handleCloseEmojiPicker = function(e) {\n var target = $(e.target);\n\n if (\n viewState.showEmojiPicker &&\n !target.closest(SELECTORS.EMOJI_PICKER_CONTAINER).length &&\n !target.closest(SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON).length\n ) {\n var newState = StateManager.setShowEmojiPicker(viewState, false);\n render(newState);\n }\n };\n\n /**\n * Listen to, and handle events for conversations.\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n */\n var registerEventListeners = function(namespace, header, body, footer) {\n var isLoadingMoreMessages = false;\n var messagesContainer = getMessagesContainer(body);\n var emojiPickerElement = footer.find(SELECTORS.EMOJI_PICKER);\n var emojiAutoCompleteContainer = footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);\n var messageTextArea = footer.find(SELECTORS.MESSAGE_TEXT_AREA);\n var headerActivateHandlers = [\n [SELECTORS.ACTION_REQUEST_BLOCK, generateConfirmActionHandler(requestBlockUser)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_REMOVE_CONTACT, generateConfirmActionHandler(requestRemoveContact)],\n [SELECTORS.ACTION_REQUEST_DELETE_CONVERSATION, generateConfirmActionHandler(requestDeleteConversation)],\n [SELECTORS.ACTION_CANCEL_EDIT_MODE, handleCancelEditMode],\n [SELECTORS.ACTION_VIEW_CONTACT, generateHandleViewContact(namespace)],\n [SELECTORS.ACTION_VIEW_GROUP_INFO, generateHandleViewGroupInfo(namespace)],\n [SELECTORS.ACTION_CONFIRM_FAVOURITE, handleSetFavourite],\n [SELECTORS.ACTION_CONFIRM_MUTE, handleSetMuted],\n [SELECTORS.ACTION_CONFIRM_UNFAVOURITE, handleUnsetFavourite],\n [SELECTORS.ACTION_CONFIRM_UNMUTE, handleUnsetMuted]\n ];\n var bodyActivateHandlers = [\n [SELECTORS.ACTION_CANCEL_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_CONFIRM_BLOCK, generateConfirmActionHandler(blockUser)],\n [SELECTORS.ACTION_CONFIRM_UNBLOCK, generateConfirmActionHandler(unblockUser)],\n [SELECTORS.ACTION_CONFIRM_ADD_CONTACT, generateConfirmActionHandler(addContact)],\n [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT, generateConfirmActionHandler(removeContact)],\n [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(deleteSelectedMessages)],\n [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION, generateConfirmActionHandler(deleteConversation)],\n [SELECTORS.ACTION_OKAY_CONFIRM, generateConfirmActionHandler(cancelRequest)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST, generateConfirmActionHandler(acceptContactRequest)],\n [SELECTORS.ACTION_DECLINE_CONTACT_REQUEST, generateConfirmActionHandler(declineContactRequest)],\n [SELECTORS.MESSAGE, handleSelectMessage],\n [SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE, handleDeleteMessagesForAllUsersToggle],\n [SELECTORS.RETRY_SEND, handleRetrySendMessage]\n ];\n var footerActivateHandlers = [\n [SELECTORS.SEND_MESSAGE_BUTTON, handleSendMessage],\n [SELECTORS.TOGGLE_EMOJI_PICKER_BUTTON, handleToggleEmojiPicker],\n [SELECTORS.ACTION_REQUEST_DELETE_SELECTED_MESSAGES, generateConfirmActionHandler(requestDeleteSelectedMessages)],\n [SELECTORS.ACTION_REQUEST_ADD_CONTACT, generateConfirmActionHandler(requestAddContact)],\n [SELECTORS.ACTION_REQUEST_UNBLOCK, generateConfirmActionHandler(requestUnblockUser)],\n ];\n\n AutoRows.init(footer);\n\n if (emojiAutoCompleteContainer.length) {\n initialiseEmojiAutoComplete(\n emojiAutoCompleteContainer[0],\n messageTextArea[0],\n function(hasSuggestions) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, hasSuggestions);\n render(newState);\n },\n function(emoji) {\n var newState = StateManager.setShowEmojiAutoComplete(viewState, false);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos).replace(/\\S*$/, '');\n var textAfter = currentText.substring(cursorPos).replace(/^\\S*/, '');\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', textBefore.length + emoji.length);\n messageTextArea.prop('selectionEnd', textBefore.length + emoji.length);\n }\n );\n }\n\n if (emojiPickerElement.length) {\n initialiseEmojiPicker(emojiPickerElement[0], function(emoji) {\n var newState = StateManager.setShowEmojiPicker(viewState, !viewState.showEmojiPicker);\n render(newState);\n\n messageTextArea.focus();\n var cursorPos = messageTextArea.prop('selectionStart');\n var currentText = messageTextArea.val();\n var textBefore = currentText.substring(0, cursorPos);\n var textAfter = currentText.substring(cursorPos, currentText.length);\n\n messageTextArea.val(textBefore + emoji + textAfter);\n // Set the cursor position to after the inserted emoji.\n messageTextArea.prop('selectionStart', cursorPos + emoji.length);\n messageTextArea.prop('selectionEnd', cursorPos + emoji.length);\n });\n }\n\n CustomEvents.define(header, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(body, [\n CustomEvents.events.activate\n ]);\n CustomEvents.define(footer, [\n CustomEvents.events.activate,\n CustomEvents.events.enter,\n CustomEvents.events.escape\n ]);\n CustomEvents.define(messagesContainer, [\n CustomEvents.events.scrollTop,\n CustomEvents.events.scrollLock\n ]);\n\n messagesContainer.on(CustomEvents.events.scrollTop, function(e, data) {\n var hasMembers = Object.keys(viewState.members).length > 1;\n\n if (!isResetting && !isLoadingMoreMessages && !hasLoadedAllMessages() && hasMembers) {\n isLoadingMoreMessages = true;\n var newState = StateManager.setLoadingMessages(viewState, true);\n render(newState);\n\n loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, [])\n .then(function() {\n isLoadingMoreMessages = false;\n setMessagesOffset(getMessagesOffset() + LOAD_MESSAGE_LIMIT);\n return;\n })\n .catch(function(error) {\n isLoadingMoreMessages = false;\n Notification.exception(error);\n });\n }\n\n data.originalEvent.preventDefault();\n });\n\n headerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n header.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n bodyActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n body.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footerActivateHandlers.forEach(function(handler) {\n var selector = handler[0];\n var handlerFunction = handler[1];\n footer.on(CustomEvents.events.activate, selector, handlerFunction);\n });\n\n footer.on(CustomEvents.events.enter, SELECTORS.MESSAGE_TEXT_AREA, function(e, data) {\n var enterToSend = footer.attr('data-enter-to-send');\n if (enterToSend && enterToSend != 'false' && enterToSend != '0') {\n handleSendMessage(e, data);\n }\n });\n\n footer.on(CustomEvents.events.escape, SELECTORS.EMOJI_PICKER_CONTAINER, handleToggleEmojiPicker);\n $(document.body).on('click', handleCloseEmojiPicker);\n\n PubSub.subscribe(MessageDrawerEvents.ROUTE_CHANGED, function(newRouteData) {\n if (newMessagesPollTimer) {\n if (newRouteData.route != MessageDrawerRoutes.VIEW_CONVERSATION) {\n newMessagesPollTimer.stop();\n }\n }\n });\n };\n\n /**\n * Reset the timer that polls for new messages.\n *\n * @param {Number} conversationId The conversation id\n */\n var resetMessagePollTimer = function(conversationId) {\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n\n newMessagesPollTimer = new BackOffTimer(\n getLoadNewMessagesCallback(conversationId, NEWEST_FIRST),\n BackOffTimer.getIncrementalCallback(\n viewState.messagePollMin * MILLISECONDS_IN_SEC,\n MILLISECONDS_IN_SEC,\n viewState.messagePollMax * MILLISECONDS_IN_SEC,\n viewState.messagePollAfterMax * MILLISECONDS_IN_SEC\n )\n );\n\n newMessagesPollTimer.start();\n };\n\n /**\n * Reset the state to the initial state and render the UI.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number|null} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n */\n var resetState = function(body, conversationId, loggedInUserProfile) {\n // Reset all of the states back to the beginning if we're loading a new\n // conversation.\n if (newMessagesPollTimer) {\n newMessagesPollTimer.stop();\n }\n loadedAllMessages = false;\n messagesOffset = 0;\n newMessagesPollTimer = null;\n isRendering = false;\n renderBuffer = [];\n isResetting = true;\n isSendingMessage = false;\n isDeletingConversationContent = false;\n sendMessageBuffer = [];\n\n var loggedInUserId = loggedInUserProfile.id;\n var midnight = parseInt(body.attr('data-midnight'), 10);\n var messagePollMin = parseInt(body.attr('data-message-poll-min'), 10);\n var messagePollMax = parseInt(body.attr('data-message-poll-max'), 10);\n var messagePollAfterMax = parseInt(body.attr('data-message-poll-after-max'), 10);\n var initialState = StateManager.buildInitialState(\n midnight,\n loggedInUserId,\n conversationId,\n messagePollMin,\n messagePollMax,\n messagePollAfterMax\n );\n\n if (!viewState) {\n viewState = initialState;\n }\n\n render(initialState);\n };\n\n /**\n * Load a new empty private conversation between two users or self-conversation.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @param {Int} otherUserId The other user's id.\n * @return {Promise} Renderer promise.\n */\n var resetNoConversation = function(body, loggedInUserProfile, otherUserId) {\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, null, loggedInUserProfile);\n\n var resetNoConversationPromise = null;\n\n if (loggedInUserProfile.id != otherUserId) {\n // Private conversation between two different users.\n resetNoConversationPromise = Repository.getConversationBetweenUsers(\n loggedInUserProfile.id,\n otherUserId,\n true,\n true,\n 0,\n 0,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n } else {\n // Self conversation.\n resetNoConversationPromise = Repository.getSelfConversation(\n loggedInUserProfile.id,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return resetNoConversationPromise.then(function(conversation) {\n // Looks like we have a conversation after all! Let's use that.\n return resetByConversation(body, conversation, loggedInUserProfile);\n })\n .catch(function() {\n // Can't find a conversation. Oh well. Just load up a blank one.\n return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Number} conversationId The conversation id.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetById = function(body, conversationId, loggedInUserProfile) {\n var cache = null;\n if (conversationId in stateCache) {\n cache = stateCache[conversationId];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversationId, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadNewConversation(\n conversationId,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n 0,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversationId);\n });\n };\n\n /**\n * Load new messages into the conversation based on a time interval.\n *\n * @param {Object} body Conversation body container element.\n * @param {Object} conversation The conversation.\n * @param {Object} loggedInUserProfile The logged in user's profile.\n * @return {Promise} Renderer promise.\n */\n var resetByConversation = function(body, conversation, loggedInUserProfile) {\n var cache = null;\n if (conversation.id in stateCache) {\n cache = stateCache[conversation.id];\n }\n\n // Always reset the state back to the initial state so that the\n // state manager and patcher can work correctly.\n resetState(body, conversation.id, loggedInUserProfile);\n\n var promise = $.Deferred().resolve({}).promise();\n if (cache) {\n // We've seen this conversation before so there is no need to\n // send any network requests.\n var newState = cache.state;\n // Reset some loading states just in case they were left weirdly.\n newState = StateManager.setLoadingMessages(newState, false);\n newState = StateManager.setLoadingMembers(newState, false);\n setMessagesOffset(cache.messagesOffset);\n setLoadedAllMessages(cache.loadedAllMessages);\n render(newState);\n } else {\n promise = loadExistingConversation(\n conversation,\n loggedInUserProfile,\n LOAD_MESSAGE_LIMIT,\n NEWEST_FIRST\n );\n }\n\n return promise.then(function() {\n return resetMessagePollTimer(conversation.id);\n });\n };\n\n /**\n * Setup the conversation page. This is a rather complex function because there are a\n * few combinations of arguments that can be provided to this function to show the\n * conversation.\n *\n * There are:\n * 1.) A conversation object with no action or other user id (e.g. from the overview page)\n * 2.) A conversation id with no action or other user id (e.g. from the contacts page)\n * 3.) No conversation/id with an action and other other user id. (e.g. from contact page)\n *\n * @param {string} namespace The route namespace.\n * @param {Object} header Conversation header container element.\n * @param {Object} body Conversation body container element.\n * @param {Object} footer Conversation footer container element.\n * @param {Object|Number|null} conversationOrId Conversation or id or null\n * @param {String} action An action to take on the conversation\n * @param {Number} otherUserId The other user id for a private conversation\n * @return {Object} jQuery promise\n */\n var show = function(namespace, header, body, footer, conversationOrId, action, otherUserId) {\n var conversation = null;\n var conversationId = null;\n\n // Check what we were given to identify the conversation.\n if (conversationOrId && conversationOrId !== null && typeof conversationOrId == 'object') {\n conversation = conversationOrId;\n conversationId = parseInt(conversation.id, 10);\n } else {\n conversation = null;\n conversationId = parseInt(conversationOrId, 10);\n conversationId = isNaN(conversationId) ? null : conversationId;\n }\n\n if (!conversationId && action && otherUserId) {\n // If we didn't get a conversation id got a user id then let's see if we've\n // previously loaded a private conversation with this user.\n conversationId = getCachedPrivateConversationIdFromUserId(otherUserId);\n }\n\n // 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 03854ff9bc256..34d00fb7b9ac6 100644 --- a/message/amd/src/message_drawer_view_conversation.js +++ b/message/amd/src/message_drawer_view_conversation.js @@ -1282,6 +1282,32 @@ function( }); }; + /** + * Create a plain version of an HTML text. + * + * This texts is used as a message preview while is sent to the server. This way + * it is possible to prevent self-xss. + * + * @param {String} text Text to send. + * @return {String} The plain text version of the text. + */ + const previewText = function(text) { + // Remove all script and styles from text (we don't want it there). + let plaintext = text.replace(//gi, ''); + plaintext = plaintext.replace(//gi, ''); + // Beautify a bit the output adding some line breaks. + plaintext = plaintext.replace(/<\/div>/ig, '\n'); + plaintext = plaintext.replace(/<\/li>/ig, '\n'); + plaintext = plaintext.replace(/
  • /ig, ' * '); + plaintext = plaintext.replace(/<\/ul>/ig, '\n'); + plaintext = plaintext.replace(/<\/p>/ig, '\n'); + plaintext = plaintext.replace(/]*>/gi, '\n'); + // Remove all remaining tags and convert line breaks into html. + plaintext = plaintext.replace(/<[^>]+>/ig, ''); + plaintext = plaintext.replace(/\n+/ig, '\n'); + return plaintext.replace(/\n/ig, '
    '); + }; + /** * Buffers messages to be sent to the server. We use a buffer here to allow the * user to freely input messages without blocking the interface for them. @@ -1292,14 +1318,22 @@ function( */ var sendMessage = function(text) { var id = 'temp' + Date.now(); + // Render a preview version of the message while sending. + let loadingmessage = { + id: id, + useridfrom: viewState.loggedInUserId, + text: previewText(text), + timecreated: null + }; + var newState = StateManager.addMessages(viewState, [loadingmessage]); + render(newState); + // Send the real message. var message = { id: id, useridfrom: viewState.loggedInUserId, text: text, timecreated: null }; - var newState = StateManager.addMessages(viewState, [message]); - render(newState); sendMessageBuffer.push(message); processSendMessageBuffer(); };