From ad4aad6c297be236b352fb32e618cce8829571c8 Mon Sep 17 00:00:00 2001 From: Ryan Wyllie Date: Wed, 28 Nov 2018 12:08:42 +0800 Subject: [PATCH 1/4] MDL-65060 core_message: add render buffer to conversations Change the conversation logic to render using a buffer so that we can immediately save state. The render patches are based on the state so the UI will always reach eventual consistency with the state since the patches are processed in order. --- .../message_drawer_view_conversation.min.js | 2 +- .../src/message_drawer_view_conversation.js | 498 +++++++++--------- 2 files changed, 258 insertions(+), 242 deletions(-) diff --git a/message/amd/build/message_drawer_view_conversation.min.js b/message/amd/build/message_drawer_view_conversation.min.js index 3412c9efa2f..239d1533d2f 100644 --- a/message/amd/build/message_drawer_view_conversation.min.js +++ b/message/amd/build/message_drawer_view_conversation.min.js @@ -1 +1 @@ -define(["jquery","core/auto_rows","core/backoff_timer","core/custom_interaction_events","core/notification","core/pubsub","core/str","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_view_conversation_constants","core_message/message_drawer_view_conversation_patcher","core_message/message_drawer_view_conversation_renderer","core_message/message_drawer_view_conversation_state_manager","core_message/message_drawer_router","core_message/message_drawer_routes"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var p={},q=null,r=!1,s=0,t=null,u=!0,v=!1,w=null,x=j.NEWEST_MESSAGES_FIRST,y=j.LOAD_MESSAGE_LIMIT,z=j.INITIAL_NEW_MESSAGE_POLL_TIMEOUT,A=j.SELECTORS,B=j.CONVERSATION_TYPES,C=function(){if(!q||q.type!=B.PRIVATE)return null;var a=q.loggedInUserId,b=Object.keys(q.members).filter(function(b){return a!=b});return b.length?b[0]:null},D=function(a){return Object.keys(p).reduce(function(b,c){if(!b){var d=p[c].state;d.type==B.PRIVATE&&a in d.members&&(b=d.id)}return b},null)},E=function(a){return{id:parseInt(a.attr("data-user-id"),10),fullname:null,profileimageurl:null,profileimageurlsmall:null,isonline:null,showonlinestatus:null,isblocked:null,iscontact:null,isdeleted:null,canmessage:null,requirescontact:null,contactrequests:[]}},F=function(){return s},G=function(a){s=a,p[q.id].messagesOffset=a},H=function(){return r},I=function(a){r=a,p[q.id].loadedAllMessages=a},J=function(a){return a.find(A.MESSAGES_CONTAINER)},K=function(b){return{id:b.id,name:b.name,subname:b.subname,imageUrl:b.imageUrl,isFavourite:b.isFavourite,type:b.type,totalMemberCount:b.totalMemberCount,loggedInUserId:b.loggedInUserId,messages:b.messages.map(function(b){return a.extend({},b)}),members:Object.keys(b.members).map(function(c){var d=a.extend({},b.members[c]);return d.contactrequests=b.members[c].contactrequests.map(function(b){return a.extend({},b)}),d})}},L=function(a,b){var c=a.id,d=m.setLoadingMembers(q,!0);return d=m.setLoadingMessages(d,!0),w(d).then(function(){return h.getMemberInfo(c,[b],!0,!0)}).then(function(a){if(a.length)return a[0];throw new Error("Unable to load other user profile")}).then(function(b){var c=m.addMembers(q,[b,a]);return c=m.setLoadingMembers(c,!1),c=m.setLoadingMessages(c,!1),c=m.setName(c,b.fullname),c=m.setType(c,1),c=m.setImageUrl(c,b.profileimageurl),c=m.setTotalMemberCount(c,2),w(c).then(function(){return b})})["catch"](function(a){var b=m.setLoadingMembers(q,!1);w(b),e.exception(a)})},M=function(a,b){var c=a.members.filter(function(a){return a.id!=b}),d=c.length?c[0]:null,e=a.name,f=a.imageurl;a.type==B.PRIVATE&&(e=e||d?d.fullname:"",f=f||d?d.profileimageurl:"");var g=m.addMembers(q,a.members);return g=m.setName(g,e),g=m.setSubname(g,a.subname),g=m.setType(g,a.type),g=m.setImageUrl(g,f),g=m.setTotalMemberCount(g,a.membercount),g=m.setIsFavourite(g,a.isfavourite),g=m.addMessages(g,a.messages)},N=function(a,b,c,d,f){var g=b.id,i=m.setLoadingMembers(q,!0);return i=m.setLoadingMessages(i,!0),w(i).then(function(){return h.getConversation(g,a,!0,!0,0,0,c+1,d,f)}).then(function(a){return a.messages.length>c?a.messages=a.messages.slice(1):I(!0),G(d+c),a}).then(function(a){var c=a.members.filter(function(a){return a.id==b.id});c.length<1&&(a.members=a.members.concat([b]));var d=M(a,b.id);return d=m.setLoadingMembers(d,!1),d=m.setLoadingMessages(d,!1),w(d).then(function(){return a})}).then(function(){return R(a)})["catch"](function(a){var b=m.setLoadingMembers(q,!1);b=m.setLoadingMessages(b,!1),w(b),e.exception(a)})},O=function(a,b,c,d){var f=a.members.filter(function(a){return a.id==b.id});f.length<1&&(a.members=a.members.concat([b]));var g=M(a,b.id);g=m.setLoadingMembers(g,!1),g=m.setLoadingMessages(g,!0);var h=a.messages.length;return w(g).then(function(){if(hb?a.messages=a.messages.slice(0,-1):I(!0),a):a}).then(function(a){var b=a.members.filter(function(a){return!(a.id in q.members)}),c=m.addMembers(q,b);return c=m.addMessages(c,a.messages),c=m.setLoadingMessages(c,!1),w(c).then(function(){return a})})["catch"](function(a){var b=m.setLoadingMessages(q,!1);throw w(b),a})},Q=function(b,c){return function(){var d=q.messages,e=d.length?d[d.length-1]:null;if(e&&!u&&!v){for(var g=[],h=d.length-1;h>=0;h--){var j=d[h];if(j.timeCreated!==e.timeCreated)break;g.push(j.id)}return P(b,0,0,c,g,e.timeCreated).then(function(a){if(a.messages.length){t.restart();var c=K(q);return f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,c),R(b)}return a})}return a.Deferred().resolve().promise()}},R=function(a){var b=q.loggedInUserId;return h.markAllConversationMessagesAsRead(b,a).then(function(){var b=m.markMessagesAsRead(q,q.messages);return f.publish(i.CONVERSATION_READ,a),w(b)})},S=function(a){return ea(a).then(function(){var b=m.addPendingBlockUsersById(q,[a]);return w(b)})},T=function(a){var b=m.setLoadingConfirmAction(q,!0);return w(b).then(function(){return h.blockUser(q.loggedInUserId,a)}).then(function(b){var c=m.addMembers(q,[b]);return c=m.removePendingBlockUsersById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_BLOCKED,a),w(c)})},U=function(a){return ea(a).then(function(){var b=m.addPendingUnblockUsersById(q,[a]);return w(b)})},V=function(a){var b=m.setLoadingConfirmAction(q,!0);return w(b).then(function(){return h.unblockUser(q.loggedInUserId,a)}).then(function(b){var c=m.addMembers(q,[b]);return c=m.removePendingUnblockUsersById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_UNBLOCKED,a),w(c)})},W=function(a){return ea(a).then(function(){var b=m.addPendingRemoveContactsById(q,[a]);return w(b)})},X=function(a){var b=m.setLoadingConfirmAction(q,!0);return w(b).then(function(){return h.deleteContacts(q.loggedInUserId,[a])}).then(function(b){var c=m.addMembers(q,b);return c=m.removePendingRemoveContactsById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_REMOVED,a),w(c)})},Y=function(a){return ea(a).then(function(){var b=m.addPendingAddContactsById(q,[a]);return w(b)})},Z=function(a){var b=m.setLoadingConfirmAction(q,!0);return w(b).then(function(){return h.createContactRequest(q.loggedInUserId,a)}).then(function(a){if(!a.request)throw new Error(a.warnings[0].message);return a.request}).then(function(b){var c=m.removePendingAddContactsById(q,[a]);return c=m.addContactRequests(c,[b]),c=m.setLoadingConfirmAction(c,!1),w(c)})},$=function(){var a=q.loggedInUserId,b=q.id;return h.setFavouriteConversations(a,[b]).then(function(){var a=m.setIsFavourite(q,!0);return w(a)}).then(function(){return f.publish(i.CONVERSATION_SET_FAVOURITE,K(q))})},_=function(){var a=q.loggedInUserId,b=q.id;return h.unsetFavouriteConversations(a,[b]).then(function(){var a=m.setIsFavourite(q,!1);return w(a)}).then(function(){return f.publish(i.CONVERSATION_UNSET_FAVOURITE,K(q))})},aa=function(a){var b=q.selectedMessageIds;return ea(a).then(function(){var a=m.addPendingDeleteMessagesById(q,b);return w(a)})},ba=function(){var a=q.pendingDeleteMessageIds,b=m.setLoadingConfirmAction(q,!0);return w(b).then(function(){return h.deleteMessages(q.loggedInUserId,a)}).then(function(){var b=m.removeMessagesById(q,a);b=m.removePendingDeleteMessagesById(b,a),b=m.removeSelectedMessagesById(b,a),b=m.setLoadingConfirmAction(b,!1);var c=q.messages[q.messages.length-1],d=b.messages.length?b.messages[b.messages.length-1]:null;if(d&&d.id!=c.id){var e=K(b);f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,e)}else b.messages.length||f.publish(i.CONVERSATION_DELETED,b.id);return w(b)})},ca=function(a){return ea(a).then(function(){var a=m.setPendingDeleteConversation(q,!0);return w(a)})},da=function(){var a=m.setLoadingConfirmAction(q,!0);return w(a).then(function(){return h.deleteConversation(q.loggedInUserId,q.id)}).then(function(){var a=m.removeMessages(q,q.messages);return a=m.removeSelectedMessagesById(a,q.selectedMessageIds),a=m.setPendingDeleteConversation(a,!1),a=m.setLoadingConfirmAction(a,!1),f.publish(i.CONVERSATION_DELETED,a.id),w(a)})},ea=function(a){var b=q.pendingDeleteMessageIds,c=m.removePendingAddContactsById(q,[a]);return c=m.removePendingRemoveContactsById(c,[a]),c=m.removePendingUnblockUsersById(c,[a]),c=m.removePendingBlockUsersById(c,[a]),c=m.removePendingDeleteMessagesById(c,b),c=m.setPendingDeleteConversation(c,!1),w(c)},fa=function(a){var b=q.loggedInUserId,c=q.members[a].contactrequests.filter(function(a){return a.requesteduserid==b}),d=c[0],e=m.setLoadingConfirmAction(q,!0);return w(e).then(function(){return h.acceptContactRequest(a,b)}).then(function(a){var b=m.removeContactRequests(q,[d]);return b=m.addMembers(q,[a]),b=m.setLoadingConfirmAction(b,!1),w(b)}).then(function(){f.publish(i.CONTACT_ADDED,q.members[a]),f.publish(i.CONTACT_REQUEST_ACCEPTED,d)})},ga=function(a){var b=q.loggedInUserId,c=q.members[a].contactrequests.filter(function(a){return a.requesteduserid==b}),d=c[0],e=m.setLoadingConfirmAction(q,!0);return w(e).then(function(){return h.declineContactRequest(a,b)}).then(function(a){var b=m.removeContactRequests(q,[d]);return b=m.addMembers(q,[a]),b=m.setLoadingConfirmAction(b,!1),w(b)}).then(function(){f.publish(i.CONTACT_REQUEST_DECLINED,d)})},ha=function(a,b){v=!0;var c=m.setSendingMessage(q,!0),d=null;return w(c).then(function(){if(a||q.type!=B.PRIVATE)return h.sendMessageToConversation(a,b);var c=C();return h.sendMessageToUser(c,b).then(function(a){return d=parseInt(a.conversationid,10),a})}).then(function(a){var b=m.addMessages(q,[a]);b=m.setSendingMessage(b,!1);var c=K(b);return b.id||(b=m.setId(b,d),c.id=d,xa(d),f.publish(i.CONVERSATION_CREATED,c)),w(b).then(function(){v=!1,f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,c)})})["catch"](function(a){v=!1;var b=m.setSendingMessage(q,!1);w(b),e.exception(a)})},ia=function(a){var b=q;return b=q.selectedMessageIds.indexOf(a)>-1?m.removeSelectedMessagesById(q,[a]):m.addSelectedMessagesById(q,[a]),w(b)},ja=function(){return ea(C()).then(function(){var a=m.removeSelectedMessagesById(q,q.selectedMessageIds);return w(a)})},ka=function(a,b,c){return function(d){var e=k.buildPatch(q,d);return l.render(a,b,c,e).then(function(){q=d,d.id&&(p[d.id]={state:d,messagesOffset:F(),loadedAllMessages:H()})})}},la=function(a){return function(b,c){q.loadingConfirmAction||a(C())["catch"](function(a){var b=m.setLoadingConfirmAction(q,!1);w(b),e.exception(a)}),c.originalEvent.preventDefault()}},ma=function(b,c){var d=a(b.target),e=d.closest(A.FOOTER_CONTAINER),f=e.find(A.MESSAGE_TEXT_AREA),g=f.val().trim();""!==g&&ha(q.id,g),c.originalEvent.preventDefault()},na=function(b,c){var d=window.getSelection(),f=a(b.target);if(""==d.toString()&&!f.is("a")){var g=f.closest(A.MESSAGE),h=parseInt(g.attr("data-message-id"),10);ia(h)["catch"](e.exception),c.originalEvent.preventDefault()}},oa=function(a,b){ja()["catch"](e.exception),b.originalEvent.preventDefault()},pa=function(a,b){var c=C(),d=q.members[c];n.go(o.VIEW_CONTACT,d),b.originalEvent.preventDefault()},qa=function(a,b){$()["catch"](e.exception),b.originalEvent.preventDefault()},ra=function(a,b){_()["catch"](e.exception),b.originalEvent.preventDefault()},sa=function(a,b){n.go(o.VIEW_GROUP_INFO,{id:q.id,name:q.name,subname:q.subname,imageUrl:q.imageUrl,totalMemberCount:q.totalMemberCount},q.loggedInUserId),b.originalEvent.preventDefault()},ta=[[A.ACTION_REQUEST_BLOCK,la(S)],[A.ACTION_REQUEST_UNBLOCK,la(U)],[A.ACTION_REQUEST_ADD_CONTACT,la(Y)],[A.ACTION_REQUEST_REMOVE_CONTACT,la(W)],[A.ACTION_REQUEST_DELETE_CONVERSATION,la(ca)],[A.ACTION_CANCEL_EDIT_MODE,oa],[A.ACTION_VIEW_CONTACT,pa],[A.ACTION_VIEW_GROUP_INFO,sa],[A.ACTION_CONFIRM_FAVOURITE,qa],[A.ACTION_CONFIRM_UNFAVOURITE,ra]],ua=[[A.ACTION_CANCEL_CONFIRM,la(ea)],[A.ACTION_CONFIRM_BLOCK,la(T)],[A.ACTION_CONFIRM_UNBLOCK,la(V)],[A.ACTION_CONFIRM_ADD_CONTACT,la(Z)],[A.ACTION_CONFIRM_REMOVE_CONTACT,la(X)],[A.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES,la(ba)],[A.ACTION_CONFIRM_DELETE_CONVERSATION,la(da)],[A.ACTION_REQUEST_ADD_CONTACT,la(Y)],[A.ACTION_ACCEPT_CONTACT_REQUEST,la(fa)],[A.ACTION_DECLINE_CONTACT_REQUEST,la(ga)],[A.MESSAGE,na]],va=[[A.SEND_MESSAGE_BUTTON,ma],[A.ACTION_REQUEST_DELETE_SELECTED_MESSAGES,la(aa)],[A.ACTION_REQUEST_ADD_CONTACT,la(Y)],[A.ACTION_REQUEST_UNBLOCK,la(U)]],wa=function(a,c,g){var h=!1,j=J(c);b.init(g),d.define(a,[d.events.activate]),d.define(c,[d.events.activate]),d.define(g,[d.events.activate,d.events.enter]),d.define(j,[d.events.scrollTop,d.events.scrollLock]),j.on(d.events.scrollTop,function(a,b){var c=Object.keys(q.members).length>1;if(!u&&!h&&!H()&&c){h=!0;var d=m.setLoadingMessages(q,!0);w(d).then(function(){return P(q.id,y,F(),x,[])}).then(function(){h=!1,G(F()+y)})["catch"](function(a){h=!1,e.exception(a)})}b.originalEvent.preventDefault()}),ta.forEach(function(b){var c=b[0],e=b[1];a.on(d.events.activate,c,e)}),ua.forEach(function(a){var b=a[0],e=a[1];c.on(d.events.activate,b,e)}),va.forEach(function(a){var b=a[0],c=a[1];g.on(d.events.activate,b,c)}),g.on(d.events.enter,A.MESSAGE_TEXT_AREA,function(a,b){var c=g.attr("data-enter-to-send");c&&"false"!=c&&"0"!=c&&ma(a,b)}),f.subscribe(i.ROUTE_CHANGED,function(a){t&&a.route!=o.VIEW_CONVERSATION&&t.stop()})},xa=function(a){t&&t.stop(),t=new c(Q(a,x),function(a){return a?2*a:z}),t.start()},ya=function(a,b,c){var d=c.id,e=parseInt(a.attr("data-midnight"),10),f=m.buildInitialState(e,d,b);return q||(q=f),t&&t.stop(),w(f)},za=function(a,b,c){return ya(a,null,b).then(function(){return h.getConversationBetweenUsers(b.id,c,!0,!0,0,0,y,0,x).then(function(c){return Ba(a,c,b)})["catch"](function(){return L(b,c)})})},Aa=function(a,b,c){var d=null;return b in p&&(d=p[b]),ya(a,b,c).then(function(){if(d){var a=d.state;return a=m.setLoadingMessages(a,!1),a=m.setLoadingMembers(a,!1),G(d.messagesOffset),I(d.loadedAllMessages),w(a)}return N(b,c,y,0,x)}).then(function(){return xa(b)})},Ba=function(a,b,c){var d=null;return b.id in p&&(d=p[b.id]),ya(a,b.id,c).then(function(){if(d){var a=d.state;return a=m.setLoadingMessages(a,!1),a=m.setLoadingMembers(a,!1),G(d.messagesOffset),I(d.loadedAllMessages),w(a)}return O(b,c,y,x)}).then(function(){return xa(b.id)})},Ca=function(b,c,d,f,g,h){var i=null,k=null;f&&null!==f&&"object"==typeof f?(i=f,k=parseInt(i.id,10)):(i=null,k=parseInt(f,10),k=isNaN(k)?null:k),!k&&g&&h&&(k=D(h)),c.attr("data-init")||(w=ka(b,c,d),wa(b,c,d),c.attr("data-init",!0));var l=!q||q.id!=k||h&&h!=C();if(l){u=!0;var m=null,n=E(c);return m=i?Ba(c,i,n,h):k?Aa(c,k,n,h):za(c,n,h),m.then(function(){u=!1,b.find(j.SELECTORS.CAN_RECEIVE_FOCUS).first().focus()})["catch"](function(a){u=!1,e.exception(a)})}if(xa(k),q.type==B.PRIVATE&&g){var o=C();switch(g){case"block":return S(o);case"unblock":return U(o);case"add-contact":return Y(o);case"remove-contact":return W(o)}}return a.Deferred().resolve().promise()},Da=function(){return g.get_string("messagedrawerviewconversation","core_message",q.name)};return{show:Ca,description:Da}}); \ No newline at end of file +define(["jquery","core/auto_rows","core/backoff_timer","core/custom_interaction_events","core/notification","core/pubsub","core/str","core_message/message_repository","core_message/message_drawer_events","core_message/message_drawer_view_conversation_constants","core_message/message_drawer_view_conversation_patcher","core_message/message_drawer_view_conversation_renderer","core_message/message_drawer_view_conversation_state_manager","core_message/message_drawer_router","core_message/message_drawer_routes"],function(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o){var p={},q=null,r=!1,s=0,t=null,u=!1,v=[],w=!0,x=!1,y=null,z=j.NEWEST_MESSAGES_FIRST,A=j.LOAD_MESSAGE_LIMIT,B=j.INITIAL_NEW_MESSAGE_POLL_TIMEOUT,C=j.SELECTORS,D=j.CONVERSATION_TYPES,E=function(){if(!q||q.type!=D.PRIVATE)return null;var a=q.loggedInUserId,b=Object.keys(q.members).filter(function(b){return a!=b});return b.length?b[0]:null},F=function(a){return Object.keys(p).reduce(function(b,c){if(!b){var d=p[c].state;d.type==D.PRIVATE&&a in d.members&&(b=d.id)}return b},null)},G=function(a){return{id:parseInt(a.attr("data-user-id"),10),fullname:null,profileimageurl:null,profileimageurlsmall:null,isonline:null,showonlinestatus:null,isblocked:null,iscontact:null,isdeleted:null,canmessage:null,requirescontact:null,contactrequests:[]}},H=function(){return s},I=function(a){s=a,p[q.id].messagesOffset=a},J=function(){return r},K=function(a){r=a,p[q.id].loadedAllMessages=a},L=function(a){return a.find(C.MESSAGES_CONTAINER)},M=function(b){return{id:b.id,name:b.name,subname:b.subname,imageUrl:b.imageUrl,isFavourite:b.isFavourite,type:b.type,totalMemberCount:b.totalMemberCount,loggedInUserId:b.loggedInUserId,messages:b.messages.map(function(b){return a.extend({},b)}),members:Object.keys(b.members).map(function(c){var d=a.extend({},b.members[c]);return d.contactrequests=b.members[c].contactrequests.map(function(b){return a.extend({},b)}),d})}},N=function(a,b){var c=a.id,d=m.setLoadingMembers(q,!0);return d=m.setLoadingMessages(d,!0),y(d),h.getMemberInfo(c,[b],!0,!0).then(function(a){if(a.length)return a[0];throw new Error("Unable to load other user profile")}).then(function(b){var c=m.addMembers(q,[b,a]);return c=m.setLoadingMembers(c,!1),c=m.setLoadingMessages(c,!1),c=m.setName(c,b.fullname),c=m.setType(c,1),c=m.setImageUrl(c,b.profileimageurl),c=m.setTotalMemberCount(c,2),y(c),b})["catch"](function(a){var b=m.setLoadingMembers(q,!1);y(b),e.exception(a)})},O=function(a,b){var c=a.members.filter(function(a){return a.id!=b}),d=c.length?c[0]:null,e=a.name,f=a.imageurl;a.type==D.PRIVATE&&(e=e||d?d.fullname:"",f=f||d?d.profileimageurl:"");var g=m.addMembers(q,a.members);return g=m.setName(g,e),g=m.setSubname(g,a.subname),g=m.setType(g,a.type),g=m.setImageUrl(g,f),g=m.setTotalMemberCount(g,a.membercount),g=m.setIsFavourite(g,a.isfavourite),g=m.addMessages(g,a.messages)},P=function(a,b,c,d,f){var g=b.id,i=m.setLoadingMembers(q,!0);return i=m.setLoadingMessages(i,!0),y(i),h.getConversation(g,a,!0,!0,0,0,c+1,d,f).then(function(a){return a.messages.length>c?a.messages=a.messages.slice(1):K(!0),I(d+c),a}).then(function(a){var c=a.members.filter(function(a){return a.id==b.id});c.length<1&&(a.members=a.members.concat([b]));var d=O(a,b.id);return d=m.setLoadingMembers(d,!1),d=m.setLoadingMessages(d,!1),y(d).then(function(){return a})}).then(function(){return T(a)})["catch"](function(a){var b=m.setLoadingMembers(q,!1);b=m.setLoadingMessages(b,!1),y(b),e.exception(a)})},Q=function(a,b,c,d){var f=a.members.filter(function(a){return a.id==b.id});f.length<1&&(a.members=a.members.concat([b]));var g=a.messages.length,h=g>=c,i=O(a,b.id);i=m.setLoadingMembers(i,!1),i=m.setLoadingMessages(i,!h);var j=y(i);return j.then(function(){return h?{messages:a.messages}:R(a.id,c,g,d,[])}).then(function(){var a=q.messages;return I(a.length),T(q.id),a})["catch"](e.exception)},R=function(a,b,c,d,e,f){return h.getMessages(q.loggedInUserId,a,b?b+1:b,c,d,f).then(function(a){return a.messages.length&&e.length&&(a.messages=a.messages.filter(function(a){return e.indexOf(parseInt(a.id,10))<0})),a}).then(function(a){return b?(a.messages.length>b?a.messages=a.messages.slice(0,-1):K(!0),a):a}).then(function(a){var b=a.members.filter(function(a){return!(a.id in q.members)}),c=m.addMembers(q,b);return c=m.addMessages(c,a.messages),c=m.setLoadingMessages(c,!1),y(c).then(function(){return a})})["catch"](function(a){var b=m.setLoadingMessages(q,!1);throw y(b),a})},S=function(b,c){return function(){var d=q.messages,e=d.length?d[d.length-1]:null;if(e&&!w&&!x){for(var g=[],h=d.length-1;h>=0;h--){var j=d[h];if(j.timeCreated!==e.timeCreated)break;g.push(j.id)}return R(b,0,0,c,g,e.timeCreated).then(function(a){if(a.messages.length){t.restart();var c=M(q);return f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,c),T(b)}return a})}return a.Deferred().resolve().promise()}},T=function(a){var b=q.loggedInUserId;return h.markAllConversationMessagesAsRead(b,a).then(function(){var b=m.markMessagesAsRead(q,q.messages);return f.publish(i.CONVERSATION_READ,a),y(b)})},U=function(a){ga(a);var b=m.addPendingBlockUsersById(q,[a]);y(b)},V=function(a){var b=m.setLoadingConfirmAction(q,!0);return y(b),h.blockUser(q.loggedInUserId,a).then(function(b){var c=m.addMembers(q,[b]);return c=m.removePendingBlockUsersById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_BLOCKED,a),y(c)})},W=function(a){ga(a);var b=m.addPendingUnblockUsersById(q,[a]);y(b)},X=function(a){var b=m.setLoadingConfirmAction(q,!0);return y(b),h.unblockUser(q.loggedInUserId,a).then(function(b){var c=m.addMembers(q,[b]);return c=m.removePendingUnblockUsersById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_UNBLOCKED,a),y(c)})},Y=function(a){ga(a);var b=m.addPendingRemoveContactsById(q,[a]);y(b)},Z=function(a){var b=m.setLoadingConfirmAction(q,!0);return y(b),h.deleteContacts(q.loggedInUserId,[a]).then(function(b){var c=m.addMembers(q,b);return c=m.removePendingRemoveContactsById(c,[a]),c=m.setLoadingConfirmAction(c,!1),f.publish(i.CONTACT_REMOVED,a),y(c)})},$=function(a){ga(a);var b=m.addPendingAddContactsById(q,[a]);y(b)},_=function(a){var b=m.setLoadingConfirmAction(q,!0);return y(b),h.createContactRequest(q.loggedInUserId,a).then(function(a){if(!a.request)throw new Error(a.warnings[0].message);return a.request}).then(function(b){var c=m.removePendingAddContactsById(q,[a]);return c=m.addContactRequests(c,[b]),c=m.setLoadingConfirmAction(c,!1),y(c)})},aa=function(){var a=q.loggedInUserId,b=q.id;return h.setFavouriteConversations(a,[b]).then(function(){var a=m.setIsFavourite(q,!0);return y(a)}).then(function(){return f.publish(i.CONVERSATION_SET_FAVOURITE,M(q))})},ba=function(){var a=q.loggedInUserId,b=q.id;return h.unsetFavouriteConversations(a,[b]).then(function(){var a=m.setIsFavourite(q,!1);return y(a)}).then(function(){return f.publish(i.CONVERSATION_UNSET_FAVOURITE,M(q))})},ca=function(a){var b=q.selectedMessageIds;ga(a);var c=m.addPendingDeleteMessagesById(q,b);y(c)},da=function(){var a=q.pendingDeleteMessageIds,b=m.setLoadingConfirmAction(q,!0);return y(b),h.deleteMessages(q.loggedInUserId,a).then(function(){var b=m.removeMessagesById(q,a);b=m.removePendingDeleteMessagesById(b,a),b=m.removeSelectedMessagesById(b,a),b=m.setLoadingConfirmAction(b,!1);var c=q.messages[q.messages.length-1],d=b.messages.length?b.messages[b.messages.length-1]:null;if(d&&d.id!=c.id){var e=M(b);f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,e)}else b.messages.length||f.publish(i.CONVERSATION_DELETED,b.id);return y(b)})},ea=function(a){ga(a);var b=m.setPendingDeleteConversation(q,!0);y(b)},fa=function(){var a=m.setLoadingConfirmAction(q,!0);return y(a),h.deleteConversation(q.loggedInUserId,q.id).then(function(){var a=m.removeMessages(q,q.messages);return a=m.removeSelectedMessagesById(a,q.selectedMessageIds),a=m.setPendingDeleteConversation(a,!1),a=m.setLoadingConfirmAction(a,!1),f.publish(i.CONVERSATION_DELETED,a.id),y(a)})},ga=function(a){var b=q.pendingDeleteMessageIds,c=m.removePendingAddContactsById(q,[a]);c=m.removePendingRemoveContactsById(c,[a]),c=m.removePendingUnblockUsersById(c,[a]),c=m.removePendingBlockUsersById(c,[a]),c=m.removePendingDeleteMessagesById(c,b),c=m.setPendingDeleteConversation(c,!1),y(c)},ha=function(a){var b=q.loggedInUserId,c=q.members[a].contactrequests.filter(function(a){return a.requesteduserid==b}),d=c[0],e=m.setLoadingConfirmAction(q,!0);return y(e),h.acceptContactRequest(a,b).then(function(a){var b=m.removeContactRequests(q,[d]);return b=m.addMembers(q,[a]),b=m.setLoadingConfirmAction(b,!1),y(b)}).then(function(){f.publish(i.CONTACT_ADDED,q.members[a]),f.publish(i.CONTACT_REQUEST_ACCEPTED,d)})},ia=function(a){var b=q.loggedInUserId,c=q.members[a].contactrequests.filter(function(a){return a.requesteduserid==b}),d=c[0],e=m.setLoadingConfirmAction(q,!0);return y(e),h.declineContactRequest(a,b).then(function(a){var b=m.removeContactRequests(q,[d]);return b=m.addMembers(q,[a]),b=m.setLoadingConfirmAction(b,!1),y(b)}).then(function(){f.publish(i.CONTACT_REQUEST_DECLINED,d)})},ja=function(a,b){x=!0;var c=m.setSendingMessage(q,!0),d=null;y(c);var g=null;if(a||q.type!=D.PRIVATE)g=h.sendMessageToConversation(a,b);else{var j=E();g=h.sendMessageToUser(j,b).then(function(a){return d=parseInt(a.conversationid,10),a})}g.then(function(a){var b=m.addMessages(q,[a]);b=m.setSendingMessage(b,!1);var c=M(b);b.id||(b=m.setId(b,d),c.id=d,Aa(d),f.publish(i.CONVERSATION_CREATED,c)),y(b),x=!1,f.publish(i.CONVERSATION_NEW_LAST_MESSAGE,c)})["catch"](function(a){x=!1;var b=m.setSendingMessage(q,!1);y(b),e.exception(a)})},ka=function(a){var b=q;b=q.selectedMessageIds.indexOf(a)>-1?m.removeSelectedMessagesById(q,[a]):m.addSelectedMessagesById(q,[a]),y(b)},la=function(){ga(E());var a=m.removeSelectedMessagesById(q,q.selectedMessageIds);y(a)},ma=function(a,b,c){if(!u&&v.length){u=!0;var d=v.shift();l.render(a,b,c,d.patch).then(function(){u=!1,d.deferred.resolve(!0),ma(a,b,c)})["catch"](function(a){u=!1,d.deferred.reject(a),e.exception(a)})}},na=function(b,c,d){return function(e){var f=k.buildPatch(q,e),g=a.Deferred();return Object.keys(f).length?v.push({patch:f,deferred:g}):g.resolve(!0),q=e,e.id&&(p[e.id]={state:e,messagesOffset:H(),loadedAllMessages:J()}),ma(b,c,d),g.promise()}},oa=function(a){return function(b,c){if(!q.loadingConfirmAction){a(E());var d=m.setLoadingConfirmAction(q,!1);y(d)}c.originalEvent.preventDefault()}},pa=function(b,c){var d=a(b.target),e=d.closest(C.FOOTER_CONTAINER),f=e.find(C.MESSAGE_TEXT_AREA),g=f.val().trim();""!==g&&ja(q.id,g),c.originalEvent.preventDefault()},qa=function(b,c){var d=window.getSelection(),e=a(b.target);if(""==d.toString()&&!e.is("a")){var f=e.closest(C.MESSAGE),g=parseInt(f.attr("data-message-id"),10);ka(g),c.originalEvent.preventDefault()}},ra=function(a,b){la(),b.originalEvent.preventDefault()},sa=function(a,b){var c=E(),d=q.members[c];n.go(o.VIEW_CONTACT,d),b.originalEvent.preventDefault()},ta=function(a,b){aa()["catch"](e.exception),b.originalEvent.preventDefault()},ua=function(a,b){ba()["catch"](e.exception),b.originalEvent.preventDefault()},va=function(a,b){n.go(o.VIEW_GROUP_INFO,{id:q.id,name:q.name,subname:q.subname,imageUrl:q.imageUrl,totalMemberCount:q.totalMemberCount},q.loggedInUserId),b.originalEvent.preventDefault()},wa=[[C.ACTION_REQUEST_BLOCK,oa(U)],[C.ACTION_REQUEST_UNBLOCK,oa(W)],[C.ACTION_REQUEST_ADD_CONTACT,oa($)],[C.ACTION_REQUEST_REMOVE_CONTACT,oa(Y)],[C.ACTION_REQUEST_DELETE_CONVERSATION,oa(ea)],[C.ACTION_CANCEL_EDIT_MODE,ra],[C.ACTION_VIEW_CONTACT,sa],[C.ACTION_VIEW_GROUP_INFO,va],[C.ACTION_CONFIRM_FAVOURITE,ta],[C.ACTION_CONFIRM_UNFAVOURITE,ua]],xa=[[C.ACTION_CANCEL_CONFIRM,oa(ga)],[C.ACTION_CONFIRM_BLOCK,oa(V)],[C.ACTION_CONFIRM_UNBLOCK,oa(X)],[C.ACTION_CONFIRM_ADD_CONTACT,oa(_)],[C.ACTION_CONFIRM_REMOVE_CONTACT,oa(Z)],[C.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES,oa(da)],[C.ACTION_CONFIRM_DELETE_CONVERSATION,oa(fa)],[C.ACTION_REQUEST_ADD_CONTACT,oa($)],[C.ACTION_ACCEPT_CONTACT_REQUEST,oa(ha)],[C.ACTION_DECLINE_CONTACT_REQUEST,oa(ia)],[C.MESSAGE,qa]],ya=[[C.SEND_MESSAGE_BUTTON,pa],[C.ACTION_REQUEST_DELETE_SELECTED_MESSAGES,oa(ca)],[C.ACTION_REQUEST_ADD_CONTACT,oa($)],[C.ACTION_REQUEST_UNBLOCK,oa(W)]],za=function(a,c,g){var h=!1,j=L(c);b.init(g),d.define(a,[d.events.activate]),d.define(c,[d.events.activate]),d.define(g,[d.events.activate,d.events.enter]),d.define(j,[d.events.scrollTop,d.events.scrollLock]),j.on(d.events.scrollTop,function(a,b){var c=Object.keys(q.members).length>1;if(!w&&!h&&!J()&&c){h=!0;var d=m.setLoadingMessages(q,!0);y(d),R(q.id,A,H(),z,[]).then(function(){h=!1,I(H()+A)})["catch"](function(a){h=!1,e.exception(a)})}b.originalEvent.preventDefault()}),wa.forEach(function(b){var c=b[0],e=b[1];a.on(d.events.activate,c,e)}),xa.forEach(function(a){var b=a[0],e=a[1];c.on(d.events.activate,b,e)}),ya.forEach(function(a){var b=a[0],c=a[1];g.on(d.events.activate,b,c)}),g.on(d.events.enter,C.MESSAGE_TEXT_AREA,function(a,b){var c=g.attr("data-enter-to-send");c&&"false"!=c&&"0"!=c&&pa(a,b)}),f.subscribe(i.ROUTE_CHANGED,function(a){t&&a.route!=o.VIEW_CONVERSATION&&t.stop()})},Aa=function(a){t&&t.stop(),t=new c(S(a,z),function(a){return a?2*a:B}),t.start()},Ba=function(a,b,c){w=!0,u=!1,v=[],x=!1;var d=c.id,e=parseInt(a.attr("data-midnight"),10),f=m.buildInitialState(e,d,b);q||(q=f),t&&t.stop(),y(f)},Ca=function(a,b,c){return Ba(a,null,b),h.getConversationBetweenUsers(b.id,c,!0,!0,0,0,A,0,z).then(function(c){return Ea(a,c,b)})["catch"](function(){return N(b,c)})},Da=function(b,c,d){var e=null;c in p&&(e=p[c]),Ba(b,c,d);var f=a.Deferred().resolve({}).promise();if(e){var g=e.state;g=m.setLoadingMessages(g,!1),g=m.setLoadingMembers(g,!1),I(e.messagesOffset),K(e.loadedAllMessages),y(g)}else f=P(c,d,A,0,z);return f.then(function(){return Aa(c)})},Ea=function(b,c,d){var e=null;c.id in p&&(e=p[c.id]),Ba(b,c.id,d);var f=a.Deferred().resolve({}).promise();if(e){var g=e.state;g=m.setLoadingMessages(g,!1),g=m.setLoadingMembers(g,!1),I(e.messagesOffset),K(e.loadedAllMessages),y(g)}else f=Q(c,d,A,z);return f.then(function(){return Aa(c.id)})},Fa=function(b,c,d,f,g,h){var i=null,k=null;f&&null!==f&&"object"==typeof f?(i=f,k=parseInt(i.id,10)):(i=null,k=parseInt(f,10),k=isNaN(k)?null:k),!k&&g&&h&&(k=F(h)),c.attr("data-init")||(y=na(b,c,d),za(b,c,d),c.attr("data-init",!0));var l=!q||q.id!=k||h&&h!=E();if(l){var m=null,n=G(c);return m=i?Ea(c,i,n,h):k?Da(c,k,n,h):Ca(c,n,h),m.then(function(){w=!1,b.find(j.SELECTORS.CAN_RECEIVE_FOCUS).first().focus()})["catch"](function(a){w=!1,e.exception(a)})}if(Aa(k),q.type==D.PRIVATE&&g){var o=E();switch(g){case"block":return U(o);case"unblock":return W(o);case"add-contact":return $(o);case"remove-contact":return Y(o)}}return a.Deferred().resolve().promise()},Ga=function(){return g.get_string("messagedrawerviewconversation","core_message",q.name)};return{show:Fa,description:Ga}}); \ 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 1eafb1473a7..298031cb534 100644 --- a/message/amd/src/message_drawer_view_conversation.js +++ b/message/amd/src/message_drawer_view_conversation.js @@ -97,11 +97,13 @@ function( var loadedAllMessages = false; var messagesOffset = 0; var newMessagesPollTimer = null; + var isRendering = false; + var renderBuffer = []; // If the UI is currently resetting. var isResetting = true; // If the UI is currently sending a message. var isSendingMessage = false; - // This is the render function which will be generated when this module is + // These functions which will be generated when this module is // first called. See generateRenderFunction for details. var render = null; @@ -267,10 +269,9 @@ function( var loggedInUserId = loggedInUserProfile.id; var newState = StateManager.setLoadingMembers(viewState, true); newState = StateManager.setLoadingMessages(newState, true); - return render(newState) - .then(function() { - return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true); - }) + render(newState); + + return Repository.getMemberInfo(loggedInUserId, [otherUserId], true, true) .then(function(profiles) { if (profiles.length) { return profiles[0]; @@ -286,10 +287,8 @@ function( newState = StateManager.setType(newState, 1); newState = StateManager.setImageUrl(newState, profile.profileimageurl); newState = StateManager.setTotalMemberCount(newState, 2); - return render(newState) - .then(function() { - return profile; - }); + render(newState); + return profile; }) .catch(function(error) { var newState = StateManager.setLoadingMembers(viewState, false); @@ -349,20 +348,19 @@ function( var loggedInUserId = loggedInUserProfile.id; var newState = StateManager.setLoadingMembers(viewState, true); newState = StateManager.setLoadingMessages(newState, true); - return render(newState) - .then(function() { - return Repository.getConversation( - loggedInUserId, - conversationId, - true, - true, - 0, - 0, - messageLimit + 1, - messageOffset, - newestFirst - ); - }) + render(newState); + + return Repository.getConversation( + loggedInUserId, + conversationId, + true, + true, + 0, + 0, + messageLimit + 1, + messageOffset, + newestFirst + ) .then(function(conversation) { if (conversation.messages.length > messageLimit) { conversation.messages = conversation.messages.slice(1); @@ -425,37 +423,30 @@ function( conversation.members = conversation.members.concat([loggedInUserProfile]); } + var messageCount = conversation.messages.length; + var hasLoadedEnoughMessages = messageCount >= messageLimit; var newState = updateStateFromConversation(conversation, loggedInUserProfile.id); newState = StateManager.setLoadingMembers(newState, false); - newState = StateManager.setLoadingMessages(newState, true); - var messageCount = conversation.messages.length; - return render(newState) - .then(function() { - if (messageCount < messageLimit) { + newState = StateManager.setLoadingMessages(newState, !hasLoadedEnoughMessages); + var renderPromise = render(newState); + + return renderPromise.then(function() { + if (!hasLoadedEnoughMessages) { // We haven't got enough messages so let's load some more. - return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []) - .then(function(result) { - // Give the list of messages to the next handler. - return result.messages; - }); + return loadMessages(conversation.id, messageLimit, messageCount, newestFirst, []); } else { // We've got enough messages. No need to load any more for now. - var newState = StateManager.setLoadingMessages(viewState, false); - return render(newState) - .then(function() { - // Give the list of messages to the next handler. - return conversation.messages; - }); + return {messages: conversation.messages}; } }) - .then(function(messages) { + .then(function() { + var messages = viewState.messages; // Update the offset to reflect the number of messages we've loaded. setMessagesOffset(messages.length); + markConversationAsRead(viewState.id); + return messages; }) - .then(function() { - return markConversationAsRead(conversation.id); - }) .catch(Notification.exception); }; @@ -602,14 +593,12 @@ function( * Tell the statemanager there is request to block a user and run the renderer * to show the block user dialogue. * - * @param {Number} userId User id. - * @return {Promise} Renderer promise. + * @param {Number} userId User id. */ var requestBlockUser = function(userId) { - return cancelRequest(userId).then(function() { - var newState = StateManager.addPendingBlockUsersById(viewState, [userId]); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.addPendingBlockUsersById(viewState, [userId]); + render(newState); }; /** @@ -621,10 +610,9 @@ function( */ var blockUser = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.blockUser(viewState.loggedInUserId, userId); - }) + render(newState); + + return Repository.blockUser(viewState.loggedInUserId, userId) .then(function(profile) { var newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.removePendingBlockUsersById(newState, [userId]); @@ -638,14 +626,12 @@ function( * Tell the statemanager there is a request to unblock a user and run the renderer * to show the unblock user dialogue. * - * @param {Number} userId User id of user to unblock. - * @return {Promise} Renderer promise. + * @param {Number} userId User id of user to unblock. */ var requestUnblockUser = function(userId) { - return cancelRequest(userId).then(function() { - var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.addPendingUnblockUsersById(viewState, [userId]); + render(newState); }; /** @@ -657,10 +643,9 @@ function( */ var unblockUser = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.unblockUser(viewState.loggedInUserId, userId); - }) + render(newState); + + return Repository.unblockUser(viewState.loggedInUserId, userId) .then(function(profile) { var newState = StateManager.addMembers(viewState, [profile]); newState = StateManager.removePendingUnblockUsersById(newState, [userId]); @@ -674,14 +659,12 @@ function( * Tell the statemanager there is a request to remove a user from the contact list * and run the renderer to show the remove user from contacts dialogue. * - * @param {Number} userId User id of user to remove from contacts. - * @return {Promise} Renderer promise. + * @param {Number} userId User id of user to remove from contacts. */ var requestRemoveContact = function(userId) { - return cancelRequest(userId).then(function() { - var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.addPendingRemoveContactsById(viewState, [userId]); + render(newState); }; /** @@ -693,10 +676,9 @@ function( */ var removeContact = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.deleteContacts(viewState.loggedInUserId, [userId]); - }) + render(newState); + + return Repository.deleteContacts(viewState.loggedInUserId, [userId]) .then(function(profiles) { var newState = StateManager.addMembers(viewState, profiles); newState = StateManager.removePendingRemoveContactsById(newState, [userId]); @@ -710,14 +692,12 @@ function( * Tell the statemanager there is a request to add a user to the contact list * and run the renderer to show the add user to contacts dialogue. * - * @param {Number} userId User id of user to add to contacts. - * @return {Promise} Renderer promise. + * @param {Number} userId User id of user to add to contacts. */ var requestAddContact = function(userId) { - return cancelRequest(userId).then(function() { - var newState = StateManager.addPendingAddContactsById(viewState, [userId]); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.addPendingAddContactsById(viewState, [userId]); + render(newState); }; /** @@ -729,10 +709,9 @@ function( */ var addContact = function(userId) { var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.createContactRequest(viewState.loggedInUserId, userId); - }) + render(newState); + + return Repository.createContactRequest(viewState.loggedInUserId, userId) .then(function(response) { if (!response.request) { throw new Error(response.warnings[0].message); @@ -796,15 +775,13 @@ function( * Tell the statemanager there is a request to delete the selected messages * and run the renderer to show confirm delete messages dialogue. * - * @param {Number} userId User id. - * @return {Promise} Renderer promise. + * @param {Number} userId User id. */ var requestDeleteSelectedMessages = function(userId) { var selectedMessageIds = viewState.selectedMessageIds; - return cancelRequest(userId).then(function() { - var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.addPendingDeleteMessagesById(viewState, selectedMessageIds); + render(newState); }; /** @@ -816,10 +793,9 @@ function( var deleteSelectedMessages = function() { var messageIds = viewState.pendingDeleteMessageIds; var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.deleteMessages(viewState.loggedInUserId, messageIds); - }) + render(newState); + + return Repository.deleteMessages(viewState.loggedInUserId, messageIds) .then(function() { var newState = StateManager.removeMessagesById(viewState, messageIds); newState = StateManager.removePendingDeleteMessagesById(newState, messageIds); @@ -844,14 +820,12 @@ function( * Tell the statemanager there is a request to delete a conversation * and run the renderer to show confirm delete conversation dialogue. * - * @param {Number} userId User id of other user. - * @return {Promise} Renderer promise. + * @param {Number} userId User id of other user. */ var requestDeleteConversation = function(userId) { - return cancelRequest(userId).then(function() { - var newState = StateManager.setPendingDeleteConversation(viewState, true); - return render(newState); - }); + cancelRequest(userId); + var newState = StateManager.setPendingDeleteConversation(viewState, true); + render(newState); }; /** @@ -862,10 +836,9 @@ function( */ var deleteConversation = function() { var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.deleteConversation(viewState.loggedInUserId, viewState.id); - }) + render(newState); + + return Repository.deleteConversation(viewState.loggedInUserId, viewState.id) .then(function() { var newState = StateManager.removeMessages(viewState, viewState.messages); newState = StateManager.removeSelectedMessagesById(newState, viewState.selectedMessageIds); @@ -880,7 +853,6 @@ function( * Tell the statemanager to cancel all pending actions. * * @param {Number} userId User id. - * @return {Promise} Renderer promise. */ var cancelRequest = function(userId) { var pendingDeleteMessageIds = viewState.pendingDeleteMessageIds; @@ -890,7 +862,7 @@ function( newState = StateManager.removePendingBlockUsersById(newState, [userId]); newState = StateManager.removePendingDeleteMessagesById(newState, pendingDeleteMessageIds); newState = StateManager.setPendingDeleteConversation(newState, false); - return render(newState); + render(newState); }; /** @@ -908,10 +880,9 @@ function( }); var request = requests[0]; var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.acceptContactRequest(userId, loggedInUserId); - }) + render(newState); + + return Repository.acceptContactRequest(userId, loggedInUserId) .then(function(profile) { var newState = StateManager.removeContactRequests(viewState, [request]); newState = StateManager.addMembers(viewState, [profile]); @@ -940,10 +911,9 @@ function( }); var request = requests[0]; var newState = StateManager.setLoadingConfirmAction(viewState, true); - return render(newState) - .then(function() { - return Repository.declineContactRequest(userId, loggedInUserId); - }) + render(newState); + + return Repository.declineContactRequest(userId, loggedInUserId) .then(function(profile) { var newState = StateManager.removeContactRequests(viewState, [request]); newState = StateManager.addMembers(viewState, [profile]); @@ -968,22 +938,24 @@ function( isSendingMessage = true; var newState = StateManager.setSendingMessage(viewState, true); var newConversationId = null; - return render(newState) - .then(function() { - if (!conversationId && viewState.type == CONVERSATION_TYPES.PRIVATE) { - // If it's a new private conversation then we need to use the old - // web service function to create the conversation. - var otherUserId = getOtherUserId(); - return Repository.sendMessageToUser(otherUserId, text) - .then(function(message) { - newConversationId = parseInt(message.conversationid, 10); - return message; - }); - } else { - return Repository.sendMessageToConversation(conversationId, text); - } - }) - .then(function(message) { + render(newState); + + var sendMessagePromise = null; + + if (!conversationId && viewState.type == CONVERSATION_TYPES.PRIVATE) { + // If it's a new private conversation then we need to use the old + // web service function to create the conversation. + var otherUserId = getOtherUserId(); + sendMessagePromise = Repository.sendMessageToUser(otherUserId, text) + .then(function(message) { + newConversationId = parseInt(message.conversationid, 10); + return message; + }); + } else { + sendMessagePromise = Repository.sendMessageToConversation(conversationId, text); + } + + sendMessagePromise.then(function(message) { var newState = StateManager.addMessages(viewState, [message]); newState = StateManager.setSendingMessage(newState, false); var conversation = formatConversationForEvent(newState); @@ -997,12 +969,10 @@ function( PubSub.publish(MessageDrawerEvents.CONVERSATION_CREATED, conversation); } - return render(newState) - .then(function() { - isSendingMessage = false; - PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation); - return; - }); + render(newState); + isSendingMessage = false; + PubSub.publish(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, conversation); + return; }) .catch(function(error) { isSendingMessage = false; @@ -1016,7 +986,6 @@ function( * Toggle the selected messages update the statemanager and render the result. * * @param {Number} messageId The id of the message to be toggled - * @return {Promise} Renderer promise. */ var toggleSelectMessage = function(messageId) { var newState = viewState; @@ -1027,7 +996,7 @@ function( newState = StateManager.addSelectedMessagesById(viewState, [messageId]); } - return render(newState); + render(newState); }; /** @@ -1036,10 +1005,41 @@ function( * @return {Promise} Renderer promise. */ var cancelEditMode = function() { - return cancelRequest(getOtherUserId()) + cancelRequest(getOtherUserId()); + var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds); + render(newState); + }; + + /** + * Process the patches in the render buffer one at a time in order until the + * buffer is empty. + * + * @param {Object} header The conversation header container element. + * @param {Object} body The conversation body container element. + * @param {Object} footer The conversation footer container element. + */ + var processRenderBuffer = function(header, body, footer) { + if (isRendering) { + return; + } + + if (!renderBuffer.length) { + return; + } + + isRendering = true; + var renderable = renderBuffer.shift(); + Renderer.render(header, body, footer, renderable.patch) .then(function() { - var newState = StateManager.removeSelectedMessagesById(viewState, viewState.selectedMessageIds); - return render(newState); + isRendering = false; + renderable.deferred.resolve(true); + // Keep processing the buffer until it's empty. + processRenderBuffer(header, body, footer); + }) + .catch(function(error) { + isRendering = false; + renderable.deferred.reject(error); + Notification.exception(error); }); }; @@ -1054,22 +1054,39 @@ function( var generateRenderFunction = function(header, body, footer) { return function(newState) { var patch = Patcher.buildPatch(viewState, newState); + var deferred = $.Deferred(); + + // Check if the patch has any data. Ignore empty patches. + if (Object.keys(patch).length) { + // Add the patch to the render buffer which gets processed in order. + renderBuffer.push({ + patch: patch, + deferred: deferred + }); + } else { + deferred.resolve(true); + } // This is a great place to add in some console logging if you need // to debug something. You can log the current state, the next state, // and the generated patch and see exactly what will be updated. - return Renderer.render(header, body, footer, patch) - .then(function() { - viewState = newState; - if (newState.id) { - // Only cache created conversations. - stateCache[newState.id] = { - state: newState, - messagesOffset: getMessagesOffset(), - loadedAllMessages: hasLoadedAllMessages() - }; - } - return; - }); + + // Optimistically update the state. We're going to assume that the rendering + // will always succeed. The rendering is asynchronous (annoyingly) so it's buffered + // but it'll reach eventual consistency with the current state. + viewState = newState; + if (newState.id) { + // Only cache created conversations. + stateCache[newState.id] = { + state: newState, + messagesOffset: getMessagesOffset(), + loadedAllMessages: hasLoadedAllMessages() + }; + } + + // Start processing the buffer. + processRenderBuffer(header, body, footer); + + return deferred.promise(); }; }; @@ -1082,12 +1099,9 @@ function( var generateConfirmActionHandler = function(actionCallback) { return function(e, data) { if (!viewState.loadingConfirmAction) { - actionCallback(getOtherUserId()) - .catch(function(error) { - var newState = StateManager.setLoadingConfirmAction(viewState, false); - render(newState); - Notification.exception(error); - }); + actionCallback(getOtherUserId()); + var newState = StateManager.setLoadingConfirmAction(viewState, false); + render(newState); } data.originalEvent.preventDefault(); }; @@ -1135,7 +1149,7 @@ function( var element = target.closest(SELECTORS.MESSAGE); var messageId = parseInt(element.attr('data-message-id'), 10); - toggleSelectMessage(messageId).catch(Notification.exception); + toggleSelectMessage(messageId); data.originalEvent.preventDefault(); }; @@ -1147,7 +1161,7 @@ function( * @param {Object} data Data for this event. */ var handleCancelEditMode = function(e, data) { - cancelEditMode().catch(Notification.exception); + cancelEditMode(); data.originalEvent.preventDefault(); }; @@ -1273,10 +1287,9 @@ function( if (!isResetting && !isLoadingMoreMessages && !hasLoadedAllMessages() && hasMembers) { isLoadingMoreMessages = true; var newState = StateManager.setLoadingMessages(viewState, true); - render(newState) - .then(function() { - return loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, []); - }) + render(newState); + + loadMessages(viewState.id, LOAD_MESSAGE_LIMIT, getMessagesOffset(), NEWEST_FIRST, []) .then(function() { isLoadingMoreMessages = false; setMessagesOffset(getMessagesOffset() + LOAD_MESSAGE_LIMIT); @@ -1355,9 +1368,15 @@ function( * @param {Object} body Conversation body container element. * @param {Number|null} conversationId The conversation id. * @param {Object} loggedInUserProfile The logged in user's profile. - * @return {Promise} Renderer promise. */ var resetState = function(body, conversationId, loggedInUserProfile) { + // Reset all of the states back to the beginning if we're loading a new + // conversation. + isResetting = true; + isRendering = false; + renderBuffer = []; + isSendingMessage = false; + var loggedInUserId = loggedInUserProfile.id; var midnight = parseInt(body.attr('data-midnight'), 10); var initialState = StateManager.buildInitialState(midnight, loggedInUserId, conversationId); @@ -1370,7 +1389,7 @@ function( newMessagesPollTimer.stop(); } - return render(initialState); + render(initialState); }; /** @@ -1384,27 +1403,25 @@ function( var resetNoConversation = function(body, loggedInUserProfile, otherUserId) { // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. - return resetState(body, null, loggedInUserProfile) - .then(function() { - return Repository.getConversationBetweenUsers( - loggedInUserProfile.id, - otherUserId, - true, - true, - 0, - 0, - LOAD_MESSAGE_LIMIT, - 0, - NEWEST_FIRST - ) - .then(function(conversation) { - // Looks like we have a conversation after all! Let's use that. - return resetByConversation(body, conversation, loggedInUserProfile); - }) - .catch(function() { - // Can't find a conversation. Oh well. Just load up a blank one. - return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId); - }); + resetState(body, null, loggedInUserProfile); + return Repository.getConversationBetweenUsers( + loggedInUserProfile.id, + otherUserId, + true, + true, + 0, + 0, + LOAD_MESSAGE_LIMIT, + 0, + NEWEST_FIRST + ) + .then(function(conversation) { + // Looks like we have a conversation after all! Let's use that. + return resetByConversation(body, conversation, loggedInUserProfile); + }) + .catch(function() { + // Can't find a conversation. Oh well. Just load up a blank one. + return loadEmptyPrivateConversation(loggedInUserProfile, otherUserId); }); }; @@ -1424,31 +1441,32 @@ function( // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. - return resetState(body, conversationId, loggedInUserProfile) - .then(function() { - if (cache) { - // We've seen this conversation before so there is no need to - // send any network requests. - var newState = cache.state; - // Reset some loading states just in case they were left weirdly. - newState = StateManager.setLoadingMessages(newState, false); - newState = StateManager.setLoadingMembers(newState, false); - setMessagesOffset(cache.messagesOffset); - setLoadedAllMessages(cache.loadedAllMessages); - return render(newState); - } else { - return loadNewConversation( - conversationId, - loggedInUserProfile, - LOAD_MESSAGE_LIMIT, - 0, - NEWEST_FIRST - ); - } - }) - .then(function() { - return resetMessagePollTimer(conversationId); - }); + resetState(body, conversationId, loggedInUserProfile); + + var promise = $.Deferred().resolve({}).promise(); + if (cache) { + // We've seen this conversation before so there is no need to + // send any network requests. + var newState = cache.state; + // Reset some loading states just in case they were left weirdly. + newState = StateManager.setLoadingMessages(newState, false); + newState = StateManager.setLoadingMembers(newState, false); + setMessagesOffset(cache.messagesOffset); + setLoadedAllMessages(cache.loadedAllMessages); + render(newState); + } else { + promise = loadNewConversation( + conversationId, + loggedInUserProfile, + LOAD_MESSAGE_LIMIT, + 0, + NEWEST_FIRST + ); + } + + return promise.then(function() { + return resetMessagePollTimer(conversationId); + }); }; /** @@ -1467,30 +1485,31 @@ function( // Always reset the state back to the initial state so that the // state manager and patcher can work correctly. - return resetState(body, conversation.id, loggedInUserProfile) - .then(function() { - if (cache) { - // We've seen this conversation before so there is no need to - // send any network requests. - var newState = cache.state; - // Reset some loading states just in case they were left weirdly. - newState = StateManager.setLoadingMessages(newState, false); - newState = StateManager.setLoadingMembers(newState, false); - setMessagesOffset(cache.messagesOffset); - setLoadedAllMessages(cache.loadedAllMessages); - return render(newState); - } else { - return loadExistingConversation( - conversation, - loggedInUserProfile, - LOAD_MESSAGE_LIMIT, - NEWEST_FIRST - ); - } - }) - .then(function() { - return resetMessagePollTimer(conversation.id); - }); + resetState(body, conversation.id, loggedInUserProfile); + + var promise = $.Deferred().resolve({}).promise(); + if (cache) { + // We've seen this conversation before so there is no need to + // send any network requests. + var newState = cache.state; + // Reset some loading states just in case they were left weirdly. + newState = StateManager.setLoadingMessages(newState, false); + newState = StateManager.setLoadingMembers(newState, false); + setMessagesOffset(cache.messagesOffset); + setLoadedAllMessages(cache.loadedAllMessages); + render(newState); + } else { + promise = loadExistingConversation( + conversation, + loggedInUserProfile, + LOAD_MESSAGE_LIMIT, + NEWEST_FIRST + ); + } + + return promise.then(function() { + return resetMessagePollTimer(conversation.id); + }); }; /** @@ -1532,7 +1551,7 @@ function( } if (!body.attr('data-init')) { - // Generate the render function to bind the header, body, and footer + // Generate these functions to bind the header, body, and footer // elements to it so that we don't need to pass them around this module. render = generateRenderFunction(header, body, footer); registerEventListeners(header, body, footer); @@ -1549,9 +1568,6 @@ function( // conversation with. var isNewConversation = !viewState || (viewState.id != conversationId) || (otherUserId && otherUserId != getOtherUserId()); if (isNewConversation) { - // Reset all of the states back to the beginning if we're loading a new - // conversation. - isResetting = true; var renderPromise = null; var loggedInUserProfile = getLoggedInUserProfile(body); if (conversation) { From 9772b2e138d7ed24fb0a1de3d591a07275336cb7 Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Mon, 20 May 2019 15:38:54 +0200 Subject: [PATCH 2/4] MDL-65060 core_message: Group_message_message_content selector --- lib/behat/classes/partial_named_selector.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/behat/classes/partial_named_selector.php b/lib/behat/classes/partial_named_selector.php index 6d73f4a9077..baa9d4f1a1b 100644 --- a/lib/behat/classes/partial_named_selector.php +++ b/lib/behat/classes/partial_named_selector.php @@ -93,6 +93,7 @@ class behat_partial_named_selector extends \Behat\Mink\Selector\PartialNamedSele 'group_message_header' => 'group_message_header', 'group_message_member' => 'group_message_member', 'group_message_tab' => 'group_message_tab', + 'group_message_message_content' => 'group_message_message_content', 'icon' => 'icon', 'link' => 'link', 'link_or_button' => 'link_or_button', @@ -169,6 +170,9 @@ XPATH XPATH , 'group_message_tab' => << << << Date: Mon, 20 May 2019 15:39:13 +0200 Subject: [PATCH 3/4] MDL-65060 core_message: Group message data generator --- lib/tests/behat/behat_data_generators.php | 52 ++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/tests/behat/behat_data_generators.php b/lib/tests/behat/behat_data_generators.php index 4faa1554a82..9a922b1963e 100644 --- a/lib/tests/behat/behat_data_generators.php +++ b/lib/tests/behat/behat_data_generators.php @@ -198,6 +198,11 @@ class behat_data_generators extends behat_base { 'required' => array('user', 'contact'), 'switchids' => array('user' => 'userid', 'contact' => 'contactid') ), + 'group messages' => array( + 'datagenerator' => 'group_messages', + 'required' => array('user', 'group', 'message'), + 'switchids' => array('user' => 'userid', 'group' => 'groupid') + ), ); /** @@ -887,6 +892,10 @@ class behat_data_generators extends behat_base { * @return void */ protected function process_private_messages(array $data) { + if (empty($data['format'])) { + $data['format'] = 'FORMAT_PLAIN'; + } + if (!$conversationid = \core_message\api::get_conversation_between_users([$data['userid'], $data['contactid']])) { $conversation = \core_message\api::create_conversation( \core_message\api::MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, @@ -894,7 +903,48 @@ class behat_data_generators extends behat_base { ); $conversationid = $conversation->id; } - \core_message\api::send_message_to_conversation($data['userid'], $conversationid, $data['message'], FORMAT_PLAIN); + \core_message\api::send_message_to_conversation( + $data['userid'], + $conversationid, + $data['message'], + constant($data['format']) + ); + } + + /** + * Send a new message from user to a group conversation + * + * @param array $data + * @return void + */ + protected function process_group_messages(array $data) { + global $DB; + + if (empty($data['format'])) { + $data['format'] = 'FORMAT_PLAIN'; + } + + $group = $DB->get_record('groups', ['id' => $data['groupid']]); + $coursecontext = context_course::instance($group->courseid); + if (!$conversation = \core_message\api::get_conversation_by_area('core_group', 'groups', $data['groupid'], + $coursecontext->id)) { + $members = $DB->get_records_menu('groups_members', ['groupid' => $data['groupid']], '', 'userid, id'); + $conversation = \core_message\api::create_conversation( + \core_message\api::MESSAGE_CONVERSATION_TYPE_GROUP, + array_keys($members), + $group->name, + \core_message\api::MESSAGE_CONVERSATION_ENABLED, + 'core_group', + 'groups', + $group->id, + $coursecontext->id); + } + \core_message\api::send_message_to_conversation( + $data['userid'], + $conversation->id, + $data['message'], + constant($data['format']) + ); } /** From 8f03499b61f41d02a4a8cded53fc2e8f3f69f84d Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Mon, 20 May 2019 15:39:41 +0200 Subject: [PATCH 4/4] MDL-65060 core_message: Delete messages behat test --- message/tests/behat/delete_messages.feature | 268 ++++++++++++++++++++ 1 file changed, 268 insertions(+) create mode 100644 message/tests/behat/delete_messages.feature diff --git a/message/tests/behat/delete_messages.feature b/message/tests/behat/delete_messages.feature new file mode 100644 index 00000000000..3f985ebce04 --- /dev/null +++ b/message/tests/behat/delete_messages.feature @@ -0,0 +1,268 @@ +@core @core_message @javascript +Feature: Delete messages from conversations + In order to manage a course group in a course + As a user + I need to be able to delete messages from conversations + + Background: + Given the following "courses" exist: + | fullname | shortname | category | groupmode | + | Course 1 | C1 | 0 | 1 | + And the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@example.com | + | student2 | Student | 2 | student2@example.com | + And the following "course enrolments" exist: + | user | course | role | + | student1 | C1 | student | + | student2 | C1 | student | + And the following "groups" exist: + | name | course | idnumber | enablemessaging | + | Group 1 | C1 | G1 | 1 | + And the following "group members" exist: + | user | group | + | student1 | G1 | + | student2 | G1 | + And the following "group messages" exist: + | user | group | message | + | student1 | G1 | Hi! | + | student2 | G1 | How are you? | + | student1 | G1 | Can somebody help me? | + And the following "private messages" exist: + | user | contact | message | + | student1 | student2 | Hi! | + | student2 | student1 | Hello! | + | student1 | student2 | Are you free? | + And the following config values are set as admin: + | messaging | 1 | + + Scenario: Delete a message sent by the user from a group conversation + Given I log in as "student1" + And I open messaging + And "Group 1" "group_message" should exist + And I select "Group 1" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I click on "How are you?" "group_message_message_content" + And I click on "Can somebody help me?" "group_message_message_content" + And I should see "3" in the "[data-region='message-selected-court']" "css_element" +# Clicking to unselect + And I click on "How are you?" "group_message_message_content" + And I click on "Can somebody help me?" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should see "##today##j F##" in the "Group 1" "group_message_conversation" + And I should see "How are you?" in the "Group 1" "group_message_conversation" + And I should see "Can somebody help me?" in the "Group 1" "group_message_conversation" + And I should not see "Messages selected" + + Scenario: Delete two messages from a group conversation; one sent by another user. + Given I log in as "student1" + And I open messaging + And "Group 1" "group_message" should exist + And I select "Group 1" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And I click on "How are you?" "group_message_message_content" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should see "##today##j F##" in the "Group 1" "group_message_conversation" + And I should not see "How are you?" in the "Group 1" "group_message_conversation" + And I should see "Can somebody help me?" in the "Group 1" "group_message_conversation" + And I should not see "Messages selected" +# Check messages were not deleted for other users + And I log out + And I log in as "student2" + And I open messaging + And I select "Group 1" conversation in messaging + And I should see "Hi!" + And I should see "How are you?" + And I should see "Can somebody help me?" + + Scenario: Cancel deleting two messages from a group conversation + Given I log in as "student1" + And I open messaging + And "Group 1" "group_message" should exist + And I select "Group 1" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I click on "How are you?" "group_message_message_content" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Canceling deletion, so messages should be there + And I should see "Cancel" + And I click on "//button[@data-action='cancel-confirm']" "xpath_element" + Then I should not see "Cancel" + And I should see "Hi!" + And I should see "How are you?" in the "Group 1" "group_message_conversation" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + + Scenario: Delete a message sent by the user from a private conversation + Given I log in as "student1" + And I open messaging + And I should see "Private" + And I open the "Private" conversations list + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + And I should see "Hello!" in the "Student 2" "group_message_conversation" + And I should see "Are you free?" in the "Student 2" "group_message_conversation" + And I should not see "Messages selected" + + Scenario: Delete two messages from a private conversation; one sent by another user + Given I log in as "student1" + And I open messaging + And I should see "Private" + And I open the "Private" conversations list + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And I click on "Hello!" "group_message_message_content" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should not see "Hello!" in the "Student 2" "group_message_conversation" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + And I should see "Are you free?" in the "Student 2" "group_message_conversation" + And I should not see "Messages selected" +# Check messages were not deleted for the other user + And I log out + And I log in as "student2" + And I open messaging + And I open the "Private" conversations list + And I select "Student 1" conversation in messaging + And I should see "Hi!" + And I should see "Hello!" + And I should see "Are you free?" + + Scenario: Cancel deleting two messages from a private conversation + Given I log in as "student1" + And I open messaging + And I should see "Private" + And I open the "Private" conversations list + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I click on "Hello!" "group_message_message_content" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Canceling deletion, so messages should be there + And I should see "Cancel" + And I click on "//button[@data-action='cancel-confirm']" "xpath_element" + Then I should not see "Cancel" + And I should see "Hi!" + And I should see "Hello!" in the "Student 2" "group_message_conversation" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + + Scenario: Delete a message sent by the user from a favorite conversation + Given the following "favourite conversations" exist: + | user | contact | + | student1 | student2 | + And I log in as "student1" + And I open messaging + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + And I should see "Hello!" in the "Student 2" "group_message_conversation" + And I should not see "Messages selected" + + Scenario: Delete two messages from a favourite conversation; one sent by another user + Given the following "favourite conversations" exist: + | user | contact | + | student1 | student2 | + And I log in as "student1" + And I open messaging + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I should see "1" in the "[data-region='message-selected-court']" "css_element" + And I click on "Hello!" "group_message_message_content" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Deleting, so messages should not be there + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + Then I should not see "Delete" + And I should not see "Hi!" + And I should not see "Hello!" in the "Student 2" "group_message_conversation" + And I should see "##today##j F##" in the "Student 2" "group_message_conversation" + And I should see "Are you free?" in the "Student 2" "group_message_conversation" + And I should not see "Messages selected" + + Scenario: Cancel deleting two messages from a favourite conversation + Given the following "favourite conversations" exist: + | user | contact | + | student1 | student2 | + And I log in as "student1" + And I open messaging + And I should see "Student 2" + And I select "Student 2" conversation in messaging + And I click on "Hi!" "group_message_message_content" + And I click on "Hello!" "group_message_message_content" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" +# Canceling deletion, so messages should be there + And I should see "Cancel" + And I click on "//button[@data-action='cancel-confirm']" "xpath_element" + Then I should not see "Cancel" + And I should see "Hi!" + And I should see "Hello!" in the "Student 2" "group_message_conversation" + And I should see "2" in the "[data-region='message-selected-court']" "css_element" + + Scenario: Check an empty favourite conversation is still favourite + Given the following "favourite conversations" exist: + | user | contact | + | student1 | student2 | + And I log in as "student1" + And I open messaging + And I should see "Student 2" + And I select "Student 2" conversation in the "favourites" conversations list + And I click on "Hi!" "group_message_message_content" + And I click on "Hello!" "group_message_message_content" + And I click on "Are you free?" "group_message_message_content" + And "Delete selected messages" "button" should exist + When I click on "Delete selected messages" "button" + And I should see "Delete" + And I click on "//button[@data-action='confirm-delete-selected-messages']" "xpath_element" + And I go back in "view-conversation" message drawer + Then I should not see "Student 2" in the "//*[@data-region='message-drawer']//div[@data-region='view-overview-favourites']" "xpath_element" + And I send "Hi!" message to "Student 2" user + And I go back in "view-conversation" message drawer + And I go back in "view-search" message drawer + And I open the "Starred" conversations list + And I should see "Student 2" in the "//*[@data-region='message-drawer']//div[@data-region='view-overview-favourites']" "xpath_element"