fed38be840764cb930a398691d60f9f974e18131
[moodle.git] / message / amd / src / message_drawer_view_overview_section.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Controls a section of the overview page in the message drawer.
18  *
19  * @module     core_message/message_drawer_view_overview_section
20  * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
23 define(
24 [
25     'jquery',
26     'core/custom_interaction_events',
27     'core/notification',
28     'core/pubsub',
29     'core/str',
30     'core/pending',
31     'core/templates',
32     'core/user_date',
33     'core_message/message_repository',
34     'core_message/message_drawer_events',
35     'core_message/message_drawer_router',
36     'core_message/message_drawer_routes',
37     'core_message/message_drawer_lazy_load_list',
38     'core_message/message_drawer_view_conversation_constants'
39 ],
40 function(
41     $,
42     CustomEvents,
43     Notification,
44     PubSub,
45     Str,
46     Pending,
47     Templates,
48     UserDate,
49     MessageRepository,
50     MessageDrawerEvents,
51     MessageDrawerRouter,
52     MessageDrawerRoutes,
53     LazyLoadList,
54     MessageDrawerViewConversationContants
55 ) {
57     var SELECTORS = {
58         TOGGLE: '[data-region="toggle"]',
59         CONVERSATION: '[data-conversation-id]',
60         BLOCKED_ICON_CONTAINER: '[data-region="contact-icon-blocked"]',
61         LAST_MESSAGE: '[data-region="last-message"]',
62         LAST_MESSAGE_DATE: '[data-region="last-message-date"]',
63         MUTED_ICON_CONTAINER: '[data-region="muted-icon-container"]',
64         UNREAD_COUNT: '[data-region="unread-count"]',
65         SECTION_TOTAL_COUNT: '[data-region="section-total-count"]',
66         SECTION_TOTAL_COUNT_CONTAINER: '[data-region="section-total-count-container"]',
67         SECTION_UNREAD_COUNT: '[data-region="section-unread-count"]',
68         PLACEHOLDER_CONTAINER: '[data-region="placeholder-container"]'
69     };
71     var TEMPLATES = {
72         CONVERSATIONS_LIST: 'core_message/message_drawer_conversations_list',
73         CONVERSATIONS_LIST_ITEMS_PLACEHOLDER: 'core_message/message_drawer_conversations_list_items_placeholder'
74     };
76     var LOAD_LIMIT = 50;
77     var loadedConversationsById = {};
78     var loadedTotalCounts = false;
79     var loadedUnreadCounts = false;
81     /**
82      * Get the section visibility status.
83      *
84      * @param  {Object} root The section container element.
85      * @return {Bool} Is section visible.
86      */
87     var isVisible = function(root) {
88         return LazyLoadList.getRoot(root).hasClass('show');
89     };
91     /**
92      * Set this section as expanded.
93      *
94      * @param  {Object} root The section container element.
95      */
96     var setExpanded = function(root) {
97         root.addClass('expanded');
98     };
100     /**
101      * Set this section as collapsed.
102      *
103      * @param  {Object} root The section container element.
104      */
105     var setCollapsed = function(root) {
106         root.removeClass('expanded');
107     };
109     /**
110      * Render the total count value and show it for the user. Also update the placeholder
111      * HTML for better visuals.
112      *
113      * @param {Object} root The section container element.
114      * @param {Number} count The total count
115      */
116     var renderTotalCount = function(root, count) {
117         var container = root.find(SELECTORS.SECTION_TOTAL_COUNT_CONTAINER);
118         var countElement = container.find(SELECTORS.SECTION_TOTAL_COUNT);
119         countElement.text(count);
120         container.removeClass('hidden');
121         Str.get_string('totalconversations', 'core_message', count).done(function(string) {
122             container.attr('aria-label', string);
123         });
125         var numPlaceholders = count > 20 ? 20 : count;
126         // Array of "true" up to the number of placeholders we want.
127         var placeholders = Array.apply(null, Array(numPlaceholders)).map(function() {
128             return true;
129         });
131         // Replace the current placeholder (loading spinner) with some nicer placeholders that
132         // better represent the content.
133         Templates.render(TEMPLATES.CONVERSATIONS_LIST_ITEMS_PLACEHOLDER, {placeholders: placeholders})
134             .then(function(html) {
135                 var placeholderContainer = root.find(SELECTORS.PLACEHOLDER_CONTAINER);
136                 placeholderContainer.html(html);
137                 return;
138             })
139             .catch(function() {
140                 // Silently ignore. Doesn't matter if we can't render the placeholders.
141             });
142     };
144     /**
145      * Render the unread count value and show it for the user if it's higher than zero.
146      *
147      * @param {Object} root The section container element.
148      * @param {Number} count The unread count
149      */
150     var renderUnreadCount = function(root, count) {
151         var countElement = root.find(SELECTORS.SECTION_UNREAD_COUNT);
152         countElement.text(count);
154         Str.get_string('unreadconversations', 'core_message', count).done(function(string) {
155             countElement.attr('aria-label', string);
156         });
158         if (count > 0) {
159             countElement.removeClass('hidden');
160         }
161     };
163     /**
164      * Create a formatted conversation object from the the one we get from events. The new object
165      * will be in a format that matches what we receive from the server.
166      *
167      * @param {Object} conversation
168      * @return {Object} formatted conversation.
169      */
170     var formatConversationFromEvent = function(conversation) {
171         // Recursively lowercase all of the keys for an object.
172         var recursivelyLowercaseKeys = function(object) {
173             return Object.keys(object).reduce(function(carry, key) {
174                 if ($.isArray(object[key])) {
175                     carry[key.toLowerCase()] = object[key].map(recursivelyLowercaseKeys);
176                 } else {
177                     carry[key.toLowerCase()] = object[key];
178                 }
180                 return carry;
181             }, {});
182         };
184         // Recursively lowercase all of the keys for the conversation.
185         var formatted = recursivelyLowercaseKeys(conversation);
187         // Make sure all messages have the useridfrom property set.
188         formatted.messages = formatted.messages.map(function(message) {
189             message.useridfrom = message.userfrom.id;
190             return message;
191         });
193         return formatted;
194     };
196     /**
197      * Render the messages in the overview page.
198      *
199      * @param {Object} contentContainer Conversations content container.
200      * @param {Array} conversations List of conversations to render.
201      * @param {Number} userId Logged in user id.
202      * @return {Object} jQuery promise.
203      */
204     var render = function(conversations, userId) {
206         // Helper to format the last message for rendering.
207         // Returns a promise which resolves to either a string, or null
208         // (such as in the event of an empty personal space).
209         var pending = new Pending();
211         var formatMessagePreview = async function(lastMessage) {
212             if (!lastMessage) {
213                 return null;
214             }
215             // Check the message html for a src attribute, indicative of media.
216             // Replace <img with <noimg to stop browsers pre-fetching the image as part of tmp element creation.
217             var tmpElement = document.createElement("element");
218             tmpElement.innerHTML = lastMessage.text.replace(/<img /g, '<noimg ');
219             var isMedia = tmpElement.querySelector("[src]") || false;
221             if (!isMedia) {
222                 // Try to get the text value of the content.
223                 // If that's not possible, we'll report it under the catch-all 'other media'.
224                 var messagePreview = $(lastMessage.text).text();
225                 if (messagePreview) {
226                     // The text value of the message must have no html/script tags.
227                     if (messagePreview.indexOf('<') == -1) {
228                         return messagePreview;
229                     }
230                 }
231             }
233             // As a fallback, report unknowns as 'other media' type content.
234             var pix = 'i/messagecontentmultimediageneral';
235             var label = 'messagecontentmultimediageneral';
237             if (lastMessage.text.includes('<img')) {
238                 pix = 'i/messagecontentimage';
239                 label = 'messagecontentimage';
240             } else if (lastMessage.text.includes('<video')) {
241                 pix = 'i/messagecontentvideo';
242                 label = 'messagecontentvideo';
243             } else if (lastMessage.text.includes('<audio')) {
244                 pix = 'i/messagecontentaudio';
245                 label = 'messagecontentaudio';
246             }
248             try {
249                 var labelString = await Str.get_string(label, 'core_message');
250                 var icon = await Templates.renderPix(pix, 'core', labelString);
251                 return icon + ' ' + labelString;
252             } catch (error) {
253                 Notification.exception(error);
254                 return null;
255             }
256         };
258         var mapPromises = conversations.map(function(conversation) {
260             var lastMessage = conversation.messages.length ? conversation.messages[conversation.messages.length - 1] : null;
262             return formatMessagePreview(lastMessage)
263                 .then(function(messagePreview) {
264                     var formattedConversation = {
265                         id: conversation.id,
266                         imageurl: conversation.imageurl,
267                         name: conversation.name,
268                         subname: conversation.subname,
269                         unreadcount: conversation.unreadcount,
270                         ismuted: conversation.ismuted,
271                         lastmessagedate: lastMessage ? lastMessage.timecreated : null,
272                         sentfromcurrentuser: lastMessage ? lastMessage.useridfrom == userId : null,
273                         lastmessage: messagePreview
274                     };
276                     var otherUser = null;
277                     if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF) {
278                         // Self-conversations have only one member.
279                         otherUser = conversation.members[0];
280                     } else if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PRIVATE) {
281                         // For private conversations, remove the current userId from the members to get the other user.
282                         otherUser = conversation.members.reduce(function(carry, member) {
283                             if (!carry && member.id != userId) {
284                                 carry = member;
285                             }
286                             return carry;
287                         }, null);
288                     }
290                     if (otherUser !== null) {
291                         formattedConversation.userid = otherUser.id;
292                         formattedConversation.showonlinestatus = otherUser.showonlinestatus;
293                         formattedConversation.isonline = otherUser.isonline;
294                         formattedConversation.isblocked = otherUser.isblocked;
295                     }
297                     if (conversation.type == MessageDrawerViewConversationContants.CONVERSATION_TYPES.PUBLIC) {
298                         formattedConversation.lastsendername = conversation.members.reduce(function(carry, member) {
299                             if (!carry && lastMessage && member.id == lastMessage.useridfrom) {
300                                 carry = member.fullname;
301                             }
302                             return carry;
303                         }, null);
304                     }
306                     return formattedConversation;
307                 }).catch(Notification.exception);
308         });
310         return Promise.all(mapPromises)
311             .then(function(formattedConversations) {
312                 formattedConversations.forEach(function(conversation) {
313                     if (new Date().toDateString() == new Date(conversation.lastmessagedate * 1000).toDateString()) {
314                         conversation.istoday = true;
315                     }
316                 });
318                 return Templates.render(TEMPLATES.CONVERSATIONS_LIST, {conversations: formattedConversations});
319             }).then(function(html, js) {
320                 pending.resolve();
321                 return $.Deferred().resolve(html, js);
322             }).catch(function(error) {
323                 pending.resolve();
324                 Notification.exception(error);
325             });
326     };
328     /**
329      * Build the callback to load conversations.
330      *
331      * @param  {Array|null} types The conversation types for this section.
332      * @param  {bool} includeFavourites Include/exclude favourites.
333      * @param  {Number} offset Result offset
334      * @return {Function}
335      */
336     var getLoadCallback = function(types, includeFavourites, offset) {
337         // Note: This function is a bit messy because we've added the concept of loading
338         // multiple conversations types (e.g. private + self) at once but haven't properly
339         // updated the web service to accept an array of types. Instead we've added a new
340         // parameter for the self type which means we can only ever load self + other type.
341         // This should be improved to make it more extensible in the future. Adding new params
342         // for each type isn't very scalable.
343         var type = null;
344         // Include self conversations in the results by default.
345         var includeSelfConversations = true;
346         if (types && types.length) {
347             // Just get the conversation types that aren't "self" for now.
348             var nonSelfConversationTypes = types.filter(function(candidate) {
349                 return candidate != MessageDrawerViewConversationContants.CONVERSATION_TYPES.SELF;
350             });
351             // If we're specifically asking for a list of types that doesn't include the self
352             // conversations then we don't need to include them.
353             includeSelfConversations = types.length != nonSelfConversationTypes.length;
354             // As mentioned above the webservice is currently limited to loading one type at a
355             // time (plus self conversations) so let's hope we never change this.
356             type = nonSelfConversationTypes[0];
357         }
359         return function(root, userId) {
360             return MessageRepository.getConversations(
361                     userId,
362                     type,
363                     LOAD_LIMIT + 1,
364                     offset,
365                     includeFavourites,
366                     includeSelfConversations
367                 )
368                 .then(function(response) {
369                     var conversations = response.conversations;
371                     if (conversations.length > LOAD_LIMIT) {
372                         conversations = conversations.slice(0, -1);
373                     } else {
374                         LazyLoadList.setLoadedAll(root, true);
375                     }
377                     offset = offset + LOAD_LIMIT;
379                     conversations.forEach(function(conversation) {
380                         loadedConversationsById[conversation.id] = conversation;
381                     });
383                     return conversations;
384                 })
385                 .catch(Notification.exception);
386         };
387     };
389     /**
390      * Get the total count container element.
391      *
392      * @param  {Object} root Overview messages container element.
393      * @return {Object} Total count container element.
394      */
395     var getTotalConversationCountElement = function(root) {
396         return root.find(SELECTORS.SECTION_TOTAL_COUNT);
397     };
399     /**
400      * Get the unread conversations count container element.
401      *
402      * @param  {Object} root Overview messages container element.
403      * @return {Object} Unread conversations count container element.
404      */
405     var getTotalUnreadConversationCountElement = function(root) {
406         return root.find(SELECTORS.SECTION_UNREAD_COUNT);
407     };
409     /**
410      * Increment the total conversations count.
411      *
412      * @param  {Object} root Overview messages container element.
413      */
414     var incrementTotalConversationCount = function(root) {
415         if (loadedTotalCounts) {
416             var element = getTotalConversationCountElement(root);
417             var count = parseInt(element.text());
418             count = count + 1;
419             element.text(count);
420         }
421     };
423     /**
424      * Decrement the total conversations count.
425      *
426      * @param  {Object} root Overview messages container element.
427      */
428     var decrementTotalConversationCount = function(root) {
429         if (loadedTotalCounts) {
430             var element = getTotalConversationCountElement(root);
431             var count = parseInt(element.text());
432             count = count - 1;
433             element.text(count);
434         }
435     };
437     /**
438      * Decrement the total unread conversations count.
439      *
440      * @param  {Object} root Overview messages container element.
441      */
442     var decrementTotalUnreadConversationCount = function(root) {
443         if (loadedUnreadCounts) {
444             var element = getTotalUnreadConversationCountElement(root);
445             var count = parseInt(element.text());
446             count = count - 1;
447             element.text(count);
449             if (count < 1) {
450                 element.addClass('hidden');
451             }
452         }
453     };
455     /**
456      * Get a contact / conversation element.
457      *
458      * @param  {Object} root Overview messages container element.
459      * @param  {Number} conversationId The conversation id.
460      * @return {Object} Conversation element.
461      */
462     var getConversationElement = function(root, conversationId) {
463         return root.find('[data-conversation-id="' + conversationId + '"]');
464     };
466     /**
467      * Get a contact / conversation element from a user id.
468      *
469      * @param  {Object} root Overview messages container element.
470      * @param  {Number} userId The user id.
471      * @return {Object} Conversation element.
472      */
473     var getConversationElementFromUserId = function(root, userId) {
474         return root.find('[data-user-id="' + userId + '"]');
475     };
477     /**
478      * Show the conversation is muted icon.
479      *
480      * @param  {Object} conversationElement The conversation element.
481      */
482     var muteConversation = function(conversationElement) {
483         conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).removeClass('hidden');
484     };
486     /**
487      * Hide the conversation is muted icon.
488      *
489      * @param  {Object} conversationElement The conversation element.
490      */
491     var unmuteConversation = function(conversationElement) {
492         conversationElement.find(SELECTORS.MUTED_ICON_CONTAINER).addClass('hidden');
493     };
495     /**
496      * Show the contact is blocked icon.
497      *
498      * @param  {Object} conversationElement The conversation element.
499      */
500     var blockContact = function(conversationElement) {
501         conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).removeClass('hidden');
502     };
504     /**
505      * Hide the contact is blocked icon.
506      *
507      * @param  {Object} conversationElement The conversation element.
508      */
509     var unblockContact = function(conversationElement) {
510         conversationElement.find(SELECTORS.BLOCKED_ICON_CONTAINER).addClass('hidden');
511     };
513     /**
514      * Create an render new conversation element in the list of conversations.
515      *
516      * @param  {Object} root Overview messages container element.
517      * @param  {Object} conversation The conversation.
518      * @param  {Number} userId The logged in user id.
519      * @return {Object} jQuery promise
520      */
521     var createNewConversationFromEvent = function(root, conversation, userId) {
522         var existingConversations = root.find(SELECTORS.CONVERSATION);
524         if (!existingConversations.length) {
525             // If we didn't have any conversations then we need to show
526             // the content of the list and hide the empty message.
527             var listRoot = LazyLoadList.getRoot(root);
528             LazyLoadList.showContent(listRoot);
529             LazyLoadList.hideEmptyMessage(listRoot);
530         }
532         // Cache the conversation.
533         loadedConversationsById[conversation.id] = conversation;
535         return render([conversation], userId)
536             .then(function(html) {
537                 var contentContainer = LazyLoadList.getContentContainer(root);
538                 return contentContainer.prepend(html);
539             })
540             .then(function() {
541                 return incrementTotalConversationCount(root);
542             })
543             .catch(Notification.exception);
544     };
546     /**
547      * Delete a conversation from the list of conversations.
548      *
549      * @param  {Object} root Overview messages container element.
550      * @param  {Object} conversationElement The conversation element.
551      */
552     var deleteConversation = function(root, conversationElement) {
553         conversationElement.remove();
554         decrementTotalConversationCount(root);
556         var conversations = root.find(SELECTORS.CONVERSATION);
557         if (!conversations.length) {
558             // If we don't have any conversations then we need to hide
559             // the content of the list and show the empty message.
560             var listRoot = LazyLoadList.getRoot(root);
561             LazyLoadList.hideContent(listRoot);
562             LazyLoadList.showEmptyMessage(listRoot);
563         }
564     };
566     /**
567      * Mark a conversation as read.
568      *
569      * @param  {Object} root Overview messages container element.
570      * @param  {Object} conversationElement The conversation element.
571      */
572     var markConversationAsRead = function(root, conversationElement) {
573         var unreadCount = conversationElement.find(SELECTORS.UNREAD_COUNT);
574         unreadCount.text('0');
575         unreadCount.addClass('hidden');
576         decrementTotalUnreadConversationCount(root);
577     };
579     /**
580      * Listen to, and handle events in this section.
581      *
582      * @param {String} namespace Unique identifier for the Routes
583      * @param {Object} root The section container element.
584      * @param {Function} loadCallback The callback to load items.
585      * @param {Array|null} types The conversation types for this section
586      * @param {bool} includeFavourites If this section includes favourites
587      * @param {String} fromPanel Routing argument to send if the section is loaded in message index left panel.
588      */
589     var registerEventListeners = function(namespace, root, loadCallback, types, includeFavourites, fromPanel) {
590         var listRoot = LazyLoadList.getRoot(root);
591         var conversationBelongsToThisSection = function(conversation) {
592             // Make sure the type is an int so that the index of check matches correctly.
593             var conversationType = parseInt(conversation.type, 10);
594             if (
595                 // If the conversation type isn't one this section cares about then we can ignore it.
596                 (types && types.indexOf(conversationType) < 0) ||
597                 // If this is the favourites section and the conversation isn't a favourite then ignore it.
598                 (includeFavourites && !conversation.isFavourite) ||
599                 // If this section doesn't include favourites and the conversation is a favourite then ignore it.
600                 (!includeFavourites && conversation.isFavourite)
601             ) {
602                 return false;
603             }
605             return true;
606         };
608         // Set the minimum height of the section to the height of the toggle. This
609         // smooths out the collapse animation.
610         var toggle = root.find(SELECTORS.TOGGLE);
611         root.css('min-height', toggle.outerHeight());
613         root.on('show.bs.collapse', function() {
614             setExpanded(root);
615             LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {
616                 return render(conversations, userId)
617                     .then(function(html) {
618                         contentContainer.append(html);
619                         return html;
620                     })
621                     .catch(Notification.exception);
622             });
623         });
625         root.on('hidden.bs.collapse', function() {
626             setCollapsed(root);
627         });
629         PubSub.subscribe(MessageDrawerEvents.CONTACT_BLOCKED, function(userId) {
630             var conversationElement = getConversationElementFromUserId(root, userId);
631             if (conversationElement.length) {
632                 blockContact(conversationElement);
633             }
634         });
636         PubSub.subscribe(MessageDrawerEvents.CONTACT_UNBLOCKED, function(userId) {
637             var conversationElement = getConversationElementFromUserId(root, userId);
639             if (conversationElement.length) {
640                 unblockContact(conversationElement);
641             }
642         });
644         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_MUTED, function(conversation) {
645             var conversationId = conversation.id;
646             var conversationElement = getConversationElement(root, conversationId);
647             if (conversationElement.length) {
648                 muteConversation(conversationElement);
649             }
650         });
652         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_MUTED, function(conversation) {
653             var conversationId = conversation.id;
654             var conversationElement = getConversationElement(root, conversationId);
655             if (conversationElement.length) {
656                 unmuteConversation(conversationElement);
657             }
658         });
660         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_NEW_LAST_MESSAGE, function(conversation) {
661             if (!conversationBelongsToThisSection(conversation)) {
662                 return;
663             }
665             var loggedInUserId = conversation.loggedInUserId;
666             var conversationId = conversation.id;
667             var element = getConversationElement(root, conversationId);
668             conversation = formatConversationFromEvent(conversation);
669             if (element.length) {
670                 var contentContainer = LazyLoadList.getContentContainer(root);
671                 render([conversation], loggedInUserId)
672                     .then(function(html) {
673                             contentContainer.prepend(html);
674                             element.remove();
675                             return html;
676                         })
677                     .catch(Notification.exception);
678             } else {
679                 createNewConversationFromEvent(root, conversation, loggedInUserId);
680             }
681         });
683         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_DELETED, function(conversationId) {
684             var conversationElement = getConversationElement(root, conversationId);
685             delete loadedConversationsById[conversationId];
686             if (conversationElement.length) {
687                 deleteConversation(root, conversationElement);
688             }
689         });
691         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_READ, function(conversationId) {
692             var conversationElement = getConversationElement(root, conversationId);
693             if (conversationElement.length) {
694                 markConversationAsRead(root, conversationElement);
695             }
696         });
698         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_SET_FAVOURITE, function(conversation) {
699             var conversationElement = null;
700             if (conversationBelongsToThisSection(conversation)) {
701                 conversationElement = getConversationElement(root, conversation.id);
702                 if (!conversationElement.length) {
703                     createNewConversationFromEvent(
704                         root,
705                         formatConversationFromEvent(conversation),
706                         conversation.loggedInUserId
707                     );
708                 }
709             } else {
710                 conversationElement = getConversationElement(root, conversation.id);
711                 if (conversationElement.length) {
712                     deleteConversation(root, conversationElement);
713                 }
714             }
715         });
717         PubSub.subscribe(MessageDrawerEvents.CONVERSATION_UNSET_FAVOURITE, function(conversation) {
718             var conversationElement = null;
719             if (conversationBelongsToThisSection(conversation)) {
720                 conversationElement = getConversationElement(root, conversation.id);
721                 if (!conversationElement.length) {
722                     createNewConversationFromEvent(
723                         root,
724                         formatConversationFromEvent(conversation),
725                         conversation.loggedInUserId
726                     );
727                 }
728             } else {
729                 conversationElement = getConversationElement(root, conversation.id);
730                 if (conversationElement.length) {
731                     deleteConversation(root, conversationElement);
732                 }
733             }
734         });
736         CustomEvents.define(root, [CustomEvents.events.activate]);
737         root.on(CustomEvents.events.activate, SELECTORS.CONVERSATION, function(e, data) {
738             var conversationElement = $(e.target).closest(SELECTORS.CONVERSATION);
739             var conversationId = conversationElement.attr('data-conversation-id');
740             var conversation = loadedConversationsById[conversationId];
741             MessageDrawerRouter.go(namespace, MessageDrawerRoutes.VIEW_CONVERSATION, conversation, fromPanel);
743             data.originalEvent.preventDefault();
744         });
745     };
747     /**
748      * Setup the section.
749      *
750      * @param {String} namespace Unique identifier for the Routes
751      * @param {Object} header The header container element.
752      * @param {Object} body The section container element.
753      * @param {Object} footer The footer container element.
754      * @param {Array} types The conversation types that show in this section
755      * @param {bool} includeFavourites If this section includes favourites
756      * @param {Object} totalCountPromise Resolves wth the total conversations count
757      * @param {Object} unreadCountPromise Resolves wth the unread conversations count
758      * @param {bool} fromPanel shown in message app panel.
759      */
760     var show = function(namespace, header, body, footer, types, includeFavourites, totalCountPromise, unreadCountPromise,
761         fromPanel) {
762         var root = $(body);
764         if (!root.attr('data-init')) {
765             var loadCallback = getLoadCallback(types, includeFavourites, 0);
766             registerEventListeners(namespace, root, loadCallback, types, includeFavourites, fromPanel);
768             if (isVisible(root)) {
769                 setExpanded(root);
770                 var listRoot = LazyLoadList.getRoot(root);
771                 LazyLoadList.show(listRoot, loadCallback, function(contentContainer, conversations, userId) {
772                     return render(conversations, userId)
773                         .then(function(html) {
774                             contentContainer.append(html);
775                             return html;
776                         })
777                         .catch(Notification.exception);
778                 });
779             }
781             // This is given to us by the calling code because the total counts for all sections
782             // are loaded in a single ajax request rather than one request per section.
783             totalCountPromise.then(function(count) {
784                 renderTotalCount(root, count);
785                 loadedTotalCounts = true;
786                 return;
787             })
788             .catch(function() {
789                 // Silently ignore if we can't updated the counts. No need to bother the user.
790             });
792             // This is given to us by the calling code because the unread counts for all sections
793             // are loaded in a single ajax request rather than one request per section.
794             unreadCountPromise.then(function(count) {
795                 renderUnreadCount(root, count);
796                 loadedUnreadCounts = true;
797                 return;
798             })
799             .catch(function() {
800                 // Silently ignore if we can't updated the counts. No need to bother the user.
801             });
803             root.attr('data-init', true);
804         }
805     };
807     return {
808         show: show,
809         isVisible: isVisible
810     };
811 });