diff --git a/src/assets/translations/en.json b/src/assets/translations/en.json index 0e123ad..43b3e4d 100644 --- a/src/assets/translations/en.json +++ b/src/assets/translations/en.json @@ -314,6 +314,20 @@ "download_chat": "Download chat", "read_only_room": "Read only room", "read_only_room_info": "Only admins and moderators are allowed to send to the room", + "message_retention": "Disappearing messages", + "message_retention_info": "You can set a timeout for automatically removing messages from the room.\nNote: the server must support this feature.", + "message_retention_none": "Off", + "message_retention_1_hour": "1 Hour", + "message_retention_1_day": "1 Day", + "message_retention_1_week": "1 Week", + "message_retention_30_days": "30 Days", + "message_retention_365_days": "365 Days", + "message_retention_other": "Other", + "message_retention_other_seconds": "{count} Seconds", + "message_retention_other_seconds_unit": "Seconds", + "message_retention_other_required": "Value can not be empty", + "message_retention_other_too_small": "Value must be at least {count} seconds", + "message_retention_other_too_large": "Value can be at most {count} seconds", "make_public": "Make Public", "make_public_warning": "warning: Full message history will be visible to new participants", "direct_link": "My Direct Link", diff --git a/src/components/Chat.vue b/src/components/Chat.vue index e384cdc..e03c595 100644 --- a/src/components/Chat.vue +++ b/src/components/Chat.vue @@ -483,6 +483,11 @@ export default { Places: this.$t("emoji.categories.places") } }, + + /** + * A timer to handle message retention/auto deletion + */ + retentionTimer: null }; }, @@ -511,6 +516,10 @@ export default { this.$root.$off('audio-playback-ended', this.audioPlaybackEnded); this.$audioPlayer.pause(); this.stopRRTimer(); + if (this.retentionTimer) { + clearInterval(this.retentionTimer); + this.retentionTimer = null; + } }, destroyed() { @@ -774,6 +783,12 @@ export default { this.events.filter(event => (event.threadRootId && !event.parentThread)).forEach(event => this.setParentThread(event)); this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => this.setReplyToEvent(event)); console.log("Loading finished!"); + this.updateRetentionTimer(); + } else if (!value) { + if (this.retentionTimer) { + clearInterval(this.retentionTimer); + this.retentionTimer = null; + } } } }, @@ -871,6 +886,52 @@ export default { this.initialLoadDone = true; console.log("Loading finished!"); }, + + /** + * Set events to display. At the same time, filter out messages that are past rentention period etc. + */ + setEvents(events) { + this.events = this.removeTimedOutEvents(events); + }, + + updateRetentionTimer(maybeEvent) { + const retentionEvent = maybeEvent || (this.room && this.room.currentState.getStateEvents("m.room.retention", "")); + if (retentionEvent) { + const maxLifetime = parseInt(retentionEvent.getContent().max_lifetime); + if (maxLifetime) { + if (!this.retentionTimer) { + this.retentionTimer = setInterval(this.onRetentionTimer, 60000); + } + return; + } + } + if (this.retentionTimer) { + clearInterval(this.retentionTimer); + this.retentionTimer = null; + } + }, + + removeTimedOutEvents(events) { + const retentionEvent = this.room && this.room.currentState.getStateEvents("m.room.retention", ""); + let maxLifetime = 0; + if (retentionEvent) { + maxLifetime = parseInt(retentionEvent.getContent().max_lifetime); + } + return events.filter((e) => { + if (maxLifetime > 0 && !e.isState()) { // Keep all state events + return e.getLocalAge() < maxLifetime; + } + return true; + }); + }, + + onRetentionTimer() { + const events = this.removeTimedOutEvents(this.events); + if (events.length != this.events.length) { + this.events = events; // Changed + } + }, + onRoomJoined(initialEventId) { // Listen to events this.$matrix.on("Room.timeline", this.onEvent); @@ -885,7 +946,7 @@ export default { this.timelineWindow .load(initialEventId, 20) .then(() => { - self.events = self.timelineWindow.getEvents(); + self.setEvents(self.timelineWindow.getEvents()); const getMoreIfNeeded = function _getMoreIfNeeded() { const container = self.$refs.chatContainer; @@ -896,7 +957,7 @@ export default { self.timelineWindow.canPaginate(EventTimeline.BACKWARDS) ) { return self.timelineWindow.paginate(EventTimeline.BACKWARDS, 10, true, 5).then((success) => { - self.events = self.timelineWindow.getEvents(); + self.setEvents(self.timelineWindow.getEvents()); if (success) { return _getMoreIfNeeded.call(self); } else { @@ -943,7 +1004,7 @@ export default { this.onRoomJoined(null); } else { // Error. Done loading. - this.events = this.timelineWindow.getEvents(); + this.setEvents(this.timelineWindow.getEvents()); this.setInitialLoadDone(); } }) @@ -976,7 +1037,7 @@ export default { .then(() => { self.timelineSet = timelineSet; self.timelineWindow = timelineWindow; - self.events = self.timelineWindow.getEvents(); + self.setEvents(self.timelineWindow.getEvents()); }) .finally(() => { this.loading = false; @@ -1090,7 +1151,7 @@ export default { if (tl) { const parentEvent = tl.getEvents().find((e) => e.getId() === event.threadRootId); if (parentEvent) { - this.events = this.timelineWindow.getEvents(); + this.setEvents(this.timelineWindow.getEvents()); const fn = () => { Vue.set(parentEvent, "isMxThread", true); Vue.set(event, "parentThread", parentEvent); @@ -1123,7 +1184,7 @@ export default { if (tl) { const parentEvent = tl.getEvents().find((e) => e.getId() === event.replyEventId); if (parentEvent) { - this.events = this.timelineWindow.getEvents(); + this.setEvents(this.timelineWindow.getEvents()); const fn = () => {Vue.set(event, "replyEvent", parentEvent);}; if (this.initialLoadDone) { const sel = "[eventId=\"" + parentEvent.getId() + "\"]"; @@ -1162,7 +1223,7 @@ export default { this.paginateBackIfNeeded(); } - if (loadingDone && event.forwardLooking && (!event.isRelation() || event.isMxThread || event.threadRootId || event.parentThread )) { + if (loadingDone && event.forwardLooking && (!event.isRelation() || 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; @@ -1179,13 +1240,17 @@ export default { (event.getPrevContent() || {}).membership == "join" && ( (event.getContent().membership == "leave" && event.getSender() != this.currentUserId) || - (event.getContent().membership == "ban" )) - ) { - this.$store.commit("setCurrentRoomId", null); - const wasPurged = event.getContent().reason == "Room Deleted"; - this.$navigation.push({ name: "Goodbye", params: { roomWasPurged: wasPurged } }, -1); - } + (event.getContent().membership == "ban")) + ) { + this.$store.commit("setCurrentRoomId", null); + const wasPurged = event.getContent().reason == "Room Deleted"; + this.$navigation.push({ name: "Goodbye", params: { roomWasPurged: wasPurged } }, -1); } + + else if (event.getType() === "m.room.retention") { + this.updateRetentionTimer(event); + } + } }, onUserTyping(event, member) { @@ -1404,7 +1469,7 @@ export default { .then((success) => { if (success && this.scrollPosition) { this.scrollPosition.prepareFor("up"); - this.events = this.timelineWindow.getEvents(); + this.setEvents(this.timelineWindow.getEvents()); this.$nextTick(() => { // restore scroll position! console.log("Restore scroll!"); @@ -1429,7 +1494,7 @@ export default { .paginate(EventTimeline.FORWARDS, 10, true) .then((success) => { if (success) { - this.events = this.timelineWindow.getEvents(); + this.setEvents(this.timelineWindow.getEvents()); if (!this.useVoiceMode && this.scrollPosition) { this.scrollPosition.prepareFor("down"); this.$nextTick(() => { diff --git a/src/components/MessageRetentionDialog.vue b/src/components/MessageRetentionDialog.vue new file mode 100644 index 0000000..4792e24 --- /dev/null +++ b/src/components/MessageRetentionDialog.vue @@ -0,0 +1,219 @@ + + + + diff --git a/src/components/RoomInfo.vue b/src/components/RoomInfo.vue index 0ce0939..abc4280 100644 --- a/src/components/RoomInfo.vue +++ b/src/components/RoomInfo.vue @@ -159,6 +159,14 @@ v-model="readOnlyRoom" > + +
+
{{ $t('room_info.message_retention') }}
+
{{ $t('room_info.message_retention_info') }}
+
+
{{ messageRetention }}
+ edit +
@@ -261,6 +269,12 @@ @close="showPurgeConfirmation = false" /> + + @@ -268,6 +282,7 @@