import 'plugins/chatview/heading.js'; import 'plugins/chatview/bottom-panel.js'; import { html, render } from 'lit-html'; import BaseChatView from 'shared/chat/baseview.js'; import tpl_chat from './templates/chat.js'; import { __ } from 'i18n'; import { _converse, api, converse } from '@converse/headless/core'; 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' 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.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_chat(Object.assign( this.model.toJSON(), { 'markScrolled': ev => this.markScrolled(ev) }) ); render(result, this); this.help_container = this.querySelector('.chat-content__help'); return this; } renderHelpMessages () { render( html` `, this.help_container ); } 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); } } 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(); } } api.elements.define('converse-chat', ChatView);