Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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