Resolve "when uploading multiple media files, send and display it as a thread"

This commit is contained in:
N Pex 2023-08-09 15:24:50 +00:00
parent a5ff54842c
commit 5ac61eac7c
7 changed files with 369 additions and 173 deletions

View file

@ -211,8 +211,11 @@
<v-card-title v-if="imageFiles.length > 1"> {{ $t('message.images') }} </v-card-title>
<v-card-text :class="{'ma-0 pa-2' : true, 'd-flex flex-wrap justify-center': imageFiles.length > 1}">
<div :class="{'col-4': imageFiles.length > 1}" v-for="(currentImageInput, id) in imageFiles" :key="id">
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
contain class="current-image-input-path" />
<div style="position: relative">
<v-img v-if="currentImageInput && currentImageInput.image" :aspect-ratio="1" :src="currentImageInput.image"
contain class="current-image-input-path" />
<v-progress-linear :style="{ position: 'absolute', left: '0', right: '0', bottom: '0', opacity: currentImageInput.sendInfo ? '1' : '0' }" :value="currentImageInput.sendInfo ? currentImageInput.sendInfo.progress : 0"></v-progress-linear>
</div>
<div>
<span v-if="currentImageInput && currentImageInput.scaled && currentImageInput.useScaled">
{{ currentImageInput.scaledDimensions.width }} x {{ currentImageInput.scaledDimensions.height }}</span>
@ -228,7 +231,7 @@
</span>
<v-switch v-if="currentImageInput && currentImageInput.scaled" :label="$t('message.scale_image')"
v-model="currentImageInput.useScaled" />
v-model="currentImageInput.useScaled" :disabled="currentImageInput.sendInfo" />
</div>
</div>
</v-card-text>
@ -240,6 +243,7 @@
<div v-if="!currentImageInputPath.type.includes('image/')">
<span> {{ $t('message.file') }}: {{ currentImageInputPath.name }}</span>
<span> ({{ formatBytes(currentImageInputPath.size) }})</span>
<v-progress-linear :style="{ opacity: currentImageInputPath.sendInfo ? '1' : '0' }" :value="currentImageInputPath.sendInfo ? currentImageInputPath.sendInfo.progress : 0"></v-progress-linear>
</div>
</div>
</v-card-text>
@ -249,13 +253,12 @@
<v-spacer>
<div v-if="currentSendError">{{ currentSendError }}</div>
<div v-if="currentSendErrorExceededFile" class="red--text">{{ currentSendErrorExceededFile }}</div>
<div v-else>{{ currentSendProgress }}</div>
</v-spacer>
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel">
<v-btn color="primary" text @click="cancelSendAttachment" id="btn-attachment-cancel" :disabled="sendingStatus != sendStatuses.SENDING && sendingStatus != sendStatuses.INITIAL">
{{ $t("menu.cancel") }}
</v-btn>
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment"
v-if="currentSendShowSendButton" :disabled="currentSendOperation != null">{{ $t("menu.send") }}</v-btn>
<v-btn id="btn-attachment-send" color="primary" text @click="sendAttachment(undefined)"
v-if="currentSendShowSendButton" :disabled="sendingStatus != sendStatuses.INITIAL">{{ $t("menu.send") }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
@ -354,6 +357,7 @@ import BottomSheet from "./BottomSheet.vue";
import ImageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
import chatMixin from "./chatMixin";
import sendAttachmentsMixin from "./sendAttachmentsMixin";
import AudioLayout from "./AudioLayout.vue";
import FileDropLayout from "./file_mode/FileDropLayout";
import { requestNotificationAndServiceWorker, windowNotificationPermission, notificationCount } from "../plugins/notificationAndServiceWorker.js"
@ -394,7 +398,7 @@ ScrollPosition.prototype.prepareFor = function (direction) {
export default {
name: "Chat",
mixins: [chatMixin, logoMixin, roomTypeMixin],
mixins: [chatMixin, logoMixin, roomTypeMixin, sendAttachmentsMixin],
components: {
ChatHeader,
MessageOperations,
@ -425,8 +429,6 @@ export default {
scrollPosition: null,
currentFileInputs: null,
currentSendOperation: null,
currentSendProgress: null,
currentSendShowSendButton: true,
currentSendError: null,
currentSendErrorExceededFile: null,
@ -1204,46 +1206,14 @@ export default {
this.$refs.stickerPickerSheet.open();
},
onUploadProgress(p) {
if (p.total) {
this.currentSendProgress = this.$t("message.upload_progress_with_total", {
count: p.loaded || 0,
total: p.total,
});
} else {
this.currentSendProgress = this.$t("message.upload_progress", {
count: p.loaded || 0,
});
}
},
sendAttachment(withText) {
this.$refs.attachment.value = null;
if (this.isCurrentFileInputsAnArray) {
let inputFiles = this.currentFileInputs.map(entry => {
// other than file type image
if(entry instanceof File) {
return entry;
} else {
if (entry.scaled && entry.useScaled) {
// Send scaled version of image instead!
return entry.scaled;
} else {
// Send actual file image when not scaled!
return entry.actualFile;
}
}
})
const promises = inputFiles.map(inputFile => util.sendImage(this.$matrix.matrixClient, this.roomId, inputFile, this.onUploadProgress));
this.currentSendOperation = promises;
Promise.all(promises).then(() => {
this.currentSendOperation = null;
const text = withText || "";
const promise = this.sendAttachments(text, this.currentFileInputs);
promise.then(() => {
this.currentFileInputs = null;
this.currentSendProgress = null;
if (withText) {
this.sendMessage(withText);
}
this.sendingStatus = this.sendStatuses.INITIAL;
})
.catch((err) => {
if (err.name === "AbortError" || err === "Abort") {
@ -1253,22 +1223,17 @@ export default {
this.currentSendError = err.LocaleString();
this.currentSendErrorExceededFile = err.LocaleString();
}
this.currentSendOperation = null;
this.currentSendProgress = null;
});
}
},
cancelSendAttachment() {
this.$refs.attachment.value = null;
if (this.currentSendOperation) {
this.currentSendOperation.forEach(o => o.abort());
}
this.currentSendOperation = null;
this.cancelSendAttachments();
this.currentFileInputs = null;
this.currentSendProgress = null;
this.currentSendError = null;
this.currentSendErrorExceededFile = null;
this.sendingStatus = this.sendStatuses.INITIAL;
},
addAttachment(file) {

View file

@ -14,6 +14,7 @@ import MessageOutgoingAudio from "./messages/MessageOutgoingAudio.vue";
import MessageOutgoingVideo from "./messages/MessageOutgoingVideo.vue";
import MessageOutgoingSticker from "./messages/MessageOutgoingSticker.vue";
import MessageOutgoingPoll from "./messages/MessageOutgoingPoll.vue";
import MessageOutgoingThread from "./messages/MessageOutgoingThread.vue";
import MessageIncomingImageExport from "./messages/export/MessageIncomingImageExport";
import MessageIncomingAudioExport from "./messages/export/MessageIncomingAudioExport";
import MessageIncomingVideoExport from "./messages/export/MessageIncomingVideoExport";
@ -66,6 +67,7 @@ export default {
MessageOutgoingAudio,
MessageOutgoingVideo,
MessageOutgoingSticker,
MessageOutgoingThread,
MessageOutgoingPoll,
ContactJoin,
ContactLeave,
@ -128,6 +130,13 @@ export default {
},
componentForEvent(event, isForExport = false) {
if (!event.isRelation() && !event.isRedaction() && event.isRedacted()) {
const redaction = event.getRedactionEvent();
if (redaction && redaction.content && redaction.content.reason === "cancel") {
return null; // Show nothing, it was canceled!
}
}
switch (event.getType()) {
case "m.room.member":
if (event.getContent().membership == "join") {
@ -188,6 +197,10 @@ export default {
}
return MessageIncomingText;
} else {
if (event.isThreadRoot || event.isThread) {
// Outgoing thread
return MessageOutgoingThread;
}
if (event.getContent().msgtype == "m.image") {
// For SVG, make downloadable
if (

View file

@ -44,7 +44,7 @@
sendCurrentTextMessage();
}
" />
<v-btn @click="send" :disabled="!attachments || attachments.length == 0">{{ $t("menu.send") }}</v-btn>
<v-btn @click="sendAll" :disabled="!attachments || attachments.length == 0">{{ $t("menu.send") }}</v-btn>
</div>
</div>
</template>
@ -54,13 +54,13 @@
v-if="attachments && attachments.length > 0 && (status == mainStatuses.SENDING || status == mainStatuses.SENT)">
<div class="attachment-wrapper">
<div class="file-drop-sent-stack" ref="stackContainer">
<div v-if="status == mainStatuses.SENDING && countSent == 0" class="no-items">
<div v-if="status == mainStatuses.SENDING && attachmentsSentCount == 0" class="no-items">
<div class="file-drop-stack-item direct" :style="stackItemTransform(null, -1)"></div>
<div>{{ $t('file_mode.sending_progress') }}</div>
</div>
<div v-else v-for="(item, index) in sentItems" :key="item.id" class="file-drop-stack-item animated"
:style="stackItemTransform(item, index)">
<v-img v-if="item.attachment && item.attachment.image" :src="item.attachment.image" />
<div v-else v-for="(info, index) in attachmentsSent" :key="info.id" class="file-drop-stack-item animated"
:style="stackItemTransform(info, index)">
<v-img v-if="info.preview" :src="info.preview" />
</div>
<div v-if="status == mainStatuses.SENT" class="items-sent" :style="stackItemTransform(null, -1)">
<v-icon>$vuetify.icons.ic_check_circle</v-icon>
@ -69,18 +69,18 @@
<!-- Middle section -->
<div v-if="status == mainStatuses.SENDING" class="file-drop-sending-container">
<div class="file-drop-sending-item" v-for="(info, index) in sendingItems" :key="index">
<v-img v-if="info.attachment && info.attachment.image" :src="info.attachment.image" />
<div class="file-drop-sending-item" v-for="(info, index) in attachmentsSending" :key="index">
<v-img v-if="info.preview" :src="info.preview" />
<div v-else class="filename">{{ info.attachment.name }}</div>
<v-progress-linear :value="info.progress"></v-progress-linear>
<div class="file-drop-cancel clickable" @click.stop="cancelSendingItem(info)">
<div class="file-drop-cancel clickable" @click.stop="cancelSendAttachmentItem(info)">
<v-icon size="14" color="white">close</v-icon>
</div>
</div>
</div>
<div v-else-if="status == mainStatuses.SENT" class="file-drop-sending-container">
<div class="file-drop-files-sent">{{ $tc((this.messageInput && this.messageInput.length > 0) ?
"file_mode.files_sent_with_note" : "file_mode.files_sent", sentItems.length) }}</div>
"file_mode.files_sent_with_note" : "file_mode.files_sent", attachmentsSent.length) }}</div>
<div class="file-drop-section">
<v-textarea disabled full-width solo flat auto-grow v-model="messageInput" no-resize class="input-area-text"
rows="1" hide-details background-color="transparent" />
@ -105,11 +105,11 @@
<script>
import messageMixin from "../messages/messageMixin";
import util from "../../plugins/utils";
import sendAttachmentsMixin from "../sendAttachmentsMixin";
const prettyBytes = require("pretty-bytes");
export default {
mixins: [messageMixin],
mixins: [messageMixin, sendAttachmentsMixin],
components: {},
props: {
attachments: {
@ -129,13 +129,6 @@ export default {
SENT: 2,
}),
status: 0,
statuses: Object.freeze({
INITIAL: 0,
SENT: 1,
CANCELED: 2,
FAILED: 3,
}),
sendInfo: [],
dropTarget: false,
};
},
@ -151,20 +144,6 @@ export default {
return this.currentItemIndex >= 0 && this.currentItemIndex < this.attachments.length &&
this.attachments[this.currentItemIndex].image
},
countSent() {
return this.sendInfo ? this.sendInfo.reduce((a, elem, ignoredidx, ignoredarray) => elem.status == this.statuses.SENT ? a + 1 : a, 0) : 0
},
sendingItems() {
return this.sendInfo ? this.sendInfo.filter(elem => elem.status == this.statuses.INITIAL) : []
},
sentItems() {
this.sortSendinfo();
return this.sendInfo ? this.sendInfo.filter(elem => elem.status == this.statuses.SENT) : []
},
sentItemsReversed() {
const array = this.sentItems;
return array.map((ignoreditem, idx) => array[array.length - 1 - idx])
}
},
watch: {
attachments(newValue, oldValue) {
@ -207,7 +186,7 @@ export default {
},
reset() {
this.$emit('reset');
this.sendInfo = [];
this.sendingAttachments = [];
this.status = this.mainStatuses.SELECTING;
this.messageInput = "";
this.currentItemIndex = 0;
@ -218,97 +197,13 @@ export default {
console.log("Error leaving", err);
});
},
send() {
sendAll() {
this.status = this.mainStatuses.SENDING;
this.sendInfo = this.attachments.map((attachment) => {
return {
id: attachment.name,
status: this.statuses.INITIAL,
statusDate: Date.now,
attachment: attachment.actualFile || attachment,
progress: 0,
randomRotation: 0,
randomTranslationX: 0,
randomTranslationY: 0
}
});
const text = (this.messageInput && this.messageInput.length > 0) ? this.messageInput : this.$t('file_mode.files');
util.sendTextMessage(this.$matrix.matrixClient, this.room.roomId, text)
.then((eventId) => {
// Use the eventId as a thread root for all the media
let promiseChain = Promise.resolve();
const getItemPromise = (index) => {
if (index < this.sendInfo.length) {
const item = this.sendInfo[index];
if (item.status !== this.statuses.INITIAL) {
return getItemPromise(++index);
}
const itemPromise = util.sendImage(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => {
if (loaded == total) {
item.progress = 100;
} else if (total > 0) {
item.progress = 100 * loaded / total;
}
}, eventId)
.then(() => {
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
let signR = 1;
let signX = 1;
let signY = 1;
if (this.sentItems.length > 0) {
if (this.sentItems[0].randomRotation >= 0) {
signR = -1;
}
if (this.sentItems[0].randomTranslationX >= 0) {
signX = -1;
}
if (this.sentItems[0].randomTranslationY >= 0) {
signY = -1;
}
}
item.randomRotation = signR * (2 + Math.random() * 10);
item.randomTranslationX = signX * Math.random() * 20;
item.randomTranslationY = signY * Math.random() * 20;
item.status = this.statuses.SENT;
item.statusDate = Date.now;
}).catch(ignorederr => {
if (item.promise.aborted) {
item.status = this.statuses.CANCELED;
} else {
console.error("ERROR", ignorederr);
item.status = this.statuses.FAILED;
}
});
item.promise = itemPromise;
return itemPromise.then(() => getItemPromise(++index));
}
else return Promise.resolve();
};
return promiseChain.then(() => getItemPromise(0));
})
this.sendAttachments((this.messageInput && this.messageInput.length > 0) ? this.messageInput : this.$t('file_mode.files'), this.attachments)
.then(() => {
this.status = this.mainStatuses.SENT;
})
.catch((err) => {
console.error("ERROR", err);
});
},
cancelSendingItem(item) {
if (item.promise && item.status == this.statuses.INITIAL) {
item.promise.abort();
}
item.status = this.statuses.CANCELED;
},
checkDone() {
if (!this.sendInfo.some(a => a.status == this.statuses.INITIAL)) {
this.status = this.mainStatuses.SENT;
}
},
sortSendinfo() {
this.sendInfo.sort((a, b) => b.statusDate - a.statusDate);
},
stackItemTransform(item, index) {
const size = 0.6 * (this.$refs.stackContainer ? Math.min(this.$refs.stackContainer.clientWidth, this.$refs.stackContainer.clientHeight) : 176);
let transform = ""

View file

@ -1,6 +1,15 @@
<template>
<message-incoming v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
<div class="bubble">
<div class="original-message" v-if="inReplyToText">
<div class="original-message-sender">
{{ $t('message.user_said', {user: inReplyToSender || "Someone"}) }}
</div>
<div
class="original-message-text"
v-html="linkify($sanitize(inReplyToText))"
/>
</div>
<div class="message">
<v-container fluid class="imageCollection">
<v-row wrap>

View file

@ -0,0 +1,155 @@
<template>
<message-outgoing v-bind="{ ...$props, ...$attrs }" v-on="$listeners">
<div class="bubble">
<div class="original-message" v-if="inReplyToText">
<div class="original-message-sender">
{{ $t('message.user_said', { user: inReplyToSender || "Someone" }) }}
</div>
<div class="original-message-text" v-html="linkify($sanitize(inReplyToText))" />
</div>
<div class="message">
<v-container fluid class="imageCollection">
<v-row wrap>
<v-col v-for="({ size, item }) in layoutedItems()" :key="item.event.getId()" :cols="size">
<v-img :aspect-ratio="16 / 9" :src="item.src" cover @click.stop="dialogSrc = item.src; dialog = true" />
</v-col>
</v-row>
</v-container>
<div style="text-align: end">
<v-btn class="download-all-button" @click.stop="downloadAll">{{ $t("message.download_all") }}&nbsp;<v-icon
color="white">arrow_downward</v-icon></v-btn>
</div>
<i v-if="event.isRedacted()" class="deleted-text">
<v-icon size="small">block</v-icon>
{{ $t('message.outgoing_message_deleted_text') }}
</i>
<span v-html="linkify($sanitize(messageText))" v-else />
<span class="edit-marker" v-if="event.replacingEventId() && !event.isRedacted()">
{{ $t('message.edited') }}
</span>
</div>
</div>
</message-outgoing>
</template>
<script>
import MessageOutgoing from "./MessageOutgoing.vue";
import messageMixin from "./messageMixin";
import util from "../../plugins/utils";
export default {
extends: MessageOutgoing,
components: { MessageOutgoing },
mixins: [messageMixin],
data() {
return {
items: [],
dialog: false,
dialogSrc: null,
thread: null,
}
},
mounted() {
this.thread = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), "m.thread", "m.room.message");
this.processThread();
},
beforeDestroy() {
this.thread = null;
},
watch: {
thread: {
handler(newValue, oldValue) {
if (oldValue) {
oldValue.off('Relations.add', this.onAddRelation);
}
if (newValue) {
newValue.on('Relations.add', this.onAddRelation);
}
this.processThread();
},
immediate: true
}
},
methods: {
onAddRelation() {
this.processThread();
},
processThread() {
this.items = this.timelineSet.relations.getAllChildEventsForEvent(this.event.getId()).map(e => {
let ret = {
event: e,
src: null,
};
ret.promise =
util
.getThumbnail(this.$matrix.matrixClient, e, 100, 100)
.then((url) => {
ret.src = url;
})
.catch((err) => {
console.log("Failed to fetch thumbnail: ", err);
});
return ret;
});
},
layoutedItems() {
if (!this.items || this.items.length == 0) { return [] }
let array = this.items.slice(0);
let rows = []
while (array.length > 0) {
if (array.length >= 7) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 12, item: array[2] });
rows.push({ size: 3, item: array[3] });
rows.push({ size: 3, item: array[4] });
rows.push({ size: 3, item: array[5] });
rows.push({ size: 3, item: array[6] });
array = array.slice(7);
} else if (array.length >= 3) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
rows.push({ size: 12, item: array[2] });
array = array.slice(3);
} else if (array.length >= 2) {
rows.push({ size: 6, item: array[0] });
rows.push({ size: 6, item: array[1] });
array = array.slice(2);
} else {
rows.push({ size: 12, item: array[0] });
array = array.slice(1);
}
}
return rows
},
downloadAll() {
this.items.forEach(item => util.download(this.$matrix.matrixClient, item.event));
}
}
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>
<style lang="scss" scoped>
.bubble {
width: 100%;
}
.imageCollection {
border-radius: 15px;
padding: 0;
overflow: hidden;
.row {
margin: -4px; // Compensate for column padding, so the border-radius above looks round!
padding: 0;
}
.col {
padding: 2px;
}
}
</style>

View file

@ -0,0 +1,159 @@
import util from "../plugins/utils";
export default {
data() {
return {
sendStatuses: Object.freeze({
INITIAL: 0,
SENDING: 1,
SENT: 2,
CANCELED: 3,
FAILED: 4,
}),
sendingStatus: 0,
sendingPromise: null,
sendingRootEventId: null,
sendingAttachments: [],
}
},
computed: {
attachmentsSentCount() {
return this.sendingAttachments ? this.sendingAttachments.reduce((a, elem, ignoredidx, ignoredarray) => elem.status == this.sendStatuses.SENT ? a + 1 : a, 0) : 0
},
attachmentsSending() {
return this.sendingAttachments ? this.sendingAttachments.filter(elem => elem.status == this.sendStatuses.INITIAL || elem.status == this.sendStatuses.SENDING) : []
},
attachmentsSent() {
this.sortSendingAttachments();
return this.sendingAttachments ? this.sendingAttachments.filter(elem => elem.status == this.sendStatuses.SENT) : []
},
},
methods: {
sendAttachments(text, attachments) {
this.sendingStatus = this.sendStatuses.SENDING;
this.sendingAttachments = attachments.map((attachment) => {
let file = (() => {
// other than file type image
if(attachment instanceof File) {
return attachment;
} else {
if (attachment.scaled && attachment.useScaled) {
// Send scaled version of image instead!
return attachment.scaled;
} else {
// Send actual file image when not scaled!
return attachment.actualFile;
}
}
})();
let sendInfo = {
id: attachment.name,
status: this.sendStatuses.INITIAL,
statusDate: Date.now,
attachment: file,
preview: attachment.image,
progress: 0,
randomRotation: 0,
randomTranslationX: 0,
randomTranslationY: 0
};
attachment.sendInfo = sendInfo;
return sendInfo;
});
this.sendingPromise = util.sendTextMessage(this.$matrix.matrixClient, this.room.roomId, text)
.then((eventId) => {
this.sendingRootEventId = eventId;
// Use the eventId as a thread root for all the media
let promiseChain = Promise.resolve();
const getItemPromise = (index) => {
if (index < this.sendingAttachments.length) {
const item = this.sendingAttachments[index];
if (item.status !== this.sendStatuses.INITIAL) {
return getItemPromise(++index);
}
const itemPromise = util.sendImage(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => {
if (loaded == total) {
item.progress = 100;
} else if (total > 0) {
item.progress = 100 * loaded / total;
}
}, eventId)
.then(() => {
// Look at last item rotation, flipping the sign on this, so looks more like a true stack
let signR = 1;
let signX = 1;
let signY = 1;
if (this.attachmentsSent.length > 0) {
if (this.attachmentsSent[0].randomRotation >= 0) {
signR = -1;
}
if (this.attachmentsSent[0].randomTranslationX >= 0) {
signX = -1;
}
if (this.attachmentsSent[0].randomTranslationY >= 0) {
signY = -1;
}
}
item.randomRotation = signR * (2 + Math.random() * 10);
item.randomTranslationX = signX * Math.random() * 20;
item.randomTranslationY = signY * Math.random() * 20;
item.status = this.sendStatuses.SENT;
item.statusDate = Date.now;
}).catch(ignorederr => {
if (item.promise.aborted) {
item.status = this.sendStatuses.CANCELED;
} else {
console.error("ERROR", ignorederr);
item.status = this.sendStatuses.FAILED;
}
});
item.promise = itemPromise;
return itemPromise.then(() => getItemPromise(++index));
}
else return Promise.resolve();
};
return promiseChain.then(() => getItemPromise(0));
})
.then(() => {
this.sendingStatus = this.sendStatuses.SENT;
})
.catch((err) => {
console.error("ERROR", err);
});
return this.sendingPromise;
},
cancelSendAttachments() {
this.sendingAttachments.toReversed().forEach(item => {
this.cancelSendAttachmentItem(item);
});
this.sendingStatus = this.sendStatuses.CANCELED;
if (this.sendingRootEventId && this.room) {
// Redact the root event.
this.$matrix.matrixClient
.redactEvent(this.room.roomId, this.sendingRootEventId, undefined, { reason: "cancel" })
.then(() => {
console.log("Message redacted");
})
.catch((err) => {
console.log("Redaction failed: ", err);
});
}
},
cancelSendAttachmentItem(item) {
if (item.promise && item.status != this.sendStatuses.INITIAL) {
item.promise.abort();
}
item.status = this.sendStatuses.CANCELED;
},
sortSendingAttachments() {
this.sendingAttachments.sort((a, b) => b.statusDate - a.statusDate);
},
}
}

View file

@ -21,7 +21,7 @@ class Stickers {
}
isStickerShortcode(messageBody) {
if (messageBody && messageBody.startsWith(":") && messageBody.startsWith(":") && messageBody.length >= 5) {
if (messageBody && typeof messageBody === "string" && messageBody.startsWith(":") && messageBody.startsWith(":") && messageBody.length >= 5) {
const image = this.getStickerImage(messageBody);
return image != undefined && image != null;
}