keanu-weblite/src/components/messages/messageMixin.js
2023-10-25 10:44:25 +00:00

312 lines
8.9 KiB
JavaScript

import QuickReactions from "./QuickReactions.vue";
import * as linkify from 'linkifyjs';
import linkifyHtml from 'linkify-html';
import utils from "../../plugins/utils"
import Vue from "vue";
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;
},
},
componentFn: {
type: Function,
default: function () {
return () => {};
}
},
},
data() {
return {
event: {},
inReplyToEvent: null,
inReplyToSender: null,
utils
};
},
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);
}
}
}
},
beforeUnmount() {
if (this.validEvent) {
this.event.off("Event.relationsCreated", this.onRelationsCreated);
}
},
watch: {
event: {
immediate: true,
handler(newValue, oldValue) {
if (oldValue && oldValue.getId) {
oldValue.off("Event.relationsCreated", this.onRelationsCreated);
}
if (newValue && newValue.getId) {
newValue.on("Event.relationsCreated", this.onRelationsCreated);
if (newValue.isThreadRoot) {
Vue.set(newValue, "isThread", true);
}
}
}
},
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();
if ('body' in content) {
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();
if ('body' in content) {
// 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: {
onRelationsCreated(relationType, ignoredEventType) {
if (relationType === "m.thread") {
Vue.set(this.event, "isThread", true);
}
},
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;
},
formatTimeAgo(time) {
const date = new Date();
date.setTime(time);
var ti = Math.abs(new Date().getTime() - date.getTime());
ti = ti / 1000; // Convert to seconds
let s = "";
if (ti < 60) {
s = this.$t("global.time.recently");
} else if (ti < 3600 && Math.round(ti / 60) < 60) {
s = this.$tc("global.time.minutes", Math.round(ti / 60));
} else if (ti < 86400 && Math.round(ti / 60 / 60) < 24) {
s = this.$tc("global.time.hours", Math.round(ti / 60 / 60));
} else {
s = this.$tc("global.time.days", Math.round(ti / 60 / 60 / 24));
}
return this.toLocalNumbers(s);
},
/**
* Possibly convert numerals to local representation (currently only for "bo" locale)
* @param str String in which to convert numerals [0-9]
* @returns converted string
*/
toLocalNumbers(str) {
if (this.$i18n.locale == "bo") {
// Translate to tibetan numerals
let result = "";
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i);
if (c >= 48 && c <= 57) {
result += String.fromCharCode(c + 0x0f20 - 48);
} else {
result += String.fromCharCode(c);
}
}
return result;
}
return str;
},
linkify(text) {
return linkifyHtml(text);
},
},
};