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
 * This module updates the UI for the conversation page in the message
18
 * drawer.
19
 *
20
 * The module will take a patch from the message_drawer_view_conversation_patcher
21
 * module and update the UI to reflect the changes.
22
 *
23
 * This is the only module that ever modifies the UI of the conversation page.
24
 *
25
 * @module     core_message/message_drawer_view_conversation_renderer
26
 * @copyright  2018 Ryan Wyllie <ryan@moodle.com>
27
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28
 */
29
define(
30
[
31
    'jquery',
32
    'core/notification',
33
    'core/str',
34
    'core/templates',
35
    'core/user_date',
36
    'core_message/message_drawer_view_conversation_constants',
37
    'core/aria',
38
],
39
function(
40
    $,
41
    Notification,
42
    Str,
43
    Templates,
44
    UserDate,
45
    Constants,
46
    Aria
47
) {
48
    var SELECTORS = Constants.SELECTORS;
49
    var TEMPLATES = Constants.TEMPLATES;
50
    var CONVERSATION_TYPES = Constants.CONVERSATION_TYPES;
51
 
52
    /**
53
     * Get the messages container element.
54
     *
55
     * @param  {Object} body Conversation body container element.
56
     * @return {Object} The messages container element.
57
     */
58
    var getMessagesContainer = function(body) {
59
        return body.find(SELECTORS.CONTENT_MESSAGES_CONTAINER);
60
    };
61
 
62
    /**
63
     * Show the messages container element.
64
     *
65
     * @param  {Object} body Conversation body container element.
66
     */
67
    var showMessagesContainer = function(body) {
68
        getMessagesContainer(body).removeClass('hidden');
69
    };
70
 
71
    /**
72
     * Hide the messages container element.
73
     *
74
     * @param  {Object} body Conversation body container element.
75
     */
76
    var hideMessagesContainer = function(body) {
77
        getMessagesContainer(body).addClass('hidden');
78
    };
79
 
80
    /**
81
     * Get the self-conversation message container element.
82
     *
83
     * @param  {Object} body Conversation body container element.
84
     * @return {Object} The messages container element.
85
     */
86
    var getSelfConversationMessageContainer = function(body) {
87
        return body.find(SELECTORS.SELF_CONVERSATION_MESSAGE_CONTAINER);
88
    };
89
 
90
    /**
91
     * Hide the self-conversation message container element.
92
     *
93
     * @param  {Object} body Conversation body container element.
94
     * @return {Object} The messages container element.
95
     */
96
    var hideSelfConversationMessageContainer = function(body) {
97
        return getSelfConversationMessageContainer(body).addClass('hidden');
98
    };
99
 
100
    /**
101
     * Get the contact request sent container element.
102
     *
103
     * @param  {Object} body Conversation body container element.
104
     * @return {Object} The messages container element.
105
     */
106
    var getContactRequestSentContainer = function(body) {
107
        return body.find(SELECTORS.CONTACT_REQUEST_SENT_MESSAGE_CONTAINER);
108
    };
109
 
110
    /**
111
     * Hide the contact request sent container element.
112
     *
113
     * @param  {Object} body Conversation body container element.
114
     * @return {Object} The messages container element.
115
     */
116
    var hideContactRequestSentContainer = function(body) {
117
        return getContactRequestSentContainer(body).addClass('hidden');
118
    };
119
 
120
    /**
121
     * Get the footer container element.
122
     *
123
     * @param  {Object} footer Conversation footer container element.
124
     * @return {Object} The footer container element.
125
     */
126
    var getFooterContentContainer = function(footer) {
127
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_CONTAINER);
128
    };
129
 
130
    /**
131
     * Show the footer container element.
132
     *
133
     * @param  {Object} footer Conversation footer container element.
134
     */
135
    var showFooterContent = function(footer) {
136
        getFooterContentContainer(footer).removeClass('hidden');
137
    };
138
 
139
    /**
140
     * Hide the footer container element.
141
     *
142
     * @param  {Object} footer Conversation footer container element.
143
     */
144
    var hideFooterContent = function(footer) {
145
        getFooterContentContainer(footer).addClass('hidden');
146
    };
147
 
148
    /**
149
     * Get the footer edit mode container element.
150
     *
151
     * @param  {Object} footer Conversation footer container element.
152
     * @return {Object} The footer container element.
153
     */
154
    var getFooterEditModeContainer = function(footer) {
155
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_EDIT_MODE_CONTAINER);
156
    };
157
 
158
    /**
159
     * Show the footer edit mode container element.
160
     *
161
     * @param  {Object} footer Conversation footer container element.
162
     */
163
    var showFooterEditMode = function(footer) {
164
        getFooterEditModeContainer(footer).removeClass('hidden');
165
    };
166
 
167
    /**
168
     * Hide the footer edit mode container element.
169
     *
170
     * @param  {Object} footer Conversation footer container element.
171
     */
172
    var hideFooterEditMode = function(footer) {
173
        getFooterEditModeContainer(footer).addClass('hidden');
174
    };
175
 
176
    /**
177
     * Get the footer placeholder.
178
     *
179
     * @param  {Object} footer Conversation footer container element.
180
     * @return {Object} The footer placeholder container element.
181
     */
182
    var getFooterPlaceholderContainer = function(footer) {
183
        return footer.find(SELECTORS.PLACEHOLDER_CONTAINER);
184
    };
185
 
186
    /**
187
     * Show the footer placeholder
188
     *
189
     * @param  {Object} footer Conversation footer container element.
190
     */
191
    var showFooterPlaceholder = function(footer) {
192
        getFooterPlaceholderContainer(footer).removeClass('hidden');
193
    };
194
 
195
    /**
196
     * Hide the footer placeholder
197
     *
198
     * @param  {Object} footer Conversation footer container element.
199
     */
200
    var hideFooterPlaceholder = function(footer) {
201
        getFooterPlaceholderContainer(footer).addClass('hidden');
202
    };
203
 
204
    /**
205
     * Get the footer Require add as contact container element.
206
     *
207
     * @param  {Object} footer Conversation footer container element.
208
     * @return {Object} The footer Require add as contact container element.
209
     */
210
    var getFooterRequireContactContainer = function(footer) {
211
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_CONTACT_CONTAINER);
212
    };
213
 
214
    /**
215
     * Show the footer add as contact dialogue.
216
     *
217
     * @param  {Object} footer Conversation footer container element.
218
     */
219
    var showFooterRequireContact = function(footer) {
220
        getFooterRequireContactContainer(footer).removeClass('hidden');
221
    };
222
 
223
    /**
224
     * Hide the footer add as contact dialogue.
225
     *
226
     * @param  {Object} footer Conversation footer container element.
227
     */
228
    var hideFooterRequireContact = function(footer) {
229
        getFooterRequireContactContainer(footer).addClass('hidden');
230
    };
231
 
232
    /**
233
     * Get the footer Required to unblock contact container element.
234
     *
235
     * @param  {Object} footer Conversation footer container element.
236
     * @return {Object} The footer Required to unblock contact container element.
237
     */
238
    var getFooterRequireUnblockContainer = function(footer) {
239
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_REQUIRE_UNBLOCK_CONTAINER);
240
    };
241
 
242
    /**
243
     * Show the footer Required to unblock contact container element.
244
     *
245
     * @param  {Object} footer Conversation footer container element.
246
     */
247
    var showFooterRequireUnblock = function(footer) {
248
        getFooterRequireUnblockContainer(footer).removeClass('hidden');
249
    };
250
 
251
    /**
252
     * Hide the footer Required to unblock contact container element.
253
     *
254
     * @param  {Object} footer Conversation footer container element.
255
     */
256
    var hideFooterRequireUnblock = function(footer) {
257
        getFooterRequireUnblockContainer(footer).addClass('hidden');
258
    };
259
 
260
    /**
261
     * Get the footer Unable to message contact container element.
262
     *
263
     * @param  {Object} footer Conversation footer container element.
264
     * @return {Object} The footer Unable to message contact container element.
265
     */
266
    var getFooterUnableToMessageContainer = function(footer) {
267
        return footer.find(SELECTORS.CONTENT_MESSAGES_FOOTER_UNABLE_TO_MESSAGE_CONTAINER);
268
    };
269
 
270
    /**
271
     * Show the footer Unable to message contact container element.
272
     *
273
     * @param  {Object} footer Conversation footer container element.
274
     */
275
    var showFooterUnableToMessage = function(footer) {
276
        getFooterUnableToMessageContainer(footer).removeClass('hidden');
277
    };
278
 
279
    /**
280
     * Hide the footer Unable to message contact container element.
281
     *
282
     * @param  {Object} footer Conversation footer container element.
283
     */
284
    var hideFooterUnableToMessage = function(footer) {
285
        getFooterUnableToMessageContainer(footer).addClass('hidden');
286
    };
287
 
288
    /**
289
     * Hide all header elements.
290
     *
291
     * @param  {Object} header Conversation header container element.
292
     */
293
    var hideAllHeaderElements = function(header) {
294
        hideHeaderContent(header);
295
        hideHeaderEditMode(header);
296
        hideHeaderPlaceholder(header);
297
    };
298
 
299
    /**
300
     * Hide all footer dialogues and messages.
301
     *
302
     * @param  {Object} footer Conversation footer container element.
303
     */
304
    var hideAllFooterElements = function(footer) {
305
        hideFooterContent(footer);
306
        hideFooterEditMode(footer);
307
        hideFooterPlaceholder(footer);
308
        hideFooterRequireContact(footer);
309
        hideFooterRequireUnblock(footer);
310
        hideFooterUnableToMessage(footer);
311
    };
312
 
313
    /**
314
     * Get the content placeholder container element.
315
     *
316
     * @param  {Object} body Conversation body container element.
317
     * @return {Object} The body placeholder container element.
318
     */
319
    var getContentPlaceholderContainer = function(body) {
320
        return body.find(SELECTORS.CONTENT_PLACEHOLDER_CONTAINER);
321
    };
322
 
323
    /**
324
     * Show the content placeholder.
325
     *
326
     * @param  {Object} body Conversation body container element.
327
     */
328
    var showContentPlaceholder = function(body) {
329
        getContentPlaceholderContainer(body).removeClass('hidden');
330
    };
331
 
332
    /**
333
     * Hide the content placeholder.
334
     *
335
     * @param  {Object} body Conversation body container element.
336
     */
337
    var hideContentPlaceholder = function(body) {
338
        getContentPlaceholderContainer(body).addClass('hidden');
339
    };
340
 
341
    /**
342
     * Get the header content container element.
343
     *
344
     * @param  {Object} header Conversation header container element.
345
     * @return {Object} The header content container element.
346
     */
347
    var getHeaderContent = function(header) {
348
        return header.find(SELECTORS.HEADER);
349
    };
350
 
351
    /**
352
     * Show the header content.
353
     *
354
     * @param  {Object} header Conversation header container element.
355
     */
356
    var showHeaderContent = function(header) {
357
        getHeaderContent(header).removeClass('hidden');
358
    };
359
 
360
    /**
361
     * Hide the header content.
362
     *
363
     * @param  {Object} header Conversation header container element.
364
     */
365
    var hideHeaderContent = function(header) {
366
        getHeaderContent(header).addClass('hidden');
367
    };
368
 
369
    /**
370
     * Get the header edit mode container element.
371
     *
372
     * @param  {Object} header Conversation header container element.
373
     * @return {Object} The header content container element.
374
     */
375
    var getHeaderEditMode = function(header) {
376
        return header.find(SELECTORS.HEADER_EDIT_MODE);
377
    };
378
 
379
    /**
380
     * Show the header edit mode container.
381
     *
382
     * @param  {Object} header Conversation header container element.
383
     */
384
    var showHeaderEditMode = function(header) {
385
        getHeaderEditMode(header).removeClass('hidden');
386
    };
387
 
388
    /**
389
     * Hide the header edit mode container.
390
     *
391
     * @param  {Object} header Conversation header container element.
392
     */
393
    var hideHeaderEditMode = function(header) {
394
        getHeaderEditMode(header).addClass('hidden');
395
    };
396
 
397
    /**
398
     * Get the header placeholder container element.
399
     *
400
     * @param  {Object} header Conversation header container element.
401
     * @return {Object} The header placeholder container element.
402
     */
403
    var getHeaderPlaceholderContainer = function(header) {
404
        return header.find(SELECTORS.HEADER_PLACEHOLDER_CONTAINER);
405
    };
406
 
407
    /**
408
     * Show the header placeholder.
409
     *
410
     * @param  {Object} header Conversation header container element.
411
     */
412
    var showHeaderPlaceholder = function(header) {
413
        getHeaderPlaceholderContainer(header).removeClass('hidden');
414
    };
415
 
416
    /**
417
     * Hide the header placeholder.
418
     *
419
     * @param  {Object} header Conversation header container element.
420
     */
421
    var hideHeaderPlaceholder = function(header) {
422
        getHeaderPlaceholderContainer(header).addClass('hidden');
423
    };
424
 
425
    /**
426
     * Get the emoji picker container element.
427
     *
428
     * @param  {Object} footer Conversation footer container element.
429
     * @return {Object} The emoji picker container element.
430
     */
431
    var getEmojiPickerContainer = function(footer) {
432
        return footer.find(SELECTORS.EMOJI_PICKER_CONTAINER);
433
    };
434
 
435
    /**
436
     * Get the emoji picker container element.
437
     *
438
     * @param  {Object} footer Conversation footer container element.
439
     * @return {Object} The emoji picker container element.
440
     */
441
    var getEmojiAutoCompleteContainer = function(footer) {
442
        return footer.find(SELECTORS.EMOJI_AUTO_COMPLETE_CONTAINER);
443
    };
444
 
445
    /**
446
     * Get a message element.
447
     *
448
     * @param  {Object} body Conversation body container element.
449
     * @param  {Number} messageId the Message id.
450
     * @return {Object} A message element from the conversation.
451
     */
452
    var getMessageElement = function(body, messageId) {
453
        var messagesContainer = getMessagesContainer(body);
454
        return messagesContainer.find('[data-message-id="' + messageId + '"]');
455
    };
456
 
457
    /**
458
     * Get the day container element. The day container element holds a list of messages for that day.
459
     *
460
     * @param  {Object} body Conversation body container element.
461
     * @param  {Number} dayTimeCreated Midnight timestamp for the day.
462
     * @return {Object} jQuery object
463
     */
464
    var getDayElement = function(body, dayTimeCreated) {
465
        var messagesContainer = getMessagesContainer(body);
466
        return messagesContainer.find('[data-day-id="' + dayTimeCreated + '"]');
467
    };
468
 
469
    /**
470
     * Get the more messages loading icon container element.
471
     *
472
     * @param  {Object} body Conversation body container element.
473
     * @return {Object} The more messages loading container element.
474
     */
475
    var getMoreMessagesLoadingIconContainer = function(body) {
476
        return body.find(SELECTORS.MORE_MESSAGES_LOADING_ICON_CONTAINER);
477
    };
478
 
479
    /**
480
     * Show the more messages loading icon.
481
     *
482
     * @param  {Object} body Conversation body container element.
483
     */
484
    var showMoreMessagesLoadingIcon = function(body) {
485
        getMoreMessagesLoadingIconContainer(body).removeClass('hidden');
486
    };
487
 
488
    /**
489
     * Hide the more messages loading icon.
490
     *
491
     * @param  {Object} body Conversation body container element.
492
     */
493
    var hideMoreMessagesLoadingIcon = function(body) {
494
        getMoreMessagesLoadingIconContainer(body).addClass('hidden');
495
    };
496
 
497
    /**
498
     * Get the confirm dialogue container element.
499
     *
500
     * @param  {Object} root The container element to search.
501
     * @return {Object} The confirm dialogue container element.
502
     */
503
    var getConfirmDialogueContainer = function(root) {
504
        return root.find(SELECTORS.CONFIRM_DIALOGUE_CONTAINER);
505
    };
506
 
507
    /**
508
     * Show the confirm dialogue container element.
509
     *
510
     * @param  {Object} root The container element containing a dialogue.
511
     */
512
    var showConfirmDialogueContainer = function(root) {
513
        var container = getConfirmDialogueContainer(root);
514
        var siblings = container.siblings(':not(.hidden)');
515
        Aria.hide(siblings.get());
516
        siblings.attr('data-confirm-dialogue-hidden', true);
517
 
518
        container.removeClass('hidden');
519
    };
520
 
521
    /**
522
     * Hide the confirm dialogue container element.
523
     *
524
     * @param  {Object} root The container element containing a dialogue.
525
     */
526
    var hideConfirmDialogueContainer = function(root) {
527
        var container = getConfirmDialogueContainer(root);
528
        var siblings = container.siblings('[data-confirm-dialogue-hidden="true"]');
529
        Aria.unhide(siblings.get());
530
        siblings.removeAttr('data-confirm-dialogue-hidden');
531
 
532
        container.addClass('hidden');
533
    };
534
 
535
    /**
536
     * Set the number of selected messages.
537
     *
538
     * @param {Object} header The header container element.
539
     * @param {Number} value The new number to display.
540
     */
541
    var setMessagesSelectedCount = function(header, value) {
542
        getHeaderEditMode(header).find(SELECTORS.MESSAGES_SELECTED_COUNT).text(value);
543
    };
544
 
545
    /**
546
     * Format message for the mustache template, transform camelCase properties to lowercase properties.
547
     *
548
     * @param  {Array} messages Array of message objects.
549
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
550
     * @return {Array} Messages formated for mustache template.
551
     */
552
    var formatMessagesForTemplate = function(messages, datesCache) {
553
        return messages.map(function(message) {
554
            return {
555
                id: message.id,
556
                isread: message.isRead,
557
                fromloggedinuser: message.fromLoggedInUser,
558
                userfrom: message.userFrom,
559
                text: message.text,
560
                formattedtime: message.timeCreated ? datesCache[message.timeCreated] : null
561
            };
562
        });
563
    };
564
 
565
    /**
566
     * Create rendering promises for each day containing messages.
567
     *
568
     * @param  {Object} header The header container element.
569
     * @param  {Object} body The body container element.
570
     * @param  {Object} footer The footer container element.
571
     * @param  {Array} days Array of days containing messages.
572
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
573
     * @return {Promise} Days rendering promises.
574
     */
575
    var renderAddDays = function(header, body, footer, days, datesCache) {
576
        var messagesContainer = getMessagesContainer(body);
577
        var daysRenderPromises = days.map(function(data) {
578
            var timestampDate = new Date(data.value.timestamp * 1000);
579
            return Templates.render(TEMPLATES.DAY, {
580
                timestamp: data.value.timestamp,
581
                currentyear: timestampDate.getFullYear() === (new Date()).getFullYear(),
582
                messages: formatMessagesForTemplate(data.value.messages, datesCache)
583
            });
584
        });
585
 
586
        return $.when.apply($, daysRenderPromises).then(function() {
587
            // Wait until all of the rendering is done for each of the days
588
            // to ensure they are added to the page in the correct order.
589
            days.forEach(function(data, index) {
590
                daysRenderPromises[index]
591
                    .then(function(html) {
592
                        if (data.before) {
593
                            var element = getDayElement(body, data.before.timestamp);
594
                            return $(html).insertBefore(element);
595
                        } else {
596
                            return messagesContainer.append(html);
597
                        }
598
                    })
599
                    .catch(function() {
600
                        // Fail silently.
601
                    });
602
            });
603
 
604
            return;
605
        });
606
    };
607
 
608
    /**
609
     * Add (more) messages to day containers.
610
     *
611
     * @param  {Object} header The header container element.
612
     * @param  {Object} body The body container element.
613
     * @param  {Object} footer The footer container element.
614
     * @param  {Array} messages List of messages.
615
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
616
     * @return {Promise} Messages rendering promises.
617
     */
618
    var renderAddMessages = function(header, body, footer, messages, datesCache) {
619
        var messagesData = messages.map(function(data) {
620
            return data.value;
621
        });
622
        var formattedMessages = formatMessagesForTemplate(messagesData, datesCache);
623
 
624
        return Templates.render(TEMPLATES.MESSAGES, {messages: formattedMessages})
625
            .then(function(html) {
626
                var messageList = $(html);
627
                messages.forEach(function(data) {
628
                    var messageHtml = messageList.find('[data-message-id="' + data.value.id + '"]');
629
                    if (data.before) {
630
                        var element = getMessageElement(body, data.before.id);
631
                        return messageHtml.insertBefore(element);
632
                    } else {
633
                        var dayContainer = getDayElement(body, data.day.timestamp);
634
                        var dayMessagesContainer = dayContainer.find(SELECTORS.DAY_MESSAGES_CONTAINER);
635
                        return dayMessagesContainer.append(messageHtml);
636
                    }
637
                });
638
 
639
                return;
640
            });
641
    };
642
 
643
    /**
644
     * Update existing messages.
645
     *
646
     * @param  {Object} header The header container element.
647
     * @param  {Object} body The body container element.
648
     * @param  {Object} footer The footer container element.
649
     * @param  {Array} messages List of messages.
650
     * @param  {Object} datesCache Cache timestamps and their formatted date string.
651
     */
652
    var renderUpdateMessages = function(header, body, footer, messages, datesCache) {
653
        messages.forEach(function(message) {
654
            var before = message.before;
655
            var after = message.after;
656
            var element = getMessageElement(body, before.id);
657
 
658
            if (before.id != after.id) {
659
                element.attr('data-message-id', after.id);
660
            }
661
 
662
            if (before.timeCreated != after.timeCreated) {
663
                var formattedTime = datesCache[after.timeCreated];
664
                element.find(SELECTORS.LOADING_ICON_CONTAINER).addClass('hidden');
665
                element.find(SELECTORS.TIME_CREATED).text(formattedTime).removeClass('hidden');
666
            }
667
 
668
            if (before.sendState != after.sendState) {
669
                var loading = element.find(SELECTORS.LOADING_ICON_CONTAINER);
670
                var time = element.find(SELECTORS.TIME_CREATED);
671
                var retry = element.find(SELECTORS.RETRY_SEND);
672
 
673
                loading.addClass('hidden');
674
                Aria.hide(loading.get());
675
 
676
                time.addClass('hidden');
677
                Aria.hide(time.get());
678
 
679
                retry.addClass('hidden');
680
                Aria.hide(retry.get());
681
 
682
                element.removeClass('border border-danger');
683
 
684
                switch (after.sendState) {
685
                    case 'pending':
686
                        loading.removeClass('hidden');
687
                        Aria.unhide(loading.get());
688
                        break;
689
                    case 'error':
690
                        retry.removeClass('hidden');
691
                        Aria.unhide(retry.get());
692
                        element.addClass('border border-danger');
693
                        break;
694
                    case 'sent':
695
                        time.removeClass('hidden');
696
                        Aria.unhide(time.get());
697
                        break;
698
                }
699
            }
700
 
701
            if (before.text != after.text) {
702
                element.find(SELECTORS.TEXT_CONTAINER).html(after.text);
703
            }
704
 
705
            if (before.errorMessage != after.errorMessage) {
706
                var messageContainer = element.find(SELECTORS.ERROR_MESSAGE_CONTAINER);
707
                var message = messageContainer.find(SELECTORS.ERROR_MESSAGE);
708
 
709
                if (after.errorMessage) {
710
                    messageContainer.removeClass('hidden');
711
                    Aria.unhide(messageContainer.get());
712
                    message.text(after.errorMessage);
713
                } else {
714
                    messageContainer.addClass('hidden');
715
                    Aria.unhide(messageContainer.get());
716
                    message.text('');
717
                }
718
            }
719
        });
720
    };
721
 
722
    /**
723
     * Remove days from conversation.
724
     *
725
     * @param  {Object} body The body container element.
726
     * @param  {Array} days Array of days to be removed.
727
     */
728
    var renderRemoveDays = function(body, days) {
729
        days.forEach(function(data) {
730
            getDayElement(body, data.timestamp).remove();
731
        });
732
    };
733
 
734
    /**
735
     * Remove messages from conversation.
736
     *
737
     * @param  {Object} body The body container element.
738
     * @param  {Array} messages Array of messages to be removed.
739
     */
740
    var renderRemoveMessages = function(body, messages) {
741
        messages.forEach(function(data) {
742
            getMessageElement(body, data.id).remove();
743
        });
744
    };
745
 
746
    /**
747
     * Render the full conversation base on input from the statemanager.
748
     *
749
     * This will pre-load all of the formatted timestamps for each message that
750
     * needs to render to reduce the number of networks requests.
751
     *
752
     * @param  {Object} header The header container element.
753
     * @param  {Object} body The body container element.
754
     * @param  {Object} footer The footer container element.
755
     * @param  {Object} data The conversation diff.
756
     * @return {Object} jQuery promise.
757
     */
758
    var renderConversation = function(header, body, footer, data) {
759
        var renderingPromises = [];
760
        var hasAddDays = data.days.add.length > 0;
761
        var hasAddMessages = data.messages.add.length > 0;
762
        var hasUpdateMessages = data.messages.update.length > 0;
763
        var timestampsToFormat = [];
764
        var datesCachePromise = $.Deferred().resolve({}).promise();
765
 
766
        if (hasAddDays) {
767
            // Search for all of the timeCreated values in all of the messages in all of
768
            // the days that we need to render.
769
            timestampsToFormat = timestampsToFormat.concat(data.days.add.reduce(function(carry, day) {
770
                return carry.concat(day.value.messages.reduce(function(timestamps, message) {
771
                    if (message.timeCreated) {
772
                        timestamps.push(message.timeCreated);
773
                    }
774
                    return timestamps;
775
                }, []));
776
            }, []));
777
        }
778
 
779
        if (hasAddMessages) {
780
            // Search for all of the timeCreated values in all of the messages that we
781
            // need to render.
782
            timestampsToFormat = timestampsToFormat.concat(data.messages.add.reduce(function(timestamps, message) {
783
                if (message.value.timeCreated) {
784
                    timestamps.push(message.value.timeCreated);
785
                }
786
                return timestamps;
787
            }, []));
788
        }
789
 
790
        if (hasUpdateMessages) {
791
            timestampsToFormat = timestampsToFormat.concat(data.messages.update.reduce(function(timestamps, message) {
792
                if (message.before.timeCreated != message.after.timeCreated) {
793
                    timestamps.push(message.after.timeCreated);
794
                }
795
                return timestamps;
796
            }, []));
797
        }
798
 
799
        if (timestampsToFormat.length) {
800
            // If we have timestamps then pre-load the formatted version of each of them
801
            // in a single request to the server. This saves the templates doing multiple
802
            // individual requests.
803
            datesCachePromise = Str.get_string('strftimetime24', 'core_langconfig')
804
                .then(function(format) {
805
                    var requests = timestampsToFormat.map(function(timestamp) {
806
                        return {
807
                            timestamp: timestamp,
808
                            format: format
809
                        };
810
                    });
811
 
812
                    return UserDate.get(requests);
813
                })
814
                .then(function(formattedTimes) {
815
                    return timestampsToFormat.reduce(function(carry, timestamp, index) {
816
                        carry[timestamp] = formattedTimes[index];
817
                        return carry;
818
                    }, {});
819
                });
820
        }
821
 
822
        if (hasAddDays) {
823
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
824
                return renderAddDays(header, body, footer, data.days.add, datesCache);
825
            }));
826
        }
827
 
828
        if (hasAddMessages) {
829
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
830
                return renderAddMessages(header, body, footer, data.messages.add, datesCache);
831
            }));
832
        }
833
 
834
        if (hasUpdateMessages) {
835
            renderingPromises.push(datesCachePromise.then(function(datesCache) {
836
                return renderUpdateMessages(header, body, footer, data.messages.update, datesCache);
837
            }));
838
        }
839
 
840
        if (data.days.remove.length > 0) {
841
            renderRemoveDays(body, data.days.remove);
842
        }
843
 
844
        if (data.messages.remove.length > 0) {
845
            renderRemoveMessages(body, data.messages.remove);
846
        }
847
 
848
        return $.when.apply($, renderingPromises);
849
    };
850
 
851
    /**
852
     * Render the conversation header.
853
     *
854
     * @param {Object} header The header container element.
855
     * @param {Object} body The body container element.
856
     * @param {Object} footer The footer container element.
857
     * @param {Object} data Data for header.
858
     * @return {Object} jQuery promise
859
     */
860
    var renderHeader = function(header, body, footer, data) {
861
        var headerContainer = getHeaderContent(header);
862
        var template = TEMPLATES.HEADER_PUBLIC;
863
        data.context.showrouteback = (header.attr('data-from-panel') === "false");
864
        if (data.type == CONVERSATION_TYPES.PRIVATE) {
865
            template = data.showControls ? TEMPLATES.HEADER_PRIVATE : TEMPLATES.HEADER_PRIVATE_NO_CONTROLS;
866
        } else if (data.type == CONVERSATION_TYPES.SELF) {
867
            template = TEMPLATES.HEADER_SELF;
868
        }
869
 
870
        return Templates.render(template, data.context)
871
            .then(function(html, js) {
872
                Templates.replaceNodeContents(headerContainer, html, js);
873
                return;
874
            });
875
    };
876
 
877
    /**
878
     * Render the conversation footer.
879
     *
880
     * @param {Object} header The header container element.
881
     * @param {Object} body The body container element.
882
     * @param {Object} footer The footer container element.
883
     * @param {Object} data Data for footer.
884
     * @return {Object} jQuery promise.
885
     */
886
    var renderFooter = function(header, body, footer, data) {
887
        hideAllFooterElements(footer);
888
 
889
        switch (data.type) {
890
            case 'placeholder':
891
                return showFooterPlaceholder(footer);
892
            case 'add-contact':
893
                return Str.get_strings([
894
                        {
895
                            key: 'requirecontacttomessage',
896
                            component: 'core_message',
897
                            param: data.user.fullname
898
                        },
899
                        {
900
                            key: 'isnotinyourcontacts',
901
                            component: 'core_message',
902
                            param: data.user.fullname
903
                        }
904
                    ])
905
                    .then(function(strings) {
906
                        var title = strings[1];
907
                        var text = strings[0];
908
                        var footerContainer = getFooterRequireContactContainer(footer);
909
                        footerContainer.find(SELECTORS.TITLE).text(title);
910
                        footerContainer.find(SELECTORS.TEXT).text(text);
911
                        showFooterRequireContact(footer);
912
                        return strings;
913
                    });
914
            case 'edit-mode':
915
                return showFooterEditMode(footer);
916
            case 'content':
917
                return showFooterContent(footer);
918
            case 'unblock':
919
                return showFooterRequireUnblock(footer);
920
            case 'unable-to-message':
921
                return showFooterUnableToMessage(footer);
922
        }
923
 
924
        return true;
925
    };
926
 
927
    /**
928
     * Scroll to a message in the conversation.
929
     *
930
     * @param {Object} header The header container element.
931
     * @param {Object} body The body container element.
932
     * @param {Object} footer The footer container element.
933
     * @param {Number} messageId Message id.
934
     */
935
    var renderScrollToMessage = function(header, body, footer, messageId) {
936
        var messagesContainer = getMessagesContainer(body);
937
        var messageElement = getMessageElement(body, messageId);
938
        var position = messageElement.position();
939
        // Scroll the message container down to the top of the message element.
940
        if (position) {
941
            var scrollTop = messagesContainer.scrollTop() + position.top;
942
            messagesContainer.scrollTop(scrollTop);
943
        }
944
    };
945
 
946
    /**
947
     * Hide or show the conversation header.
948
     *
949
     * @param {Object} header The header container element.
950
     * @param {Object} body The body container element.
951
     * @param {Object} footer The footer container element.
952
     * @param {Bool} isLoadingMembers Members loading.
953
     */
954
    var renderLoadingMembers = function(header, body, footer, isLoadingMembers) {
955
        if (isLoadingMembers) {
956
            hideHeaderContent(header);
957
            showHeaderPlaceholder(header);
958
        } else {
959
            showHeaderContent(header);
960
            hideHeaderPlaceholder(header);
961
        }
962
    };
963
 
964
    /**
965
     * Hide or show loading conversation messages.
966
     *
967
     * @param {Object} header The header container element.
968
     * @param {Object} body The body container element.
969
     * @param {Object} footer The footer container element.
970
     * @param {Bool} isLoadingFirstMessages Messages loading.
971
     */
972
    var renderLoadingFirstMessages = function(header, body, footer, isLoadingFirstMessages) {
973
        if (isLoadingFirstMessages) {
974
            hideMessagesContainer(body);
975
            showContentPlaceholder(body);
976
        } else {
977
            showMessagesContainer(body);
978
            hideContentPlaceholder(body);
979
        }
980
    };
981
 
982
    /**
983
     * Hide or show loading more messages.
984
     *
985
     * @param {Object} header The header container element.
986
     * @param {Object} body The body container element.
987
     * @param {Object} footer The footer container element.
988
     * @param {Bool} isLoading Messages loading.
989
     */
990
    var renderLoadingMessages = function(header, body, footer, isLoading) {
991
        if (isLoading) {
992
            showMoreMessagesLoadingIcon(body);
993
        } else {
994
            hideMoreMessagesLoadingIcon(body);
995
        }
996
    };
997
 
998
    /**
999
     * Hide or show the emoji picker.
1000
     *
1001
     * @param {Object} header The header container element.
1002
     * @param {Object} body The body container element.
1003
     * @param {Object} footer The footer container element.
1004
     * @param {Bool} show Should the emoji picker be visible.
1005
     */
1006
    var renderShowEmojiPicker = function(header, body, footer, show) {
1007
        var container = getEmojiPickerContainer(footer);
1008
 
1009
        if (show) {
1010
            container.removeClass('hidden');
1011
            Aria.unhide(container.get());
1012
            container.find(SELECTORS.EMOJI_PICKER_SEARCH_INPUT).focus();
1013
        } else {
1014
            container.addClass('hidden');
1015
            Aria.hide(container.get());
1016
        }
1017
    };
1018
 
1019
    /**
1020
     * Hide or show the emoji auto complete.
1021
     *
1022
     * @param {Object} header The header container element.
1023
     * @param {Object} body The body container element.
1024
     * @param {Object} footer The footer container element.
1025
     * @param {Bool} show Should the emoji picker be visible.
1026
     */
1027
    var renderShowEmojiAutoComplete = function(header, body, footer, show) {
1028
        var container = getEmojiAutoCompleteContainer(footer);
1029
 
1030
        if (show) {
1031
            container.removeClass('hidden');
1032
            Aria.unhide(container.get());
1033
        } else {
1034
            container.addClass('hidden');
1035
            Aria.hide(container.get());
1036
        }
1037
    };
1038
 
1039
    /**
1040
     * Show a confirmation dialogue
1041
     *
1042
     * @param {Object} header The header container element.
1043
     * @param {Object} body The body container element.
1044
     * @param {Object} footer The footer container element.
1045
     * @param {String} buttonSelectors Selectors for the buttons to show.
1046
     * @param {String} bodyText Text to show in dialogue.
1047
     * @param {String} headerText Text to show in dialogue header.
1048
     * @param {Bool} canCancel Can this dialogue be cancelled.
1049
     * @param {Bool} skipHeader Skip blanking out the header
1050
     * @param {Bool} showOk Show an 'Okay' button for a dialogue which will close it
1051
     */
1052
    var showConfirmDialogue = function(
1053
        header,
1054
        body,
1055
        footer,
1056
        buttonSelectors,
1057
        bodyText,
1058
        headerText,
1059
        canCancel,
1060
        skipHeader,
1061
        showOk
1062
    ) {
1063
        var dialogue = getConfirmDialogueContainer(body);
1064
        var buttons = buttonSelectors.map(function(selector) {
1065
            return dialogue.find(selector);
1066
        });
1067
        var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
1068
        var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);
1069
        var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
1070
        var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);
1071
 
1072
        dialogue.find('button').addClass('hidden');
1073
 
1074
        if (canCancel) {
1075
            cancelButton.removeClass('hidden');
1076
        } else {
1077
            cancelButton.addClass('hidden');
1078
        }
1079
 
1080
        if (showOk) {
1081
            okayButton.removeClass('hidden');
1082
        } else {
1083
            okayButton.addClass('hidden');
1084
        }
1085
 
1086
        if (headerText) {
1087
            // Create the dialogue header.
1088
            dialogueHeader = $('<h3 class="h6" data-region="dialogue-header"></h3>');
1089
            dialogueHeader.text(headerText);
1090
            // Prepend it to the confirmation body.
1091
            var confirmDialogue = dialogue.find(SELECTORS.CONFIRM_DIALOGUE);
1092
            confirmDialogue.prepend(dialogueHeader);
1093
        } else if (dialogueHeader.length) {
1094
            // Header text is empty but dialogue header is present, so remove it.
1095
            dialogueHeader.remove();
1096
        }
1097
 
1098
        buttons.forEach(function(button) {
1099
            button.removeClass('hidden');
1100
        });
1101
        text.text(bodyText);
1102
        showConfirmDialogueContainer(footer);
1103
        showConfirmDialogueContainer(body);
1104
 
1105
        if (!skipHeader) {
1106
            showConfirmDialogueContainer(header);
1107
        }
1108
 
1109
        dialogue.find(SELECTORS.CAN_RECEIVE_FOCUS).filter(':visible').first().focus();
1110
    };
1111
 
1112
    /**
1113
     * Hide the dialogue
1114
     *
1115
     * @param {Object} header The header container element.
1116
     * @param {Object} body The body container element.
1117
     * @param {Object} footer The footer container element.
1118
     * @return {Bool} always true.
1119
     */
1120
    var hideConfirmDialogue = function(header, body, footer) {
1121
        var dialogue = getConfirmDialogueContainer(body);
1122
        var cancelButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_CANCEL_BUTTON);
1123
        var okayButton = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_OKAY_BUTTON);
1124
        var text = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_TEXT);
1125
        var dialogueHeader = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_HEADER);
1126
 
1127
        hideCheckDeleteDialogue(body);
1128
        hideConfirmDialogueContainer(body);
1129
        hideConfirmDialogueContainer(footer);
1130
        hideConfirmDialogueContainer(header);
1131
        dialogue.find('button').addClass('hidden');
1132
        cancelButton.removeClass('hidden');
1133
        okayButton.removeClass('hidden');
1134
        text.text('');
1135
 
1136
        // Remove dialogue header if present.
1137
        if (dialogueHeader.length) {
1138
            dialogueHeader.remove();
1139
        }
1140
 
1141
        header.find(SELECTORS.CAN_RECEIVE_FOCUS).first().focus();
1142
        return true;
1143
    };
1144
 
1145
    /**
1146
     * Render the confirm block user dialogue.
1147
     *
1148
     * @param {Object} header The header container element.
1149
     * @param {Object} body The body container element.
1150
     * @param {Object} footer The footer container element.
1151
     * @param {Object} user User to block.
1152
     * @return {Object} jQuery promise
1153
     */
1154
    var renderConfirmBlockUser = function(header, body, footer, user) {
1155
        if (user) {
1156
            if (user.canmessageevenifblocked) {
1157
                return Str.get_string('cantblockuser', 'core_message', user.fullname)
1158
                    .then(function(string) {
1159
                        return showConfirmDialogue(header, body, footer, [], string, '', false, false, true);
1160
                    });
1161
            } else {
1162
                return Str.get_string('blockuserconfirm', 'core_message', user.fullname)
1163
                    .then(function(string) {
1164
                        return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_BLOCK], string, '', true, false);
1165
                    });
1166
            }
1167
        } else {
1168
            return hideConfirmDialogue(header, body, footer);
1169
        }
1170
    };
1171
 
1172
    /**
1173
     * Render the confirm unblock user dialogue.
1174
     *
1175
     * @param {Object} header The header container element.
1176
     * @param {Object} body The body container element.
1177
     * @param {Object} footer The footer container element.
1178
     * @param {Object} user User to unblock.
1179
     * @return {Object} jQuery promise
1180
     */
1181
    var renderConfirmUnblockUser = function(header, body, footer, user) {
1182
        if (user) {
1183
            return Str.get_string('unblockuserconfirm', 'core_message', user.fullname)
1184
                .then(function(string) {
1185
                    return showConfirmDialogue(header, body, footer, [SELECTORS.ACTION_CONFIRM_UNBLOCK], string, '', true, false);
1186
                });
1187
        } else {
1188
            return hideConfirmDialogue(header, body, footer);
1189
        }
1190
    };
1191
 
1192
    /**
1193
     * Render the add user as contact dialogue.
1194
     *
1195
     * @param {Object} header The header container element.
1196
     * @param {Object} body The body container element.
1197
     * @param {Object} footer The footer container element.
1198
     * @param {Object} user User to add as contact.
1199
     * @return {Object} jQuery promise
1200
     */
1201
    var renderConfirmAddContact = function(header, body, footer, user) {
1202
        if (user) {
1203
            return Str.get_string('addcontactconfirm', 'core_message', user.fullname)
1204
                .then(function(string) {
1205
                    return showConfirmDialogue(
1206
                        header,
1207
                        body,
1208
                        footer,
1209
                        [SELECTORS.ACTION_CONFIRM_ADD_CONTACT],
1210
                        string,
1211
                        '',
1212
                        true,
1213
                        false
1214
                    );
1215
                });
1216
        } else {
1217
            return hideConfirmDialogue(header, body, footer);
1218
        }
1219
    };
1220
 
1221
    /**
1222
     * Render the remove user from contacts dialogue.
1223
     *
1224
     * @param {Object} header The header container element.
1225
     * @param {Object} body The body container element.
1226
     * @param {Object} footer The footer container element.
1227
     * @param {Object} user User to remove from contacts.
1228
     * @return {Object} jQuery promise
1229
     */
1230
    var renderConfirmRemoveContact = function(header, body, footer, user) {
1231
        if (user) {
1232
            return Str.get_string('removecontactconfirm', 'core_message', user.fullname)
1233
                .then(function(string) {
1234
                    return showConfirmDialogue(
1235
                        header,
1236
                        body,
1237
                        footer,
1238
                        [SELECTORS.ACTION_CONFIRM_REMOVE_CONTACT],
1239
                        string,
1240
                        '',
1241
                        true,
1242
                        false
1243
                    );
1244
                });
1245
        } else {
1246
            return hideConfirmDialogue(header, body, footer);
1247
        }
1248
    };
1249
 
1250
    /**
1251
     * Render the delete selected messages dialogue.
1252
     *
1253
     * @param {Object} header The header container element.
1254
     * @param {Object} body The body container element.
1255
     * @param {Object} footer The footer container element.
1256
     * @param {Object} data If the dialogue should show and checkbox shows to delete message for all users.
1257
     * @return {Object} jQuery promise
1258
     */
1259
    var renderConfirmDeleteSelectedMessages = function(header, body, footer, data) {
1260
        var showmessage = null;
1261
        if (data.type == CONVERSATION_TYPES.SELF) {
1262
            // Message displayed to self-conversations is slighly different.
1263
            showmessage = 'deleteselectedmessagesconfirmselfconversation';
1264
        } else {
1265
            // This other message should be displayed.
1266
            if (data.canDeleteMessagesForAllUsers) {
1267
                showCheckDeleteDialogue(body);
1268
                showmessage = 'deleteforeveryoneselectedmessagesconfirm';
1269
            } else {
1270
                showmessage = 'deleteselectedmessagesconfirm';
1271
            }
1272
        }
1273
 
1274
        if (data.show) {
1275
            return Str.get_string(showmessage, 'core_message')
1276
                .then(function(string) {
1277
                    return showConfirmDialogue(
1278
                        header,
1279
                        body,
1280
                        footer,
1281
                        [SELECTORS.ACTION_CONFIRM_DELETE_SELECTED_MESSAGES],
1282
                        string,
1283
                        '',
1284
                        true,
1285
                        false
1286
                    );
1287
                });
1288
        } else {
1289
            return hideConfirmDialogue(header, body, footer);
1290
        }
1291
    };
1292
 
1293
    /**
1294
     * Render the confirm delete conversation dialogue.
1295
     *
1296
     * @param {Object} header The header container element.
1297
     * @param {Object} body The body container element.
1298
     * @param {Object} footer The footer container element.
1299
     * @param {int|Null} type The conversation type to be removed.
1300
     * @return {Object} jQuery promise
1301
     */
1302
    var renderConfirmDeleteConversation = function(header, body, footer, type) {
1303
        var showmessage = null;
1304
        if (type == CONVERSATION_TYPES.SELF) {
1305
            // Message displayed to self-conversations is slighly different.
1306
            showmessage = 'deleteallselfconfirm';
1307
        } else if (type) {
1308
            // This other message should be displayed.
1309
            showmessage = 'deleteallconfirm';
1310
        }
1311
 
1312
        if (showmessage) {
1313
            return Str.get_string(showmessage, 'core_message')
1314
                .then(function(string) {
1315
                    return showConfirmDialogue(
1316
                        header,
1317
                        body,
1318
                        footer,
1319
                        [SELECTORS.ACTION_CONFIRM_DELETE_CONVERSATION],
1320
                        string,
1321
                        '',
1322
                        true,
1323
                        false
1324
                    );
1325
                });
1326
        } else {
1327
            return hideConfirmDialogue(header, body, footer);
1328
        }
1329
    };
1330
 
1331
    /**
1332
     * Render the confirm delete conversation dialogue.
1333
     *
1334
     * @param {Object} header The header container element.
1335
     * @param {Object} body The body container element.
1336
     * @param {Object} footer The footer container element.
1337
     * @param {Bool} user The other user object.
1338
     * @return {Object} jQuery promise
1339
     */
1340
    var renderConfirmContactRequest = function(header, body, footer, user) {
1341
        if (user) {
1342
            return Str.get_string('userwouldliketocontactyou', 'core_message', user.fullname)
1343
                .then(function(string) {
1344
                    var buttonSelectors = [
1345
                        SELECTORS.ACTION_ACCEPT_CONTACT_REQUEST,
1346
                        SELECTORS.ACTION_DECLINE_CONTACT_REQUEST
1347
                    ];
1348
                    return showConfirmDialogue(header, body, footer, buttonSelectors, string, '', false, true);
1349
                });
1350
        } else {
1351
            return hideConfirmDialogue(header, body, footer);
1352
        }
1353
    };
1354
 
1355
    /**
1356
     * Show the checkbox to allow delete message for all.
1357
     *
1358
     * @param {Object} body The body container element.
1359
     */
1360
    var showCheckDeleteDialogue = function(body) {
1361
        var dialogue = getConfirmDialogueContainer(body);
1362
        var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);
1363
        checkboxRegion.removeClass('hidden');
1364
    };
1365
 
1366
    /**
1367
     * Hide the checkbox to allow delete message for all.
1368
     *
1369
     * @param {Object} body The body container element.
1370
     */
1371
    var hideCheckDeleteDialogue = function(body) {
1372
        var dialogue = getConfirmDialogueContainer(body);
1373
        var checkboxRegion = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE_CONTAINER);
1374
        var checkbox = dialogue.find(SELECTORS.DELETE_MESSAGES_FOR_ALL_USERS_TOGGLE);
1375
        checkbox.prop('checked', false);
1376
        checkboxRegion.addClass('hidden');
1377
    };
1378
 
1379
    /**
1380
     * Show or hide the block / unblock option in the header dropdown menu.
1381
     *
1382
     * @param {Object} header The header container element.
1383
     * @param {Object} body The body container element.
1384
     * @param {Object} footer The footer container element.
1385
     * @param {Bool} isBlocked is user blocked.
1386
     */
1387
    var renderIsBlocked = function(header, body, footer, isBlocked) {
1388
        if (isBlocked) {
1389
            header.find(SELECTORS.ACTION_REQUEST_BLOCK).addClass('hidden');
1390
            header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).removeClass('hidden');
1391
        } else {
1392
            header.find(SELECTORS.ACTION_REQUEST_BLOCK).removeClass('hidden');
1393
            header.find(SELECTORS.ACTION_REQUEST_UNBLOCK).addClass('hidden');
1394
        }
1395
    };
1396
 
1397
    /**
1398
     * Show or hide the favourite / unfavourite option in the header dropdown menu
1399
     * and the favourite star in the header title.
1400
     *
1401
     * @param {Object} header The header container element.
1402
     * @param {Object} body The body container element.
1403
     * @param {Object} footer The footer container element.
1404
     * @param {Bool} state is this conversation a favourite.
1405
     */
1406
    var renderIsFavourite = function(header, body, footer, state) {
1407
        var favouriteIcon = header.find(SELECTORS.FAVOURITE_ICON_CONTAINER);
1408
        var addFavourite = header.find(SELECTORS.ACTION_CONFIRM_FAVOURITE);
1409
        var removeFavourite = header.find(SELECTORS.ACTION_CONFIRM_UNFAVOURITE);
1410
 
1411
        switch (state) {
1412
            case 'hide':
1413
                favouriteIcon.addClass('hidden');
1414
                addFavourite.addClass('hidden');
1415
                removeFavourite.addClass('hidden');
1416
                break;
1417
            case 'show-add':
1418
                favouriteIcon.addClass('hidden');
1419
                addFavourite.removeClass('hidden');
1420
                removeFavourite.addClass('hidden');
1421
                break;
1422
            case 'show-remove':
1423
                favouriteIcon.removeClass('hidden');
1424
                addFavourite.addClass('hidden');
1425
                removeFavourite.removeClass('hidden');
1426
                break;
1427
        }
1428
    };
1429
 
1430
    /**
1431
     * Show or hide the mute / unmute option in the header dropdown menu
1432
     * and the muted icon in the header title.
1433
     *
1434
     * @param {Object} header The header container element.
1435
     * @param {Object} body The body container element.
1436
     * @param {Object} footer The footer container element.
1437
     * @param {string} state The state of the conversation as defined by the patcher.
1438
     */
1439
    var renderIsMuted = function(header, body, footer, state) {
1440
        var muteIcon = header.find(SELECTORS.MUTED_ICON_CONTAINER);
1441
        var setMuted = header.find(SELECTORS.ACTION_CONFIRM_MUTE);
1442
        var unsetMuted = header.find(SELECTORS.ACTION_CONFIRM_UNMUTE);
1443
 
1444
        switch (state) {
1445
            case 'hide':
1446
                muteIcon.addClass('hidden');
1447
                setMuted.addClass('hidden');
1448
                unsetMuted.addClass('hidden');
1449
                break;
1450
            case 'show-mute':
1451
                muteIcon.addClass('hidden');
1452
                setMuted.removeClass('hidden');
1453
                unsetMuted.addClass('hidden');
1454
                break;
1455
            case 'show-unmute':
1456
                muteIcon.removeClass('hidden');
1457
                setMuted.addClass('hidden');
1458
                unsetMuted.removeClass('hidden');
1459
                break;
1460
        }
1461
    };
1462
 
1463
    /**
1464
     * Show or hide the add / remove user as contact option in the header dropdown menu.
1465
     *
1466
     * @param {Object} header The header container element.
1467
     * @param {Object} body The body container element.
1468
     * @param {Object} footer The footer container element.
1469
     * @param {Bool} state the contact state.
1470
     */
1471
    var renderIsContact = function(header, body, footer, state) {
1472
        var addContact = header.find(SELECTORS.ACTION_REQUEST_ADD_CONTACT);
1473
        var removeContact = header.find(SELECTORS.ACTION_REQUEST_REMOVE_CONTACT);
1474
 
1475
        switch (state) {
1476
            case 'pending-contact':
1477
                addContact.addClass('hidden');
1478
                removeContact.addClass('hidden');
1479
                break;
1480
            case 'contact':
1481
                addContact.addClass('hidden');
1482
                removeContact.removeClass('hidden');
1483
                break;
1484
            case 'non-contact':
1485
                addContact.removeClass('hidden');
1486
                removeContact.addClass('hidden');
1487
                break;
1488
        }
1489
    };
1490
 
1491
    /**
1492
     * Show or hide confirm action from confirm dialogue is loading.
1493
     *
1494
     * @param {Object} header The header container element.
1495
     * @param {Object} body The body container element.
1496
     * @param {Object} footer The footer container element.
1497
     * @param {Bool} isLoading confirm action is loading.
1498
     */
1499
    var renderLoadingConfirmAction = function(header, body, footer, isLoading) {
1500
        var dialogue = getConfirmDialogueContainer(body);
1501
        var buttons = dialogue.find('button');
1502
        var buttonText = dialogue.find(SELECTORS.CONFIRM_DIALOGUE_BUTTON_TEXT);
1503
        var loadingIcon = dialogue.find(SELECTORS.LOADING_ICON_CONTAINER);
1504
 
1505
        if (isLoading) {
1506
            buttons.prop('disabled', true);
1507
            buttonText.addClass('hidden');
1508
            loadingIcon.removeClass('hidden');
1509
        } else {
1510
            buttons.prop('disabled', false);
1511
            buttonText.removeClass('hidden');
1512
            loadingIcon.addClass('hidden');
1513
        }
1514
    };
1515
 
1516
    /**
1517
     * Show or hide the header and footer content for edit mode.
1518
     *
1519
     * @param {Object} header The header container element.
1520
     * @param {Object} body The body container element.
1521
     * @param {Object} footer The footer container element.
1522
     * @param {Bool} inEditMode In edit mode or not.
1523
     */
1524
    var renderInEditMode = function(header, body, footer, inEditMode) {
1525
        var messages = null;
1526
 
1527
        if (inEditMode) {
1528
            messages = body.find(SELECTORS.MESSAGE_NOT_SELECTED);
1529
            messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
1530
            hideHeaderContent(header);
1531
            showHeaderEditMode(header);
1532
        } else {
1533
            messages = getMessagesContainer(body);
1534
            messages.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
1535
            messages.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
1536
            showHeaderContent(header);
1537
            hideHeaderEditMode(header);
1538
        }
1539
    };
1540
 
1541
    /**
1542
     * Select or unselect messages.
1543
     *
1544
     * @param {Object} header The header container element.
1545
     * @param {Object} body The body container element.
1546
     * @param {Object} footer The footer container element.
1547
     * @param {Object} data The messages to select or unselect.
1548
     */
1549
    var renderSelectedMessages = function(header, body, footer, data) {
1550
        var hasSelectedMessages = data.count > 0;
1551
 
1552
        if (data.add.length) {
1553
            data.add.forEach(function(messageId) {
1554
                var message = getMessageElement(body, messageId);
1555
                message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).addClass('hidden');
1556
                message.find(SELECTORS.MESSAGE_SELECTED_ICON).removeClass('hidden');
1557
                message.attr('aria-checked', true);
1558
            });
1559
        }
1560
 
1561
        if (data.remove.length) {
1562
            data.remove.forEach(function(messageId) {
1563
                var message = getMessageElement(body, messageId);
1564
 
1565
                if (hasSelectedMessages) {
1566
                    message.find(SELECTORS.MESSAGE_NOT_SELECTED_ICON).removeClass('hidden');
1567
                }
1568
 
1569
                message.find(SELECTORS.MESSAGE_SELECTED_ICON).addClass('hidden');
1570
                message.attr('aria-checked', false);
1571
            });
1572
        }
1573
 
1574
        setMessagesSelectedCount(header, data.count);
1575
    };
1576
 
1577
    /**
1578
     * Show or hide the require add contact panel.
1579
     *
1580
     * @param {Object} header The header container element.
1581
     * @param {Object} body The body container element.
1582
     * @param {Object} footer The footer container element.
1583
     * @param {Object} data Whether the user has to be added a a contact.
1584
     * @return {Object} jQuery promise
1585
     */
1586
    var renderRequireAddContact = function(header, body, footer, data) {
1587
        if (data.show && !data.hasMessages) {
1588
            return Str.get_strings([
1589
                    {
1590
                        key: 'requirecontacttomessage',
1591
                        component: 'core_message',
1592
                        param: data.user.fullname
1593
                    },
1594
                    {
1595
                        key: 'isnotinyourcontacts',
1596
                        component: 'core_message',
1597
                        param: data.user.fullname
1598
                    }
1599
                ])
1600
                .then(function(strings) {
1601
                    var title = strings[1];
1602
                    var text = strings[0];
1603
                    return showConfirmDialogue(
1604
                        header,
1605
                        body,
1606
                        footer,
1607
                        [SELECTORS.ACTION_REQUEST_ADD_CONTACT],
1608
                        text,
1609
                        title,
1610
                        false,
1611
                        true
1612
                    );
1613
                });
1614
        } else {
1615
            return hideConfirmDialogue(header, body, footer);
1616
        }
1617
    };
1618
 
1619
    /**
1620
     * Show or hide the self-conversation message.
1621
     *
1622
     * @param {Object} header The header container element.
1623
     * @param {Object} body The body container element.
1624
     * @param {Object} footer The footer container element.
1625
     * @param {Object} displayMessage should the message be displayed?.
1626
     * @return {Object|true} jQuery promise
1627
     */
1628
    var renderSelfConversationMessage = function(header, body, footer, displayMessage) {
1629
        var container = getSelfConversationMessageContainer(body);
1630
        if (displayMessage) {
1631
            container.removeClass('hidden');
1632
        } else {
1633
            container.addClass('hidden');
1634
        }
1635
        return true;
1636
    };
1637
 
1638
    /**
1639
     * Show or hide the require add contact panel.
1640
     *
1641
     * @param {Object} header The header container element.
1642
     * @param {Object} body The body container element.
1643
     * @param {Object} footer The footer container element.
1644
     * @param {Object} userFullName Full name of the other user.
1645
     * @return {Object|true} jQuery promise
1646
     */
1647
    var renderContactRequestSent = function(header, body, footer, userFullName) {
1648
        var container = getContactRequestSentContainer(body);
1649
        if (userFullName) {
1650
            return Str.get_string('yourcontactrequestpending', 'core_message', userFullName)
1651
                .then(function(string) {
1652
                    container.find(SELECTORS.TEXT).text(string);
1653
                    container.removeClass('hidden');
1654
                    return string;
1655
                });
1656
        } else {
1657
            container.addClass('hidden');
1658
            return true;
1659
        }
1660
    };
1661
 
1662
    /**
1663
     * Reset the UI to the initial state.
1664
     *
1665
     * @param {Object} header The header container element.
1666
     * @param {Object} body The body container element.
1667
     * @param {Object} footer The footer container element.
1668
     * @return {Bool}
1669
     */
1670
    var renderReset = function(header, body, footer) {
1671
        hideConfirmDialogue(header, body, footer);
1672
        hideContactRequestSentContainer(body);
1673
        hideSelfConversationMessageContainer(body);
1674
        hideAllHeaderElements(header);
1675
        showHeaderPlaceholder(header);
1676
        hideAllFooterElements(footer);
1677
        showFooterPlaceholder(footer);
1678
        return true;
1679
    };
1680
 
1681
    var render = function(header, body, footer, patch) {
1682
        var configs = [
1683
            {
1684
                // Resetting the UI needs to come first, if it's required.
1685
                reset: renderReset
1686
            },
1687
            {
1688
                // Any async rendering (stuff that requires templates, strings etc) should
1689
                // go in here.
1690
                conversation: renderConversation,
1691
                header: renderHeader,
1692
                footer: renderFooter,
1693
                confirmBlockUser: renderConfirmBlockUser,
1694
                confirmUnblockUser: renderConfirmUnblockUser,
1695
                confirmAddContact: renderConfirmAddContact,
1696
                confirmRemoveContact: renderConfirmRemoveContact,
1697
                confirmDeleteSelectedMessages: renderConfirmDeleteSelectedMessages,
1698
                confirmDeleteConversation: renderConfirmDeleteConversation,
1699
                confirmContactRequest: renderConfirmContactRequest,
1700
                requireAddContact: renderRequireAddContact,
1701
                selfConversationMessage: renderSelfConversationMessage,
1702
                contactRequestSent: renderContactRequestSent
1703
            },
1704
            {
1705
                loadingMembers: renderLoadingMembers,
1706
                loadingFirstMessages: renderLoadingFirstMessages,
1707
                loadingMessages: renderLoadingMessages,
1708
                isBlocked: renderIsBlocked,
1709
                isContact: renderIsContact,
1710
                isFavourite: renderIsFavourite,
1711
                isMuted: renderIsMuted,
1712
                loadingConfirmAction: renderLoadingConfirmAction,
1713
                inEditMode: renderInEditMode,
1714
                showEmojiPicker: renderShowEmojiPicker,
1715
                showEmojiAutoComplete: renderShowEmojiAutoComplete,
1716
            },
1717
            {
1718
                // Scrolling should be last to make sure everything
1719
                // on the page is visible.
1720
                scrollToMessage: renderScrollToMessage,
1721
                selectedMessages: renderSelectedMessages
1722
            }
1723
        ];
1724
        // Helper function to process each of the configs above.
1725
        var processConfig = function(config) {
1726
            var results = [];
1727
 
1728
            for (var key in patch) {
1729
                if (config.hasOwnProperty(key)) {
1730
                    var renderFunc = config[key];
1731
                    var patchValue = patch[key];
1732
                    results.push(renderFunc(header, body, footer, patchValue));
1733
                }
1734
            }
1735
 
1736
            return results;
1737
        };
1738
 
1739
        // The first config is special because it resets the UI.
1740
        var renderingPromises = processConfig(configs[0]);
1741
        // The second config is special because it contains async rendering.
1742
        renderingPromises = renderingPromises.concat(processConfig(configs[1]));
1743
 
1744
        // Wait for the async rendering to complete before processing the
1745
        // rest of the configs, in order.
1746
        return $.when.apply($, renderingPromises)
1747
            .then(function() {
1748
                for (var i = 2; i < configs.length; i++) {
1749
                    processConfig(configs[i]);
1750
                }
1751
 
1752
                return;
1753
            })
1754
            .catch(Notification.exception);
1755
    };
1756
 
1757
    return {
1758
        render: render,
1759
    };
1760
});