Proyectos de Subversion Moodle

Rev

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