import 'plugins/chatview/heading.js'; import 'plugins/chatview/bottom_panel.js'; import BaseChatView from 'shared/chat/baseview.js'; import tpl_chatbox from 'templates/chatbox.js'; import { __ } from 'i18n'; import { _converse, api, converse } from '@converse/headless/core'; import { render } from 'lit-html'; const u = converse.env.utils; const { dayjs } = converse.env; /** * The View of an open/ongoing chat conversation. * @class * @namespace _converse.ChatBoxView * @memberOf _converse */ export default class ChatView extends BaseChatView { length = 200 className = 'chatbox hidden' is_chatroom = false // Leaky abstraction from MUC events = { 'click .chatbox-navback': 'showControlBox', 'click .new-msgs-indicator': 'viewUnreadMessages', } async initialize () { const jid = this.getAttribute('jid'); _converse.chatboxviews.add(jid, this); this.model = _converse.chatboxes.get(jid); this.initDebounced(); this.listenTo(_converse, 'windowStateChanged', this.onWindowStateChanged); this.listenTo(this.model, 'change:hidden', () => !this.model.get('hidden') && this.afterShown()); this.listenTo(this.model, 'change:status', this.onStatusMessageChanged); this.listenTo(this.model.messages, 'change:correcting', this.onMessageCorrecting); this.listenTo(this.model.presence, 'change:show', this.onPresenceChanged); this.render(); // Need to be registered after render has been called. this.listenTo(this.model.messages, 'add', this.onMessageAdded); this.listenTo(this.model, 'change:show_help_messages', this.renderHelpMessages); await this.model.messages.fetched; !this.model.get('hidden') && this.afterShown() /** * Triggered once the {@link _converse.ChatBoxView} has been initialized * @event _converse#chatBoxViewInitialized * @type { _converse.HeadlinesBoxView } * @example _converse.api.listen.on('chatBoxViewInitialized', view => { ... }); */ api.trigger('chatBoxViewInitialized', this); } render () { const result = tpl_chatbox(Object.assign( this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) }) ); render(result, this); this.content = this.querySelector('.chat-content'); this.help_container = this.querySelector('.chat-content__help'); return this; } getHelpMessages () { // eslint-disable-line class-methods-use-this return [ `/clear: ${__('Remove messages')}`, `/close: ${__('Close this chat')}`, `/me: ${__('Write in the third person')}`, `/help: ${__('Show this menu')}` ]; } showControlBox () { // Used in mobile view, to navigate back to the controlbox _converse.chatboxviews.get('controlbox')?.show(); this.hide(); } /** * Given a message element, determine wether it should be * marked as a followup message to the previous element. * * Also determine whether the element following it is a * followup message or not. * * Followup messages are subsequent ones written by the same * author with no other conversation elements in between and * which were posted within 10 minutes of one another. * @private * @method _converse.ChatBoxView#markFollowups * @param { HTMLElement } el - The message element */ markFollowups (el) { // eslint-disable-line class-methods-use-this const from = el.getAttribute('data-from'); const previous_el = el.previousElementSibling; const date = dayjs(el.getAttribute('data-isodate')); const next_el = el.nextElementSibling; if ( !u.hasClass('chat-msg--action', el) && !u.hasClass('chat-msg--action', previous_el) && !u.hasClass('chat-info', el) && !u.hasClass('chat-info', previous_el) && previous_el.getAttribute('data-from') === from && date.isBefore(dayjs(previous_el.getAttribute('data-isodate')).add(10, 'minutes')) && el.getAttribute('data-encrypted') === previous_el.getAttribute('data-encrypted') ) { u.addClass('chat-msg--followup', el); } if (!next_el) { return; } if ( !u.hasClass('chat-msg--action', el) && u.hasClass('chat-info', el) && next_el.getAttribute('data-from') === from && dayjs(next_el.getAttribute('data-isodate')).isBefore(date.add(10, 'minutes')) && el.getAttribute('data-encrypted') === next_el.getAttribute('data-encrypted') ) { u.addClass('chat-msg--followup', next_el); } else { u.removeClass('chat-msg--followup', next_el); } } onPresenceChanged (item) { const show = item.get('show'); const fullname = this.model.getDisplayName(); let text; if (u.isVisible(this)) { if (show === 'offline') { text = __('%1$s has gone offline', fullname); } else if (show === 'away') { text = __('%1$s has gone away', fullname); } else if (show === 'dnd') { text = __('%1$s is busy', fullname); } else if (show === 'online') { text = __('%1$s is online', fullname); } text && this.model.createMessage({ 'message': text, 'type': 'info' }); } } async close (ev) { ev?.preventDefault?.(); if (_converse.router.history.getFragment() === 'converse/chat?jid=' + this.model.get('jid')) { _converse.router.navigate(''); } if (api.connection.connected()) { // Immediately sending the chat state, because the // model is going to be destroyed afterwards. this.model.setChatState(_converse.INACTIVE); this.model.sendChatState(); } await this.model.close(ev); /** * Triggered once a chatbox has been closed. * @event _converse#chatBoxClosed * @type { _converse.ChatBoxView | _converse.ChatRoomView } * @example _converse.api.listen.on('chatBoxClosed', view => { ... }); */ api.trigger('chatBoxClosed', this); return this; } afterShown () { this.model.clearUnreadMsgCounter(); this.model.setChatState(_converse.ACTIVE); this.scrollDown(); this.maybeFocus(); } viewUnreadMessages () { this.model.save({ 'scrolled': false, 'scrollTop': null }); this.scrollDown(); } } api.elements.define('converse-chat', ChatView);