UNPKG

@jsxc/jsxc

Version:

Real-time XMPP chat application with video calls, file transfer and encrypted communication

308 lines (238 loc) 10.1 kB
import { IMessage, DIRECTION, MessageMark } from '../Message.interface'; import DateTime from './util/DateTime'; import ChatWindow from './ChatWindow'; import AvatarSet from './AvatarSet'; import Log from '../util/Log'; import LinkHandlerGeo from '@src/LinkHandlerGeo'; import Color from '@util/Color'; import Translation from '@util/Translation'; import messageHistory from './dialogs/messageHistory'; import MenuComponent from './MenuComponent'; import onLongPress from './util/LongPress'; let chatWindowMessageTemplate = require('../../template/chat-window-message.hbs'); export default class ChatWindowMessage { private element: JQuery<HTMLElement>; private message: IMessage; constructor(private originalMessage: IMessage, private chatWindow: ChatWindow, useLastVersion: boolean = true) { this.message = useLastVersion ? originalMessage.getLastVersion() : originalMessage; let template = chatWindowMessageTemplate({ id: this.message.getCssId(), direction: this.message.getDirectionString(), }); this.element = $(template); this.generateElement(); this.registerHooks(); this.initMenu(); } public getElement() { return this.element; } public restoreNextMessage() { let nextMessage = this.getNextMessage(); if (!nextMessage || nextMessage.getDOM().length > 0) { return; } let chatWindowMessage = this.chatWindow.getChatWindowMessage(nextMessage); let element = chatWindowMessage.getElement(); this.getElement().after(element); chatWindowMessage.restoreNextMessage(); } private getNextMessage() { let nextId = this.originalMessage.getNextId(); while (nextId) { let nextMessage = this.chatWindow.getTranscript().getMessage(nextId); if (!nextMessage) { Log.warn('Couldnt find next message.'); return; } if (!nextMessage.isReplacement()) { return nextMessage; } nextId = nextMessage.getNextId(); } } private async generateElement() { let bodyElement = $(await this.message.getProcessedBody()); LinkHandlerGeo.get().detect(bodyElement); this.element.find('.jsxc-content').html(bodyElement.get(0)); let timestampElement = this.element.find('.jsxc-timestamp'); DateTime.stringify(this.message.getStamp().getTime(), timestampElement); if (this.message.getDirection() === DIRECTION.OUT || this.message.getDirection() === DIRECTION.PROBABLY_OUT) { this.element.attr('data-mark', MessageMark[this.message.getMark()]); } if (this.message.isForwarded()) { this.element.addClass('jsxc-forwarded'); } if (this.message.isEncrypted()) { this.element.addClass('jsxc-encrypted'); } if (this.message.isUnread()) { this.element.addClass('jsxc-unread'); } if (this.message.isReplacement()) { this.element.addClass('jsxc-edited'); } this.element.find('.jsxc-version').on('click', () => { if (!this.element.hasClass('jsxc-edited')) { return; } messageHistory(this.originalMessage, this.chatWindow); }); if (this.message.getErrorMessage()) { this.element.addClass('jsxc-error'); this.element.find('.jsxc-error-content').text(Translation.t(this.message.getErrorMessage())); } if (this.message.hasAttachment()) { this.addAttachmentToElement(); } if (this.message.getDirection() === DIRECTION.SYS) { this.element.find('.jsxc-message-area').append('<div class="jsxc-clear"/>'); } else if (this.message.getDirection() === DIRECTION.IN) { let text = this.message.getSender().name || this.message.getPeer().bare; let lightness = 90; let color = Color.generate(text, undefined, 40, lightness); this.element.addClass(lightness < 60 ? 'jsxc-dark' : 'jsxc-light'); //lgtm [js/useless-comparison-test] this.element.css('--jsxc-message-bg', color); } let sender = this.message.getSender(); if (typeof sender.name === 'string') { this.addSenderToElement(); } } private addAttachmentToElement() { let attachment = this.message.getAttachment(); let mimeType = attachment.getMimeType(); let attachmentElement = $('<div>'); attachmentElement.addClass('jsxc-attachment'); attachmentElement.addClass('jsxc-' + mimeType.replace(/\//, '-')); attachmentElement.addClass('jsxc-' + mimeType.replace(/^([^/]+)\/.*/, '$1')); if (attachment.isPersistent()) { attachmentElement.addClass('jsxc-persistent'); } if (attachment.isImage()) { let imgElement = $('<img>') .attr('alt', 'preview') .attr('src', attachment.getThumbnailData() || '../images/placeholder_image.svg') .attr('title', attachment.getName()) .appendTo(attachmentElement); if (!attachment.hasThumbnailData()) { attachmentElement.addClass('jsxc-no-thumbnail'); } attachment.registerThumbnailHook(thumbnail => { imgElement.attr('src', thumbnail || '../images/placeholder_image.svg'); if (thumbnail) { this.element.find('.jsxc-attachment').removeClass('jsxc-no-thumbnail'); } else { this.element.find('.jsxc-attachment').addClass('jsxc-no-thumbnail'); } }); } else { attachmentElement.text(attachment.getName()); attachmentElement.removeClass('jsxc-' + mimeType.replace(/\//, '-')); } if (attachment.hasData()) { attachmentElement = $('<a target="_blank" rel="noopener noreferrer">').append(attachmentElement); attachmentElement.attr('href', attachment.getData()); attachmentElement.attr('download', attachment.getName()); if (attachment.getHandler()) { attachmentElement.on('click', ev => { ev.preventDefault(); attachmentElement.find('.jsxc-attachment').addClass('jsxc-attachment--loading'); attachment .getHandler() .call(undefined, attachment, true) .catch(err => { this.message.setErrorMessage(err.toString()); }) .then(() => { attachmentElement.find('.jsxc-attachment').removeClass('jsxc-attachment--loading'); }); }); } //@REVIEW this is a dirty hack let linkElement = this.element.find('.jsxc-content a:eq(0)'); if (linkElement.next().is('br')) { linkElement.next().remove(); } this.element.find('.jsxc-content a:eq(0)').remove(); } this.element.find('div').first().prepend(attachmentElement); } private addSenderToElement() { let sender = this.message.getSender(); let title = sender.name; if (sender.jid && sender.jid.bare) { title += '\n' + sender.jid.bare; } let senderElement = this.element.find('.jsxc-sender'); senderElement.text(sender.name + ': '); let avatarElement = $('<div>'); avatarElement.addClass('jsxc-avatar'); avatarElement.attr('title', title); this.element.prepend(avatarElement); this.element.attr('data-name', sender.name); let nextMessage = this.getNextMessage(); if (nextMessage && nextMessage.getSender().name === sender.name) { avatarElement.css('visibility', 'hidden'); return; } let contact = sender.jid && this.chatWindow.getContact(sender.jid); if (contact) { AvatarSet.get(contact).addElement(avatarElement); } else { AvatarSet.setPlaceholder(avatarElement, sender.name, sender.jid); } } private initMenu() { if (this.message.isSystem()) { return; } const messageMenu = this.chatWindow.getAccount().getChatMessageMenu(); const menuType = this.message.isOutgoing() ? 'vertical-right' : 'vertical-left'; const menu = new MenuComponent('more', menuType, messageMenu, [this.chatWindow.getContact(), this.message]); this.element.append(menu.getElement()); onLongPress(this.element, () => { menu.toggle(); }); } private registerHooks() { this.message.registerHook('replacedBy', () => { const chatWindowMessageReplacement = new ChatWindowMessage(this.originalMessage, this.chatWindow); this.element.replaceWith(chatWindowMessageReplacement.getElement()); }); this.message.registerHook('encrypted', encrypted => { if (encrypted) { this.element.addClass('jsxc-encrypted'); } else { this.element.removeClass('jsxc-encrypted'); } }); this.message.registerHook('unread', unread => { if (!unread) { this.element.removeClass('jsxc-unread'); } }); this.message.registerHook('progress', progress => { this.element.find('.jsxc-attachment').attr('data-progress', Math.round(progress * 100) + '%'); }); if (this.message.getDirection() === DIRECTION.OUT || this.message.getDirection() === DIRECTION.PROBABLY_OUT) { this.message.registerHook('mark', mark => { this.element.attr('data-mark', MessageMark[mark]); }); } this.message.registerHook('next', nextId => { if (nextId) { this.restoreNextMessage(); } }); this.message.registerHook('errorMessage', errorMessage => { if (errorMessage) { this.element.addClass('jsxc-error'); this.element.find('.jsxc-error-content').text(Translation.t(errorMessage)); } else { this.element.removeClass('jsxc-error'); this.element.find('.jsxc-error-content').empty(); } }); } }