import QuickReactions from "./QuickReactions.vue"; var linkify = require("linkifyjs"); var linkifyHtml = require("linkifyjs/html"); linkify.options.defaults.className = "link"; linkify.options.defaults.target = { url: "_blank" }; export default { components: { QuickReactions, }, props: { room: { type: Object, default: function () { return null; }, }, originalEvent: { type: Object, default: function () { return {}; }, }, nextEvent: { type: Object, default: function () { return null; }, }, timelineSet: { type: Object, default: function () { return null; }, }, }, data() { return { event: {}, inReplyToEvent: null, inReplyToSender: null, }; }, mounted() { const relatesTo = this.validEvent && this.event.getWireContent()["m.relates_to"]; if (relatesTo && relatesTo["m.in_reply_to"]) { // Can we find the original message? const originalEventId = relatesTo["m.in_reply_to"].event_id; if (originalEventId && this.timelineSet) { const originalEvent = this.timelineSet.findEventById(originalEventId); if (originalEvent) { this.inReplyToEvent = originalEvent; this.inReplyToSender = this.eventSenderDisplayName(originalEvent); } } } }, watch: { originalEvent: { immediate: true, handler(originalEvent, ignoredOldValue) { this.event = originalEvent; // Check not null and not {} if (originalEvent && originalEvent.isBeingDecrypted && originalEvent.isBeingDecrypted()) { this.originalEvent.getDecryptionPromise().then(() => { this.event = originalEvent; }); } }, }, }, computed: { /** * * @returns true if event is non-null and contains data */ validEvent() { return this.event && Object.keys(this.event).length !== 0; }, incoming() { return this.event && this.event.getSender() != this.$matrix.currentUserId; }, /** * Don't show sender and time if the next event is within 2 minutes and also from us (= back to back messages) */ showSenderAndTime() { if (this.nextEvent && this.nextEvent.getSender() == this.event.getSender()) { const ts1 = this.nextEvent.event.origin_server_ts; const ts2 = this.event.event.origin_server_ts; return ts1 - ts2 < 2 * 60 * 1000; // less than 2 minutes } return true; }, inReplyToText() { const relatesTo = this.event.getWireContent()["m.relates_to"]; if (relatesTo && relatesTo["m.in_reply_to"]) { const content = this.event.getContent(); const lines = content.body.split("\n").reverse(); while (lines.length && !lines[0].startsWith("> ")) lines.shift(); // Reply fallback has a blank line after it, so remove it to prevent leading newline if (lines[0] === "") lines.shift(); const text = lines[0] && lines[0].replace(/^> (<.*> )?/g, ""); if (text) { return text; } if (this.inReplyToEvent) { var c = this.inReplyToEvent.getContent(); return c.body; } // We don't have the original text (at the moment at least) return this.$t("fallbacks.original_text"); } return null; }, messageText() { const relatesTo = this.event.getWireContent()["m.relates_to"]; if (relatesTo && relatesTo["m.in_reply_to"]) { const content = this.event.getContent(); // Remove the new text and strip "> " from the old original text const lines = content.body.split("\n"); while (lines.length && lines[0].startsWith("> ")) lines.shift(); // Reply fallback has a blank line after it, so remove it to prevent leading newline if (lines[0] === "") lines.shift(); return lines.join("\n"); } return this.event.getContent().body; }, /** * Classes to set for the message. Currently only for "messageIn", TODO: - detect messageIn or messageOut. */ messageClasses() { return { messageIn: true, "from-admin": this.senderIsAdminOrModerator(this.event) }; }, userAvatar() { if (!this.$matrix.userAvatar) { return null; } return this.$matrix.matrixClient.mxcUrlToHttp(this.$matrix.userAvatar, 80, 80, "scale", true); }, userAvatarLetter() { if (!this.$matrix.currentUser) { return null; } return (this.$matrix.currentUserDisplayName || this.$matrix.currentUserId.substring(1)) .substring(0, 1) .toUpperCase(); }, }, methods: { ownAvatarClicked() { this.$emit("own-avatar-clicked", { event: this.event }); }, otherAvatarClicked(avatarRef) { this.$emit("other-avatar-clicked", { event: this.event, anchor: avatarRef }); }, showContextMenu(buttonRef) { this.$emit("context-menu", { event: this.event, anchor: buttonRef }); }, /** * Get a display name given an event. */ eventSenderDisplayName(event) { if (event.getSender() == this.$matrix.currentUserId) { return this.$t("message.you"); } if (this.room) { const member = this.room.getMember(event.getSender()); if (member) { return member.name; } } return event.getContent().displayname || event.getSender(); }, /** * In the case where the state_key points out a userId for an operation (e.g. membership events) * return the display name of the affected user. * @param event * @returns */ eventStateKeyDisplayName(event) { if (event.getStateKey() == this.$matrix.currentUserId) { return this.$t("message.you"); } if (this.room) { const member = this.room.getMember(event.getStateKey()); if (member) { return member.name; } } return event.getStateKey(); }, messageEventAvatar(event) { if (this.room) { const member = this.room.getMember(event.getSender()); if (member) { return member.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), 40, 40, "scale", true); } } return null; }, /** * Return true if the event sender has a powel level > 0, e.g. is moderator or admin of some sort. */ senderIsAdminOrModerator(event) { if (this.room) { const member = this.room.getMember(event.getSender()); if (member) { return member.powerLevel > 0; } } return false; }, formatTime(time) { const date = new Date(); date.setTime(time); const today = new Date(); if ( date.getDate() == today.getDate() && date.getMonth() == today.getMonth() && date.getFullYear() == today.getFullYear() ) { // For today, skip the date part return date.toLocaleTimeString(); } return date.toLocaleString(); }, linkify(text) { return linkifyHtml(text); }, }, };