@@ -274,6 +292,8 @@
+
{{ currentSendError }}
@@ -281,7 +301,7 @@
{{ $t("menu.cancel") }}
- {{ $t("menu.send") }}
@@ -360,7 +380,7 @@ import UserProfileDialog from "./UserProfileDialog.vue"
import BottomSheet from "./BottomSheet.vue";
import ImageResize from "image-resize";
import CreatePollDialog from "./CreatePollDialog.vue";
-import chatMixin from "./chatMixin";
+import chatMixin, { ROOM_READ_MARKER_EVENT_PLACEHOLDER } from "./chatMixin";
import sendAttachmentsMixin from "./sendAttachmentsMixin";
import AudioLayout from "./AudioLayout.vue";
import FileDropLayout from "./file_mode/FileDropLayout";
@@ -368,6 +388,7 @@ import roomTypeMixin from "./roomTypeMixin";
import roomMembersMixin from "./roomMembersMixin";
import PurgeRoomDialog from "../components/PurgeRoomDialog";
import MessageErrorHandler from "./MessageErrorHandler";
+import MessageOperationsChannel from './messages/channel/MessageOperationsChannel.vue';
const sizeOf = require("image-size");
const dataUriToBuffer = require("data-uri-to-buffer");
@@ -422,11 +443,15 @@ export default {
UserProfileDialog,
PurgeRoomDialog,
WelcomeHeaderChannelUser,
- MessageErrorHandler
+ MessageErrorHandler,
+ MessageOperationsChannel
},
data() {
return {
+ ROOM_TYPE_CHANNEL: ROOM_TYPE_CHANNEL,
+ ROOM_READ_MARKER_EVENT_PLACEHOLDER: ROOM_READ_MARKER_EVENT_PLACEHOLDER,
+
waitingForRoomObject: false,
events: [],
currentInput: "",
@@ -442,6 +467,7 @@ export default {
currentSendShowSendButton: true,
currentSendError: null,
currentSendErrorExceededFile: null,
+ attachmentCaption: undefined,
showEmojiPicker: false,
selectedEvent: null,
editedEvent: null,
@@ -512,7 +538,8 @@ export default {
heartPosition: {
top: 0,
left: 0
- }
+ },
+ reverseOrder: false
};
},
@@ -569,6 +596,12 @@ export default {
isCurrentFileInputsAnArray() {
return Array.isArray(this.currentFileInputs)
},
+ showAttachmentCaptionInput() {
+ // IFF we are sending one PDF, add option to set caption.
+ const imageFiles = this.imageFiles || [];
+ const otherFiles = this.nonImageFiles || [];
+ return imageFiles.length == 0 && otherFiles.length == 1 && (otherFiles[0].type === "application/pdf" || (otherFiles[0].name || "").endsWith(".pdf"));
+ },
currentFileInputsDialog: {
get() {
return this.isCurrentFileInputsAnArray
@@ -680,6 +713,8 @@ export default {
},
filteredEvents() {
+ let events = this.events;
+
if (this.room && this.$matrix.matrixClient.isRoomEncrypted(this.room.roomId)) {
if (this.room.getHistoryVisibility() == "joined") {
// For encrypted rooms where history is set to "joined" we can't read old events.
@@ -692,12 +727,46 @@ export default {
(!e.getPrevContent() || e.getPrevContent().membership != "join") &&
e.getStateKey() == this.$matrix.currentUserId) {
// Our own join event.
- return this.events.slice(idx + 1);
+ events = this.events.slice(idx + 1);
}
}
}
}
- return this.events;
+
+ // Filter out relations and redactions
+ events = this.events.toReversed().filter((e) => !e.isRelation() && !e.isRedaction());
+
+ // If Channel, remove all redacted events as well.
+ if (this.room && this.room.displayType == ROOM_TYPE_CHANNEL) {
+ events = events.filter((e) => !e.isRedacted());
+ }
+
+ // Add read marker, if it is not newer than the "latest" message we are going to display
+ //
+ let showReadMarker = false;
+ let lastDisplayedEvent = undefined;
+ events = events.flatMap((e) => {
+ let result = [];
+ Vue.set(e, "component", this.componentForEvent(e, false));
+ if (e.getId() == this.readMarker && showReadMarker) {
+ const readMarkerEvent = ROOM_READ_MARKER_EVENT_PLACEHOLDER;
+ Vue.set(readMarkerEvent, "component", this.componentForEvent(readMarkerEvent, false));
+ if (readMarkerEvent.component) {
+ Vue.set(e, "nextDisplayedEvent", lastDisplayedEvent);
+ }
+ result.push(readMarkerEvent);
+ }
+ if (e.component) {
+ Vue.set(e, "nextDisplayedEvent", lastDisplayedEvent);
+ lastDisplayedEvent = e;
+ if (e.getSender() !== this.$matrix.currentUserId) {
+ showReadMarker = true;
+ }
+ }
+ result.push(e);
+ return result;
+ })
+ return (this.reverseOrder ? events : events.toReversed()) // Reverse back if needed
},
roomCreatedByUsRecently() {
@@ -732,6 +801,12 @@ export default {
}
return null;
},
+ messageOperationsComponent() {
+ if (this.room.displayType == ROOM_TYPE_CHANNEL) {
+ return MessageOperationsChannel;
+ }
+ return MessageOperations;
+ },
chatContainerStyle() {
if (this.$config.chat_backgrounds && this.room && this.roomId) {
const roomType = this.isDirectRoom ? "direct" : this.isPublicRoom ? "public" : "invite";
@@ -843,7 +918,7 @@ export default {
this.onRoomNotJoined();
} else {
if (this.room) {
- this.onRoomJoined(this.readMarker);
+ this.onRoomJoined(this.roomDisplayType == ROOM_TYPE_CHANNEL ? null : this.readMarker);
} else {
this.waitingForRoomObject = true;
return; // no room, wait for it (we know we are joined so need to wait for sync to complete)
@@ -860,7 +935,7 @@ export default {
// Were we waiting?
if (this.room && this.room.roomId == this.roomId && this.waitingForRoomObject) {
this.waitingForRoomObject = false;
- this.onRoomJoined(this.readMarker);
+ this.onRoomJoined(this.roomDisplayType == ROOM_TYPE_CHANNEL ? null : this.readMarker);
}
},
showMessageOperations(show) {
@@ -876,6 +951,12 @@ export default {
var rectAnchor = this.showContextMenuAnchor.getBoundingClientRect();
var rectChat = this.$refs.messageOperationsStrut.getBoundingClientRect();
var rectOps = this.$refs.messageOperations.$el.getBoundingClientRect();
+ if (this.room.displayType == ROOM_TYPE_CHANNEL) {
+ top = rectAnchor.top - rectChat.top;
+ let right = rectChat.right - rectAnchor.right;
+ this.opStyle = "top:" + top + "px;right:" + right + "px";
+ return;
+ } else {
top = rectAnchor.top - rectChat.top - 50;
left = rectAnchor.left - rectChat.left - 75;
if (left + rectOps.width + 10 >= rectChat.right) {
@@ -884,6 +965,7 @@ export default {
left = 0;
}
}
+ }
}
this.opStyle = "top:" + top + "px;left:" + left + "px";
});
@@ -906,10 +988,32 @@ export default {
},
/**
- * Set events to display. At the same time, filter out messages that are past rentention period etc.
+ * Set events to display. At the same time, filter out messages that are past rentention period etc. Also, filter pinned events "at the top"
*/
- setEvents(events) {
- this.events = this.filterOutOldAndInvisible(events);
+ setEvents(events, onlyIfLengthChanges = false) {
+ let updated = this.filterOutOldAndInvisible(events);
+
+ // Handle pinning
+ //
+ if (this.room) {
+ const pinnedEvents = this.$matrix.getPinnedEvents(this.room);
+ updated.forEach((e) => {
+ Vue.set(e, "isPinned", pinnedEvents.includes(e.threadParent ? e.threadParent.getId() : e.getId()));
+ });
+
+ updated = updated.sort((e1, e2) => {
+ if (!e1.isPinned && !e2.isPinned) return 0;
+ else if (e1.isPinned && !e2.isPinned) return this.reverseOrder ? 1 : -1;
+ else if (e2.isPinned && !e1.isPinned) return this.reverseOrder ? -1 : 1;
+ else {
+ // Look at order in "pinned" value in the m.room.pinned_events event!
+ return pinnedEvents.indexOf(e1.getId()) < pinnedEvents.indexOf(e2.getId()) ? (this.reverseOrder ? 1 : -1) : (this.reverseOrder ? -1 : 1)
+ }
+ });
+ }
+ if (!onlyIfLengthChanges || updated.length != this.events.length) {
+ this.events = updated; // Changed
+ }
},
filterOutOldAndInvisible(events) {
@@ -952,10 +1056,7 @@ export default {
},
onRetentionTimer() {
- const events = this.filterOutOldAndInvisible(this.events);
- if (events.length != this.events.length) {
- this.events = events; // Changed
- }
+ this.setEvents(this.events, true);
},
onRoomJoined(initialEventId) {
@@ -969,6 +1070,9 @@ export default {
this.newlyJoinedRoom = joinEvent.getLocalAge() < 1 * 60000 /* 1 minute */;
}
+ this.reverseOrder = (this.room && this.roomDisplayType == ROOM_TYPE_CHANNEL);
+ Vue.set(this.room, "displayType", this.roomDisplayType);
+
// Listen to events
this.$matrix.on("Room.timeline", this.onEvent);
this.$matrix.on("RoomMember.typing", this.onUserTyping);
@@ -1080,7 +1184,7 @@ export default {
});
} else {
// Can't paginate, just scroll to bottom of window!
- this.smoothScrollToEnd();
+ this.smoothScrollToLatest();
}
},
@@ -1150,7 +1254,7 @@ export default {
this.$nextTick(() => {
const container = this.chatContainer;
if (container && container.scrollHeight <= container.clientHeight) {
- this.handleScrolledToTop();
+ this.handleScrolledToOldest();
}
});
},
@@ -1162,15 +1266,23 @@ export default {
const bufferHeight = container.clientHeight * WINDOW_BUFFER_SIZE;
if (container.scrollTop <= bufferHeight) {
// Scrolled to top
- this.handleScrolledToTop();
+ if (this.reverseOrder) {
+ this.handleScrolledToLatest(false);
+ } else {
+ this.handleScrolledToOldest();
+ }
} else if (container.scrollHeight - container.scrollTop.toFixed(0) - container.clientHeight <= bufferHeight) {
- this.handleScrolledToBottom(false);
+ if (this.reverseOrder) {
+ this.handleScrolledToOldest();
+ } else {
+ this.handleScrolledToLatest(false);
+ }
}
this.showScrollToEnd =
container.scrollHeight === container.clientHeight
? false
- : container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight ||
+ : (this.reverseOrder ? (container.scrollTop.toFixed(0) > 0) : (container.scrollHeight - container.scrollTop.toFixed(0) > container.clientHeight)) ||
(this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
this.restartRRTimer();
@@ -1260,16 +1372,19 @@ export default {
this.paginateBackIfNeeded();
}
- if (loadingDone && event.forwardLooking && (!event.isRelation() || event.isMxThread || event.threadRootId || event.parentThread)) {
+ if (loadingDone && event.forwardLooking && (!(event.isRelation() || event.isRedaction()) || event.isMxThread || event.threadRootId || event.parentThread)) {
// If we are at bottom, scroll to see new events...
var scrollToSeeNew = event.getSender() == this.$matrix.currentUserId; // When we sent, scroll
const container = this.chatContainer;
if (container) {
- if (container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
+ if (this.reverseOrder && container.scrollTop.toFixed(0) == 0) {
+ scrollToSeeNew = true;
+ }
+ else if (!this.reverseOrder && container.scrollHeight - container.scrollTop.toFixed(0) == container.clientHeight) {
scrollToSeeNew = true;
}
}
- this.handleScrolledToBottom(scrollToSeeNew);
+ this.handleScrolledToLatest(scrollToSeeNew);
// If kick or ban event, redirect to "goodbye"...
if (event.getType() === "m.room.member" &&
@@ -1441,6 +1556,7 @@ export default {
const promise = this.sendAttachments(text, this.currentFileInputs);
promise.then(() => {
this.currentFileInputs = null;
+ this.attachmentCaption = undefined;
this.sendingStatus = this.sendStatuses.INITIAL;
})
.catch((err) => {
@@ -1459,6 +1575,7 @@ export default {
this.$refs.attachment.value = null;
this.cancelSendAttachments();
this.currentFileInputs = null;
+ this.attachmentCaption = undefined;
this.currentSendError = null;
this.currentSendErrorExceededFile = null;
this.sendingStatus = this.sendStatuses.INITIAL;
@@ -1494,7 +1611,7 @@ export default {
});
},
- handleScrolledToTop() {
+ handleScrolledToOldest() {
if (
this.timelineWindow &&
this.timelineWindow.canPaginate(EventTimeline.BACKWARDS) &&
@@ -1505,7 +1622,7 @@ export default {
.paginate(EventTimeline.BACKWARDS, 10, true)
.then((success) => {
if (success && this.scrollPosition) {
- this.scrollPosition.prepareFor("up");
+ this.scrollPosition.prepareFor(this.reverseOrder ? "down" : "up");
this.setEvents(this.timelineWindow.getEvents());
this.$nextTick(() => {
// restore scroll position!
@@ -1520,7 +1637,7 @@ export default {
}
},
- handleScrolledToBottom(scrollToEnd) {
+ handleScrolledToLatest(smoothScrollToLatest) {
if (
this.timelineWindow &&
this.timelineWindow.canPaginate(EventTimeline.FORWARDS) &&
@@ -1533,13 +1650,13 @@ export default {
if (success) {
this.setEvents(this.timelineWindow.getEvents());
if (!this.useVoiceMode && this.scrollPosition) {
- this.scrollPosition.prepareFor("down");
+ this.scrollPosition.prepareFor(this.reverseOrder ? "up" : "down");
this.$nextTick(() => {
// restore scroll position!
console.log("Restore scroll!");
this.scrollPosition.restore();
- if (scrollToEnd) {
- this.smoothScrollToEnd();
+ if (smoothScrollToLatest) {
+ this.smoothScrollToLatest();
}
});
}
@@ -1555,6 +1672,7 @@ export default {
* Scroll so that the given event is at the middle of the chat view (if more events) or else at the bottom.
*/
scrollToEvent(eventId) {
+ console.log("Scroll to event", eventId);
const container = this.chatContainer;
const ref = this.$refs[eventId];
if (container && ref) {
@@ -1562,7 +1680,7 @@ export default {
const item = ref[0].getBoundingClientRect();
let offsetY = (parent.bottom - parent.top) / 2;
if (ref[0].clientHeight > offsetY) {
- offsetY = Math.max(0, (parent.bottom - parent.top) - ref[0].clientHeight);
+ offsetY = this.reverseOrder ? 0 : Math.max(0, (parent.bottom - parent.top) - ref[0].clientHeight);
}
const targetY = parent.top + offsetY;
const currentY = item.top;
@@ -1573,10 +1691,21 @@ export default {
}
},
- smoothScrollToEnd() {
+ smoothScrollToLatest() {
this.$nextTick(function () {
const container = this.chatContainer;
if (container && container.children.length > 0) {
+ if (this.reverseOrder) {
+ const firstChild = container.children[0];
+ console.log("Scroll into view", firstChild);
+ window.requestAnimationFrame(() => {
+ firstChild.scrollIntoView({
+ behavior: "smooth",
+ block: "end",
+ inline: "nearest",
+ });
+ });
+ } else {
const lastChild = container.children[container.children.length - 1];
console.log("Scroll into view", lastChild);
window.requestAnimationFrame(() => {
@@ -1587,6 +1716,7 @@ export default {
});
});
}
+ }
});
},
@@ -1668,6 +1798,16 @@ export default {
}
},
+ pin(event) {
+ const eventToPin = event.parentThread ? event.parentThread : event;
+ this.$matrix.setEventPinned(this.room, eventToPin, true);
+ },
+
+ unpin(event) {
+ const eventToUnpin = event.parentThread ? event.parentThread : event;
+ this.$matrix.setEventPinned(this.room, eventToUnpin, false);
+ },
+
cancelEditReply() {
this.currentInput = "";
this.editedEvent = null;
@@ -1809,8 +1949,14 @@ export default {
const elFirst = util.getFirstVisibleElement(container, (item) => item.hasAttribute("eventId"));
const elLast = util.getLastVisibleElement(container, (item) => item.hasAttribute("eventId"));
if (elFirst && elLast) {
- eventIdFirst = elFirst.getAttribute("eventId");
- eventIdLast = elLast.getAttribute("eventId");
+ if (this.reverseOrder) {
+ // For reverse order, the "first visible" is actually later in time, so swap them
+ eventIdFirst = elLast.getAttribute("eventId");
+ eventIdLast = elFirst.getAttribute("eventId");
+ } else {
+ eventIdFirst = elFirst.getAttribute("eventId");
+ eventIdLast = elLast.getAttribute("eventId");
+ }
}
}
if (eventIdFirst && eventIdLast) {
@@ -1964,9 +2110,9 @@ export default {
if (!this.useVoiceMode) { // Voice mode has own autoplay handling inside "AudioLayout"!
// Auto play consecutive audio messages, either incoming or sent.
const filteredEvents = this.filteredEvents;
- const index = filteredEvents.findIndex(e => e.getId() === matrixEventId);
- if (index >= 0 && index < (filteredEvents.length - 1)) {
- const nextEvent = filteredEvents[index + 1];
+ const event = filteredEvents.find(e => e.getId() === matrixEventId);
+ if (event && event.nextDisplayedEvent) {
+ const nextEvent = event.nextDisplayedEvent;
if (nextEvent.getContent().msgtype === "m.audio") {
// Yes, audio event!
this.$audioPlayer.play(nextEvent, this.timelineSet);
diff --git a/src/components/RoomExport.vue b/src/components/RoomExport.vue
index 6038c77..3750274 100644
--- a/src/components/RoomExport.vue
+++ b/src/components/RoomExport.vue
@@ -187,6 +187,7 @@ export default {
this.cancelled = true;
},
async getEvents() {
+ // TODO - Handle pinned messages?
const eventsPerBatch = 100;
let batchToken = null;
var nToFetch = null;
diff --git a/src/components/chatMixin.js b/src/components/chatMixin.js
index 076b48c..533649b 100644
--- a/src/components/chatMixin.js
+++ b/src/components/chatMixin.js
@@ -1,4 +1,4 @@
-import util, { STATE_EVENT_ROOM_DELETION_NOTICE } from "../plugins/utils";
+import util, { ROOM_TYPE_CHANNEL, STATE_EVENT_ROOM_DELETION_NOTICE } from "../plugins/utils";
import MessageIncomingText from "./messages/MessageIncomingText";
import MessageIncomingFile from "./messages/MessageIncomingFile";
import MessageIncomingImage from "./messages/MessageIncomingImage.vue";
@@ -53,10 +53,14 @@ import RoomGuestAccessChanged from "./messages/RoomGuestAccessChanged.vue";
import RoomEncrypted from "./messages/RoomEncrypted.vue";
import RoomDeletionNotice from "./messages/RoomDeletionNotice.vue";
import DebugEvent from "./messages/DebugEvent.vue";
+import ReadMarker from "./messages/ReadMarker.vue";
import roomDisplayOptionsMixin from "./roomDisplayOptionsMixin";
+import roomTypeMixin from "./roomTypeMixin";
+
+export const ROOM_READ_MARKER_EVENT_PLACEHOLDER = { getId: () => "ROOM_READ_MARKER" };
export default {
- mixins: [ roomDisplayOptionsMixin ],
+ mixins: [ roomDisplayOptionsMixin, roomTypeMixin ],
components: {
ChatHeader,
MessageIncomingText,
@@ -100,6 +104,7 @@ export default {
StickerPickerBottomSheet,
BottomSheet,
CreatePollDialog,
+ ReadMarker
},
methods: {
showDayMarkerBeforeEvent(event) {
@@ -125,8 +130,13 @@ export default {
},
componentForEvent(event, isForExport = false) {
+ const isChannel = this.roomDisplayType === ROOM_TYPE_CHANNEL;
+ if (event === ROOM_READ_MARKER_EVENT_PLACEHOLDER) {
+ return ReadMarker;
+ }
switch (event.getType()) {
case "m.room.member":
+ if (isChannel) break;
if (event.getContent().membership == "join") {
if (event.getPrevContent() && event.getPrevContent().membership == "join") {
// We we already joined, so this must be a display name and/or avatar update!
diff --git a/src/components/messages/MessageIncoming.vue b/src/components/messages/MessageIncoming.vue
index eb40a2c..6dd4a8f 100644
--- a/src/components/messages/MessageIncoming.vue
+++ b/src/components/messages/MessageIncoming.vue
@@ -1,7 +1,7 @@
-
+
{{ eventSenderDisplayName(event) }}
{{ utils.formatTime(event.event.origin_server_ts) }}
@@ -14,27 +14,34 @@
}}
-
+
-
+
+
\ No newline at end of file
diff --git a/src/components/messages/channel/MessageOperationsChannel.vue b/src/components/messages/channel/MessageOperationsChannel.vue
new file mode 100644
index 0000000..7a726ab
--- /dev/null
+++ b/src/components/messages/channel/MessageOperationsChannel.vue
@@ -0,0 +1,54 @@
+
+
+
+
+ $vuetify.icons.ic_edit
+
+ {{ $t("menu.edit") }}
+
+
+
+ $vuetify.icons.ic_pin
+
+ {{ $t("menu.pin") }}
+
+
+
+ $vuetify.icons.ic_pin
+
+ {{ $t("menu.unpin") }}
+
+
+
+ delete_outline
+
+ {{ $t("menu.delete") }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/messages/channel/QuickReactionsChannel.vue b/src/components/messages/channel/QuickReactionsChannel.vue
new file mode 100644
index 0000000..446251b
--- /dev/null
+++ b/src/components/messages/channel/QuickReactionsChannel.vue
@@ -0,0 +1,127 @@
+
+
+
+
+
+ $vuetify.icons.ic_like_filled
+
+ {{ $t("global.click_to_remove") }}
+
+ $vuetify.icons.ic_like {{ value.length }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/messages/channel/SwipeableThumbnailsView.vue b/src/components/messages/channel/SwipeableThumbnailsView.vue
new file mode 100644
index 0000000..fe1dcc6
--- /dev/null
+++ b/src/components/messages/channel/SwipeableThumbnailsView.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/messages/messageMixin.js b/src/components/messages/messageMixin.js
index 274589d..3340863 100644
--- a/src/components/messages/messageMixin.js
+++ b/src/components/messages/messageMixin.js
@@ -109,7 +109,7 @@ export default {
* 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()) {
+ if (!this.isPinned && 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
@@ -180,12 +180,20 @@ export default {
return this.event.getContent().body;
},
+ isPinned() {
+ return this.event.parentThread ? this.event.parentThread.isPinned : this.event.isPinned;
+ },
+
/**
- * Classes to set for the message. Currently only for "messageIn", TODO: - detect messageIn or messageOut.
+ * Classes to set for the message. Currently only for "messageIn"
*/
messageClasses() {
- return { messageIn: true, "from-admin": this.senderIsAdminOrModerator(this.event) };
+ if (this.incoming) {
+ return { messageIn: true, "from-admin": this.senderIsAdminOrModerator(this.event), "pinned": this.isPinned };
+ } else {
+ return { messageOut: true, "pinned": this.isPinned };
+ }
},
userAvatar() {
diff --git a/src/components/messages/messageOperationsMixin.js b/src/components/messages/messageOperationsMixin.js
index 5143615..289c051 100644
--- a/src/components/messages/messageOperationsMixin.js
+++ b/src/components/messages/messageOperationsMixin.js
@@ -50,5 +50,13 @@ export default {
this.$emit("close");
this.$emit("more", {event:this.event});
},
+ pin() {
+ this.$emit("close");
+ this.$emit("pin", {event:this.event});
+ },
+ unpin() {
+ this.$emit("close");
+ this.$emit("unpin", {event:this.event});
+ },
}
}
\ No newline at end of file
diff --git a/src/plugins/utils.js b/src/plugins/utils.js
index a641c47..56368b3 100644
--- a/src/plugins/utils.js
+++ b/src/plugins/utils.js
@@ -914,6 +914,7 @@ class Util {
}
download(matrixClient, event) {
+ console.log("DOWNLOAD");
this
.getAttachment(matrixClient, event)
.then((url) => {
@@ -924,6 +925,7 @@ class Util {
// PDFs are shown inline, not downloaded
link.download = event.getContent().body || this.$t("fallbacks.download_name");
}
+ console.log("LINK", link);
document.body.appendChild(link);
link.click();
setTimeout(function () {
diff --git a/src/services/matrix.service.js b/src/services/matrix.service.js
index 0ac52aa..3576223 100644
--- a/src/services/matrix.service.js
+++ b/src/services/matrix.service.js
@@ -1292,6 +1292,33 @@ export default {
});
this.notificationCount = count;
},
+
+ setEventPinned(room, event, pinned) {
+ if (room && room.currentState && event) {
+ const pinnedMessagesEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
+ const content = pinnedMessagesEvent ? pinnedMessagesEvent.getContent() : {}
+ let pinnedEvents = content["pinned"] || [];
+ if (pinned && !pinnedEvents.includes(event.getId())) {
+ pinnedEvents.push(event.getId());
+ } else if (!pinned && pinnedEvents.includes(event.getId())) {
+ pinnedEvents = pinnedEvents.filter((e) => e != event.getId());
+ } else {
+ return; // no change
+ }
+ content.pinned = pinnedEvents;
+ this.matrixClient.sendStateEvent(room.roomId, "m.room.pinned_events", content);
+ }
+ },
+
+ getPinnedEvents(room) {
+ if (room && room.currentState) {
+ const pinnedMessagesEvent = room.currentState.getStateEvents("m.room.pinned_events", "");
+ const content = pinnedMessagesEvent ? pinnedMessagesEvent.getContent() : {}
+ return content["pinned"] || [];
+ } else {
+ return [];
+ }
+ },
},
});