@jsxc/jsxc
Version:
Real-time XMPP chat application with video calls, file transfer and encrypted communication
308 lines (238 loc) • 10.1 kB
text/typescript
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();
}
});
}
}