import './message-actions.js'; import './message-body.js'; import 'shared/components/dropdown.js'; import 'shared/registry'; import MessageVersionsModal from 'modals/message-versions.js'; import OccupantModal from 'modals/occupant.js'; import UserDetailsModal from 'modals/user-details.js'; import filesize from 'filesize'; import log from '@converse/headless/log'; import tpl_message from './templates/message.js'; import tpl_spinner from 'templates/spinner.js'; import { CustomElement } from 'shared/components/element.js'; import { __ } from 'i18n'; import { _converse, api, converse } from '@converse/headless/core'; import { getHats } from './utils.js'; import { getOOBURLMarkup } from 'utils/html.js'; import { html } from 'lit'; import { renderAvatar } from 'shared/directives/avatar'; const { Strophe, dayjs } = converse.env; export default class Message extends CustomElement { static get properties () { return { jid: { type: String }, mid: { type: String } } } connectedCallback () { super.connectedCallback(); this.initialize(); } async initialize () { await this.setModels(); if (!this.model) { // Happen during tests due to a race condition log.error('Could not find module for converse-chat-message'); return; } this.listenTo(this.chatbox, 'change:first_unread_id', this.requestUpdate); this.listenTo(this.model, 'change', this.requestUpdate); this.model.vcard && this.listenTo(this.model.vcard, 'change', this.requestUpdate); if (this.model.get('type') === 'groupchat') { if (this.model.occupant) { this.listenTo(this.model.occupant, 'change', this.requestUpdate); } else { this.listenTo(this.model, 'occupantAdded', () => { this.listenTo(this.model.occupant, 'change', this.requestUpdate) }); } } } async setModels () { this.chatbox = await api.chatboxes.get(this.jid); await this.chatbox.initialized; await this.chatbox.messages.fetched; this.model = this.chatbox.messages.get(this.mid); this.model && this.requestUpdate(); } render () { if (!this.model) { return ''; } else if (this.show_spinner) { return tpl_spinner(); } else if (this.model.get('file') && this.model.get('upload') !== _converse.SUCCESS) { return this.renderFileProgress(); } else if (['error', 'info'].includes(this.model.get('type'))) { return this.renderInfoMessage(); } else { return this.renderChatMessage(); } } getProps () { return Object.assign( this.model.toJSON(), this.getDerivedMessageProps() ); } renderInfoMessage () { const isodate = dayjs(this.model.get('time')).toISOString(); const i18n_retry = __('Retry'); return html`
${ this.model.getMessageText() }
${ this.model.get('reason') ? html`${this.model.get('reason')}` : `` } ${ this.model.get('error_text') ? html`${this.model.get('error_text')}` : `` } ${ this.model.get('retry_event_id') ? html`${i18n_retry}` : '' }
`; } renderFileProgress () { if (!this.model.file) { // Can happen when file upload failed and page was reloaded return ''; } const i18n_uploading = __('Uploading file:'); const filename = this.model.file.name; const size = filesize(this.model.file.size); return html`
${ renderAvatar(this.getAvatarData()) }
${i18n_uploading} ${filename}, ${size}
`; } renderChatMessage () { return tpl_message(this, this.getProps()); } shouldShowAvatar () { return api.settings.get('show_message_avatar') && !this.model.isMeCommand() && this.type !== 'headline'; } getAvatarData () { const image_type = this.model.vcard?.get('image_type') || _converse.DEFAULT_IMAGE_TYPE; const image_data = this.model.vcard?.get('image') || _converse.DEFAULT_IMAGE; const image = "data:" + image_type + ";base64," + image_data; return { 'classes': 'chat-msg__avatar', 'height': 36, 'width': 36, image, }; } onUnfurlAnimationEnd () { if (this.model.get('url_preview_transition') === 'fade-out') { this.model.save({ 'hide_url_previews': !this.model.get('hide_url_previews'), 'url_preview_transition': 'fade-in' }); } } async onRetryClicked () { this.show_spinner = true; this.requestUpdate(); await api.trigger(this.model.get('retry_event_id'), {'synchronous': true}); this.model.destroy(); this.parentElement.removeChild(this); } isRetracted () { return this.model.get('retracted') || this.model.get('moderated') === 'retracted'; } hasMentions () { const is_groupchat = this.model.get('type') === 'groupchat'; return is_groupchat && this.model.get('sender') === 'them' && this.chatbox.isUserMentioned(this.model); } getOccupantAffiliation () { return this.model.occupant?.get('affiliation'); } getOccupantRole () { return this.model.occupant?.get('role'); } getExtraMessageClasses () { const extra_classes = [ this.model.isFollowup() ? 'chat-msg--followup' : null, this.model.get('is_delayed') ? 'delayed' : null, this.model.isMeCommand() ? 'chat-msg--action' : null, this.isRetracted() ? 'chat-msg--retracted' : null, this.model.get('type'), this.shouldShowAvatar() ? 'chat-msg--with-avatar' : null, ].map(c => c); if (this.model.get('type') === 'groupchat') { extra_classes.push(this.getOccupantRole() ?? ''); extra_classes.push(this.getOccupantAffiliation() ?? ''); if (this.model.get('sender') === 'them' && this.hasMentions()) { extra_classes.push('mentioned'); } } this.model.get('correcting') && extra_classes.push('correcting'); return extra_classes.filter(c => c).join(" "); } getDerivedMessageProps () { const format = api.settings.get('time_format'); return { 'pretty_time': dayjs(this.model.get('edited') || this.model.get('time')).format(format), 'has_mentions': this.hasMentions(), 'hats': getHats(this.model), 'is_first_unread': this.chatbox.get('first_unread_id') === this.model.get('id'), 'is_me_message': this.model.isMeCommand(), 'is_retracted': this.isRetracted(), 'username': this.model.getDisplayName(), 'should_show_avatar': this.shouldShowAvatar(), } } getRetractionText () { if (this.model.get('type') === 'groupchat' && this.model.get('moderated_by')) { const retracted_by_mod = this.model.get('moderated_by'); const chatbox = this.model.collection.chatbox; if (!this.model.mod) { this.model.mod = chatbox.occupants.findOccupant({'jid': retracted_by_mod}) || chatbox.occupants.findOccupant({'nick': Strophe.getResourceFromJid(retracted_by_mod)}); } const modname = this.model.mod ? this.model.mod.getDisplayName() : 'A moderator'; return __('%1$s has removed this message', modname); } else { return __('%1$s has removed this message', this.model.getDisplayName()); } } renderRetraction () { const retraction_text = this.isRetracted() ? this.getRetractionText() : null; return html`
${retraction_text}
${ this.model.get('moderation_reason') ? html`${this.model.get('moderation_reason')}` : '' } `; } renderMessageText () { const i18n_edited = __('This message has been edited'); const i18n_show = __('Show more'); const is_groupchat_message = (this.model.get('type') === 'groupchat'); const i18n_show_less = __('Show less'); const tpl_spoiler_hint = html`
${this.model.get('spoiler_hint')} ${ this.model.get('is_spoiler_visible') ? i18n_show_less : i18n_show }
`; const spoiler_classes = this.model.get('is_spoiler') ? `spoiler ${this.model.get('is_spoiler_visible') ? '' : 'hidden'}` : ''; const text = this.model.getMessageText(); return html` ${ this.model.get('is_spoiler') ? tpl_spoiler_hint : '' } ${ this.model.get('subject') ? html`
${this.model.get('subject')}
` : '' } ${ (this.model.get('received') && !this.model.isMeCommand() && !is_groupchat_message) ? html`` : '' } ${ (this.model.get('edited')) ? html`` : '' } ${ this.model.get('oob_url') ? html`
${getOOBURLMarkup(this.model.get('oob_url'))}
` : '' }
${ this.model.get('error_text') || this.model.get('error') }
`; } showUserModal (ev) { if (this.model.get('sender') === 'me') { _converse.xmppstatusview.showProfileModal(ev); } else if (this.model.get('type') === 'groupchat') { ev.preventDefault(); api.modal.show(OccupantModal, { 'model': this.model.occupant }, ev); } else { ev.preventDefault(); const chatbox = this.model.collection.chatbox; api.modal.show(UserDetailsModal, { model: chatbox }, ev); } } showMessageVersionsModal (ev) { ev.preventDefault(); api.modal.show(MessageVersionsModal, {'model': this.model}, ev); } toggleSpoilerMessage (ev) { ev?.preventDefault(); this.model.save({'is_spoiler_visible': !this.model.get('is_spoiler_visible')}); } } api.elements.define('converse-chat-message', Message);