diff --git a/README.md b/README.md index 72b3262..2292d13 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ The app loads runtime configutation from the server at "./config.json" and merge * **logo** - An url or base64-encoded image data url that represents the app logotype. * **accentColor** - The accent color of the app UI. Use a HTML-style color value string, like "#ff0080". * **show_status_messages** - Whether to show only user joins/leaves and display name updates, or the full range of room status updates. Possible values are "never" (only the above), "moderators" (moderators will see all status updates) or "always" (everyone will see all status updates). Defaults to "always". +* **maxSizeAutoDownloads** - Attachments smaller than this will be auto downloaded. Default is 10Mb. ### Sticker short codes - To enable sticker short codes, follow these steps: diff --git a/package-lock.json b/package-lock.json index a3fe23c..beb2548 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "jszip": "^3.9.1", "linkify-html": "^4.1.0", "linkifyjs": "^4.1.0", - "material-design-icons-iconfont": "^6.1", + "material-design-icons-iconfont": "^6.7.0", "matrix-js-sdk": "^23.4.0", "md-gum-polyfill": "^1.0.0", "mic-recorder-to-mp3": "^2.2.2", @@ -9936,9 +9936,9 @@ } }, "node_modules/material-design-icons-iconfont": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.1.0.tgz", - "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz", + "integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA==" }, "node_modules/matrix-events-sdk": { "version": "0.0.1", @@ -24023,9 +24023,9 @@ } }, "material-design-icons-iconfont": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.1.0.tgz", - "integrity": "sha512-wRJtOo1v1ch+gN8PRsj0IGJznk+kQ8mz13ds/nuhLI+Qyf/931ZlRpd92oq0IRPpZIb+bhX8pRjzIVdcPDKmiQ==" + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz", + "integrity": "sha512-lSj71DgVv20kO0kGbs42icDzbRot61gEDBLQACzkUuznRQBUYmbxzEkGU6dNBb5fRWHMaScYlAXX96HQ4/cJWA==" }, "matrix-events-sdk": { "version": "0.0.1", diff --git a/package.json b/package.json index 1f0e05c..ea37d7d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "jszip": "^3.9.1", "linkify-html": "^4.1.0", "linkifyjs": "^4.1.0", - "material-design-icons-iconfont": "^6.1", + "material-design-icons-iconfont": "^6.7.0", "matrix-js-sdk": "^23.4.0", "md-gum-polyfill": "^1.0.0", "mic-recorder-to-mp3": "^2.2.2", diff --git a/public/index.html b/public/index.html index 89b189c..370dc29 100644 --- a/public/index.html +++ b/public/index.html @@ -7,15 +7,15 @@ <%= htmlWebpackPlugin.options.title %> - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/components/CreateRoom.vue b/src/components/CreateRoom.vue index 7825678..7835d2d 100644 --- a/src/components/CreateRoom.vue +++ b/src/components/CreateRoom.vue @@ -477,14 +477,25 @@ export default { // Set power level event. Need to do that here, because we might not have the userId when the options object is created. const powerLevels = {}; powerLevels[this.$matrix.currentUserId] = 100; + let powerLevelContent = { + users: powerLevels, + events_default: this.readOnlyRoom ? 50 : 0 + } + if (this.readOnlyRoom) { + powerLevelContent.events = { + "m.room.encrypted": 0, // NOTE! Since practically all events in encrypted rooms get sent as "m.room.encrypted" we need to set + // power to 0 here. Otherwise we would not be able to send quick reactions or poll responses... + "m.poll.response": 0, + "org.matrix.msc3381.poll.response": 0, + "m.reaction": 0, + "m.room.redaction": 0, + }; + } createRoomOptions.initial_state.push( { type: "m.room.power_levels", state_key: "", - content: { - users: powerLevels, - events_default: this.readOnlyRoom ? 50 : 0 - } + content: powerLevelContent }); return this.$matrix.matrixClient diff --git a/src/components/DeviceList.vue b/src/components/DeviceList.vue index 097ac5d..63e51ea 100644 --- a/src/components/DeviceList.vue +++ b/src/components/DeviceList.vue @@ -1,6 +1,5 @@ @@ -207,6 +256,8 @@ import util from "../plugins/utils"; import profileInfoMixin from "./profileInfoMixin"; import LogoutRoomDialog from './LogoutRoomDialog.vue'; import CopyLink from "./CopyLink.vue" +import { requestNotificationPermission, windowNotificationPermission } from "../plugins/notificationAndServiceWorker.js" +import { mapState } from 'vuex' export default { name: "Profile", @@ -234,7 +285,8 @@ export default { passwordErrorMessage: null, isAvatarLoaded: true, loadValue: 0, - newPasswordHasError: false + newPasswordHasError: false, + notificationDialog: false }; }, @@ -252,7 +304,13 @@ export default { this.newPassword2 && this.newPassword1 == this.newPassword2 ); - } + }, + notificationIcon() { + return this.globalNotification ? 'notifications_active' : 'notifications_off'; + }, + ...mapState([ + 'globalNotification' + ]) }, methods: { @@ -306,7 +364,53 @@ export default { console.log("Progress: " + JSON.stringify(progress)); }); }, + updateGlobalNotificationStore(flag) { + this.$store.commit('setGlobalNotification', flag); + }, + windowNotificationPermission, + onUpdateGlobalNotification(showAlertOrDialog = true) { + const permission = this.windowNotificationPermission(); + + switch (permission) { + case 'denied': + this.updateGlobalNotificationStore(false); + if (showAlertOrDialog) { + alert(this.$t("notification.blocked_message")); + } + break; + case 'granted': + this.updateGlobalNotificationStore(!this.globalNotification); + break; + case 'default': + if (showAlertOrDialog) { + this.notificationDialog = true; + } + this.updateGlobalNotificationStore(!this.globalNotification); + break; + default: + alert(this.$t("notification.not_supported")); + } + }, + async onNotifyDialog() { + const permission = await requestNotificationPermission() + if(permission === 'denied') { + this.updateGlobalNotificationStore(false); + alert(this.$t("notification.blocked_message")); + } else { + this.updateGlobalNotificationStore(true); + } + this.notificationDialog = false; + }, + onNotifyDialogClosed() { + this.updateGlobalNotificationStore(false); + this.notificationDialog = false; + } }, + mounted() { + if(this.globalNotification && this.windowNotificationPermission() !== 'granted') { + this.onUpdateGlobalNotification(false); + } + } }; diff --git a/src/components/RoomExport.vue b/src/components/RoomExport.vue index 769af2a..6038c77 100644 --- a/src/components/RoomExport.vue +++ b/src/components/RoomExport.vue @@ -84,11 +84,10 @@ import RoomEncrypted from "./messages/RoomEncrypted.vue"; import RoomDeletionNotice from "./messages/RoomDeletionNotice.vue"; import DebugEvent from "./messages/DebugEvent.vue"; import MessageOperations from "./messages/MessageOperations.vue"; -import AvatarOperations from "./messages/AvatarOperations.vue"; import ChatHeader from "./ChatHeader.vue"; import VoiceRecorder from "./VoiceRecorder.vue"; import RoomInfoBottomSheet from "./RoomInfoBottomSheet.vue"; -import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader.vue"; +import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom.vue"; import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet.vue"; import StickerPickerBottomSheet from "./StickerPickerBottomSheet.vue"; import BottomSheet from "./BottomSheet.vue"; @@ -137,11 +136,10 @@ export default { MessageOperations, VoiceRecorder, RoomInfoBottomSheet, - CreatedRoomWelcomeHeader, + WelcomeHeaderRoom, MessageOperationsBottomSheet, StickerPickerBottomSheet, BottomSheet, - AvatarOperations, CreatePollDialog, }, props: { @@ -264,7 +262,7 @@ export default { if (parentEvent) { Vue.set(parentEvent, "isMxThread", true); Vue.set(event, "parentThread", parentEvent); - } + } }); this.events.filter(event => (event.replyEventId && !event.replyEvent)).forEach(event => { const parentEvent = this.timelineSet.findEventById(event.replyEventId) || this.room.findEventById(event.replyEventId); @@ -287,6 +285,7 @@ export default { var imageFolder = zip.folder("images"); var audioFolder = zip.folder("audio"); var videoFolder = zip.folder("video"); + var filesFolder = zip.folder("files"); var downloadPromises = []; let components = this.$refs.exportedEvent; @@ -321,7 +320,8 @@ export default { for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { const img = images[imageIndex]; img.onerror = undefined; - img.src = './avatars/' + fileName; + img.removeAttribute("src"); + img.setAttribute("data-exported-src", './avatars/' + fileName); } } } @@ -421,13 +421,15 @@ export default { var extension = ".mp3"; let fileName = comp.event.getId() + extension; audioFolder.file(fileName, blob); // TODO calc bytes + //this.$nextTick(() => { let elements = comp.$el.getElementsByTagName("audio"); let element = elements && elements[0]; if (element) { - element.src = "./audio/" + fileName; + element.setAttribute("data-exported-src", "./audio/" + fileName); } this.processedEvents += 1; resolve(true); + //}); }); } }) @@ -449,13 +451,36 @@ export default { var extension = ".mp4"; let fileName = comp.event.getId() + extension; videoFolder.file(fileName, blob); // TODO calc bytes +// comp.src = "./video/" + fileName; let elements = comp.$el.getElementsByTagName("video"); let element = elements && elements[0]; if (element) { - element.src = "./video/" + fileName; + element.setAttribute("data-exported-src", "./video/" + fileName); } this.processedEvents += 1; - + resolve(true); + }); + } + }) + .catch((ignoredErr) => { + this.processedEvents += 1; + }) + ); + break; + case "MessageIncomingFileExport": + case "MessageOutgoingFileExport": + downloadPromises.push( + util + .getAttachment(this.$matrix.matrixClient, comp.event, null, true) + .then((blob) => { + if (currentMediaSize + blob.size <= maxMediaSize) { + currentMediaSize += blob.size; + return new Promise((resolve, ignoredReject) => { + var extension = util.getFileExtension(comp.event); + let fileName = comp.event.getId() + extension; + filesFolder.file(fileName, blob); + comp.href="./files/" + fileName; + this.processedEvents += 1; resolve(true); }); } @@ -504,7 +529,8 @@ export default { getCssRules(root); this.$nextTick(() => { - doc += this.$refs.exportRoot.outerHTML; + const contentHtml = this.$refs.exportRoot.outerHTML; + doc += contentHtml.replaceAll("data-exported-src=", "src="); doc += ""; zip.file("chat.html", doc); diff --git a/src/components/RoomInfo.vue b/src/components/RoomInfo.vue index 9b31d66..a43d74b 100644 --- a/src/components/RoomInfo.vue +++ b/src/components/RoomInfo.vue @@ -138,27 +138,45 @@ - -
{{ $t("room_info.experimental_features") }}
- -
-
-
{{ $t('room_info.room_type') }}
-
-
- -
-
-
- + + + @@ -166,58 +184,56 @@ >{{ $t("room_info.members") }}
{{ members.length }}
- -
- - - {{ - member.name.substring(0, 1).toUpperCase() - }} - - - {{ - member.userId == $matrix.currentUserId - ? $t("room_info.user_you", { - user: member.user ? member.user.displayName : member.name, - }) - : $t("room_info.user", { - user: member.user ? member.user.displayName : member.name, - }) - }} - - - {{ $t("room_info.user_admin") }} - - - {{ $t("room_info.user_moderator") }} - -
{{ $t("menu.start_private_chat") }}
-
-
{{ String.fromCharCode(160) }}
-
{{ $t("menu.user_kick") }}
-
{{ $t("menu.user_kick_and_ban") }}
-
-
{{ String.fromCharCode(160) }}
-
{{ $t("menu.user_make_admin") }}
-
{{ $t("menu.user_make_moderator") }}
-
{{ $t("menu.user_revoke_moderator") }}
- -
-
- {{ - showAllMembers ? $t("room_info.hide_all") : $t("room_info.show_all") - }} -
-
+ + + + + +
+ {{ showAllMembers ? $t("room_info.hide_all") : $t("room_info.show_all") }} +
@@ -235,6 +251,13 @@ {{ $t("room_info.version_info", { version: buildVersion }) }} + + + + @@ -254,11 +284,11 @@ + + \ No newline at end of file diff --git a/src/components/chatMixin.js b/src/components/chatMixin.js index c5a4c2a..0318df9 100644 --- a/src/components/chatMixin.js +++ b/src/components/chatMixin.js @@ -19,10 +19,12 @@ import MessageIncomingImageExport from "./messages/export/MessageIncomingImageEx import MessageIncomingAudioExport from "./messages/export/MessageIncomingAudioExport"; import MessageIncomingVideoExport from "./messages/export/MessageIncomingVideoExport"; import MessageIncomingThreadExport from "./messages/export/MessageIncomingThreadExport"; +import MessageIncomingFileExport from "./messages/export/MessageIncomingFileExport"; import MessageOutgoingImageExport from "./messages/export/MessageOutgoingImageExport"; import MessageOutgoingAudioExport from "./messages/export/MessageOutgoingAudioExport"; import MessageOutgoingVideoExport from "./messages/export/MessageOutgoingVideoExport"; import MessageOutgoingThreadExport from "./messages/export/MessageOutgoingThreadExport"; +import MessageOutgoingFileExport from "./messages/export/MessageOutgoingFileExport"; import ContactJoin from "./messages/ContactJoin.vue"; import ContactLeave from "./messages/ContactLeave.vue"; import ContactInvited from "./messages/ContactInvited.vue"; @@ -36,11 +38,10 @@ import RoomTopicChanged from "./messages/RoomTopicChanged.vue"; import RoomAvatarChanged from "./messages/RoomAvatarChanged.vue"; import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue"; import MessageOperations from "./messages/MessageOperations.vue"; -import AvatarOperations from "./messages/AvatarOperations.vue"; import ChatHeader from "./ChatHeader"; import VoiceRecorder from "./VoiceRecorder"; import RoomInfoBottomSheet from "./RoomInfoBottomSheet"; -import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader"; +import WelcomeHeaderRoom from "./welcome_headers/WelcomeHeaderRoom.vue"; import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet"; import stickers from "../plugins/stickers"; import StickerPickerBottomSheet from "./StickerPickerBottomSheet"; @@ -94,11 +95,10 @@ export default { MessageOperations, VoiceRecorder, RoomInfoBottomSheet, - CreatedRoomWelcomeHeader, + WelcomeHeaderRoom, MessageOperationsBottomSheet, StickerPickerBottomSheet, BottomSheet, - AvatarOperations, CreatePollDialog, }, methods: { @@ -172,6 +172,9 @@ export default { event.getContent().info.mimetype && event.getContent().info.mimetype.startsWith("image/svg") ) { + if (isForExport) { + return MessageIncomingFileExport; + } return MessageIncomingFile; } if (isForExport) { @@ -189,6 +192,9 @@ export default { } return MessageIncomingVideo; } else if (event.getContent().msgtype == "m.file") { + if (isForExport) { + return MessageIncomingFileExport; + } return MessageIncomingFile; } else if (stickers.isStickerShortcode(event.getContent().body)) { return MessageIncomingSticker; @@ -223,6 +229,9 @@ export default { } return MessageOutgoingVideo; } else if (event.getContent().msgtype == "m.file") { + if (isForExport) { + return MessageOutgoingFileExport; + } return MessageOutgoingFile; } else if (stickers.isStickerShortcode(event.getContent().body)) { return MessageOutgoingSticker; diff --git a/src/components/file_mode/ThumbnailView.vue b/src/components/file_mode/ThumbnailView.vue index 59a5e76..1756151 100644 --- a/src/components/file_mode/ThumbnailView.vue +++ b/src/components/file_mode/ThumbnailView.vue @@ -1,19 +1,22 @@ diff --git a/src/components/messages/AvatarOperations.vue b/src/components/messages/AvatarOperations.vue deleted file mode 100644 index 509ce2d..0000000 --- a/src/components/messages/AvatarOperations.vue +++ /dev/null @@ -1,37 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/components/messages/MessageIncoming.vue b/src/components/messages/MessageIncoming.vue index 456b1f9..6e945be 100644 --- a/src/components/messages/MessageIncoming.vue +++ b/src/components/messages/MessageIncoming.vue @@ -15,7 +15,7 @@ -
+
more_vert diff --git a/src/components/messages/MessageIncomingFile.vue b/src/components/messages/MessageIncomingFile.vue index 81b25c4..ef34564 100644 --- a/src/components/messages/MessageIncomingFile.vue +++ b/src/components/messages/MessageIncomingFile.vue @@ -10,12 +10,7 @@
- {{ $t('message.file_prefix') }} - + {{ $t('message.edited') }} @@ -25,11 +20,12 @@ diff --git a/src/components/messages/MessageIncomingImage.vue b/src/components/messages/MessageIncomingImage.vue index b73752f..732c584 100644 --- a/src/components/messages/MessageIncomingImage.vue +++ b/src/components/messages/MessageIncomingImage.vue @@ -39,7 +39,7 @@ export default { const width = this.$refs.image.$el.clientWidth; const height = (width * 9) / 16; util - .getThumbnail(this.$matrix.matrixClient, this.event, width, height) + .getThumbnail(this.$matrix.matrixClient, this.event, this.$config, width, height) .then((url) => { const info = this.event.getContent().info; // JPEGs use cover, PNG and GIF ect contain. This is because PNG and GIF are expected to diff --git a/src/components/messages/MessageIncomingThread.vue b/src/components/messages/MessageIncomingThread.vue index 85ffc0e..2bab13b 100644 --- a/src/components/messages/MessageIncomingThread.vue +++ b/src/components/messages/MessageIncomingThread.vue @@ -80,7 +80,7 @@ export default { }; ret.promise = util - .getThumbnail(this.$matrix.matrixClient, e, 100, 100) + .getThumbnail(this.$matrix.matrixClient, e, this.$config, 100, 100) .then((url) => { ret.src = url; }) diff --git a/src/components/messages/MessageIncomingVideo.vue b/src/components/messages/MessageIncomingVideo.vue index 180d53d..c7af6d8 100644 --- a/src/components/messages/MessageIncomingVideo.vue +++ b/src/components/messages/MessageIncomingVideo.vue @@ -10,6 +10,15 @@ {{ $t('message.download_progress',{percentage: downloadProgress}) }}
+
+
+ {{ fileName }} +
+
+ {{ fileSize }} +
+ download +
diff --git a/src/components/messages/MessageOperations.vue b/src/components/messages/MessageOperations.vue index 20d53a6..1eec76a 100644 --- a/src/components/messages/MessageOperations.vue +++ b/src/components/messages/MessageOperations.vue @@ -1,14 +1,14 @@ diff --git a/src/components/messages/export/MessageIncomingVideoExport.vue b/src/components/messages/export/MessageIncomingVideoExport.vue index 3727ae8..1716a36 100644 --- a/src/components/messages/export/MessageIncomingVideoExport.vue +++ b/src/components/messages/export/MessageIncomingVideoExport.vue @@ -2,7 +2,7 @@
- @@ -11,14 +11,14 @@ diff --git a/src/components/messages/export/MessageOutgoingAudioExport.vue b/src/components/messages/export/MessageOutgoingAudioExport.vue index b0f6906..f32c19a 100644 --- a/src/components/messages/export/MessageOutgoingAudioExport.vue +++ b/src/components/messages/export/MessageOutgoingAudioExport.vue @@ -1,18 +1,18 @@ diff --git a/src/components/messages/export/MessageOutgoingFileExport.vue b/src/components/messages/export/MessageOutgoingFileExport.vue new file mode 100644 index 0000000..0bcaa6f --- /dev/null +++ b/src/components/messages/export/MessageOutgoingFileExport.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/src/components/messages/export/MessageOutgoingVideoExport.vue b/src/components/messages/export/MessageOutgoingVideoExport.vue index be07c5d..8faf38f 100644 --- a/src/components/messages/export/MessageOutgoingVideoExport.vue +++ b/src/components/messages/export/MessageOutgoingVideoExport.vue @@ -2,7 +2,7 @@
- @@ -11,14 +11,14 @@ diff --git a/src/components/messages/export/exportedAttachmentMixin.js b/src/components/messages/export/exportedAttachmentMixin.js new file mode 100644 index 0000000..e3b72b6 --- /dev/null +++ b/src/components/messages/export/exportedAttachmentMixin.js @@ -0,0 +1,17 @@ +import util from "../../../plugins/utils"; + +export default { + data() { + return { + src: null, + } + }, + computed: { + fileName() { + return util.getFileName(this.event); + }, + fileSize() { + return util.getFileSizeFormatted(this.event); + } + }, +} \ No newline at end of file diff --git a/src/components/roomInfoMixin.js b/src/components/roomInfoMixin.js index 969589e..b8796a6 100644 --- a/src/components/roomInfoMixin.js +++ b/src/components/roomInfoMixin.js @@ -14,11 +14,43 @@ export default { editedRoomTopic: "", isRoomTopicEditMode: false, roomTopicErrorMessage: null, - } + messageRetentionDisplay: "", + retentionPeriods: [ + { + text: this.$t("room_info.message_retention_none"), + value: 0 + }, + { + text: this.$t("room_info.message_retention_4_week"), + value: 3600 * 24 * 28 * 1000 + }, + { + text: this.$t("room_info.message_retention_2_week"), + value: 3600 * 24 * 14 * 1000 + }, + { + text: this.$t("room_info.message_retention_1_week"), + value: 3600 * 24 * 7 * 1000 + }, + { + text: this.$t("room_info.message_retention_1_day"), + value: 3600 * 24 * 1000 + }, + { + text: this.$t("room_info.message_retention_8_hours"), + value: 3600 * 8 * 1000 + }, + { + text: this.$t("room_info.message_retention_1_hour"), + value: 3600 * 1000 + } + ] + }; }, mounted() { this.$matrix.on("Room.timeline", this.roomInfoMixinOnEvent); this.updatePermissions(); + this.updateMessageRetention(); }, destroyed() { @@ -61,7 +93,10 @@ export default { publicRoomLink() { if (this.room && this.roomJoinRule == "public") { return this.$router.getRoomLink( - this.room.getCanonicalAlias(), this.room.roomId, this.room.name, utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType) + this.room.getCanonicalAlias(), + this.room.roomId, + this.room.name, + utils.roomDisplayTypeToQueryParam(this.room, this.roomDisplayType) ); } return null; @@ -69,7 +104,7 @@ export default { roomHistory() { if (this.room) { - return this.room.shouldEncryptForInvitedMembers() + return this.room.shouldEncryptForInvitedMembers(); } return false; }, @@ -92,13 +127,24 @@ export default { */ privateParty() { if (this.isPrivate) { - const membersButMe = this.room.getMembers().filter(m => m.userId != this.$matrix.currentUserId); + const membersButMe = this.room.getMembers().filter((m) => m.userId != this.$matrix.currentUserId); if (membersButMe.length == 1) { return membersButMe[0]; } } return undefined; }, + + canViewRetentionPolicy() { + return true; + }, + + /** + * Return true if we can set message retention policy in the room. + */ + canChangeRetentionPolicy() { + return this.userCanExportChat; + }, }, watch: { room: { @@ -115,14 +161,57 @@ export default { }, }, methods: { + memberAvatar(member) { + if (member) { + return member.getAvatarUrl( + this.$matrix.matrixClient.getHomeserverUrl(), + 40, + 40, + "scale", + true + ); + } + return null; + }, + // TODO - following power level comparisons assume that default power levels are used in the room! + isAdmin(member) { + return member.powerLevelNorm > 50; + }, + isModerator(member) { + return member.powerLevelNorm > 0 && member.powerLevelNorm <= 50; + }, + /** + * Get a string describing current room retention setting. + * Can be "None", "1 week", "1 hour" etc... + */ + roomMessageRetentionDisplay(maybeEvent) { + const retention = this.roomMessageRetention(maybeEvent); + const retentionPeriodsFound = this.retentionPeriods.find(rp=>rp.value===retention) + if(retentionPeriodsFound) { + return retentionPeriodsFound.text + } + }, + + roomMessageRetention(maybeEvent) { + const retentionEvent = maybeEvent || (this.room && this.room.currentState.getStateEvents("m.room.retention", "")); + if (retentionEvent) { + console.log("Retention event found", JSON.stringify(retentionEvent)); + const maxLifetime = parseInt(retentionEvent.getContent().max_lifetime); + if (maxLifetime) { + return maxLifetime; + } + } + return 0; + }, + onRoomNameClicked() { - if(this.userCanPurgeRoom) { + if (this.userCanPurgeRoom) { this.isRoomNameEditMode = !this.isRoomNameEditMode; this.editedRoomName = this.roomName; } }, updateRoomName() { - if(this.editedRoomName) { + if (this.editedRoomName) { this.$matrix.matrixClient.setRoomName(this.room.roomId, this.editedRoomName); this.isRoomNameEditMode = !this.isRoomNameEditMode; } else { @@ -130,13 +219,13 @@ export default { } }, onRoomTopicClicked() { - if(this.userCanPurgeRoom) { + if (this.userCanPurgeRoom) { this.isRoomTopicEditMode = !this.isRoomTopicEditMode; this.editedRoomTopic = this.roomTopic; } }, updateRoomTopic() { - if(this.editedRoomTopic) { + if (this.editedRoomTopic) { this.$matrix.matrixClient.setRoomTopic(this.room.roomId, this.editedRoomTopic); this.isRoomTopicEditMode = !this.isRoomTopicEditMode; } else { @@ -154,14 +243,8 @@ export default { if (this.room) { this.roomJoinRule = this.getRoomJoinRule(); const canChangeAccess = - this.room.currentState.mayClientSendStateEvent( - "m.room.join_rules", - this.$matrix.matrixClient - ) && - this.room.currentState.mayClientSendStateEvent( - "m.room.guest_access", - this.$matrix.matrixClient - ); + this.room.currentState.mayClientSendStateEvent("m.room.join_rules", this.$matrix.matrixClient) && + this.room.currentState.mayClientSendStateEvent("m.room.guest_access", this.$matrix.matrixClient); this.userCanChangeJoinRule = canChangeAccess; this.userCanPurgeRoom = canChangeAccess; //TODO - need different permissions here? } else { @@ -172,29 +255,25 @@ export default { }, roomInfoMixinOnEvent(event) { - if (event.getRoomId() !== this.roomId) { - return; // Not for this room - } - if ( - event.getType() == "m.room.join_rules" || - event.getType() == "m.room.guest_access" - ) { - this.updatePermissions(); + if (this.room && this.room.roomId == event.getRoomId()) { + if (event.getType() == "m.room.join_rules" || event.getType() == "m.room.guest_access") { + this.updatePermissions(); + } else if (event.getType() == "m.room.retention") { + this.updateMessageRetention(event); + } } }, + updateMessageRetention(event) { + this.messageRetentionDisplay = this.roomMessageRetentionDisplay(event); + }, + privatePartyAvatar(size) { const other = this.privateParty; if (other) { - return other.getAvatarUrl( - this.$matrix.matrixClient.getHomeserverUrl(), - size, - size, - "scale", - true - ); + return other.getAvatarUrl(this.$matrix.matrixClient.getHomeserverUrl(), size, size, "scale", true); } return undefined; }, }, -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/roomTypeMixin.js b/src/components/roomTypeMixin.js index 24cef13..5b6ce3e 100644 --- a/src/components/roomTypeMixin.js +++ b/src/components/roomTypeMixin.js @@ -1,4 +1,4 @@ -import { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_DEFAULT, STATE_EVENT_ROOM_TYPE } from "../plugins/utils"; +import { ROOM_TYPE_VOICE_MODE, ROOM_TYPE_FILE_MODE, ROOM_TYPE_DEFAULT, STATE_EVENT_ROOM_TYPE, ROOM_TYPE_CHANNEL } from "../plugins/utils"; export default { data() { @@ -34,7 +34,7 @@ export default { if (e) { const roomType = e.getContent().type; // Validate value, or return default - if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE].includes(roomType)) { + if ([ROOM_TYPE_FILE_MODE, ROOM_TYPE_VOICE_MODE, ROOM_TYPE_CHANNEL].includes(roomType)) { this.roomDisplayType = roomType; } else { this.roomDisplayType = ROOM_TYPE_DEFAULT; diff --git a/src/components/sendAttachmentsMixin.js b/src/components/sendAttachmentsMixin.js index 3503a11..25b603b 100644 --- a/src/components/sendAttachmentsMixin.js +++ b/src/components/sendAttachmentsMixin.js @@ -74,7 +74,7 @@ export default { if (item.status !== this.sendStatuses.INITIAL) { return getItemPromise(++index); } - const itemPromise = util.sendImage(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => { + const itemPromise = util.sendFile(this.$matrix.matrixClient, this.room.roomId, item.attachment, ({ loaded, total }) => { if (loaded == total) { item.progress = 100; } else if (total > 0) { diff --git a/src/components/welcome_headers/WelcomeHeaderChannel.vue b/src/components/welcome_headers/WelcomeHeaderChannel.vue new file mode 100644 index 0000000..ae913ad --- /dev/null +++ b/src/components/welcome_headers/WelcomeHeaderChannel.vue @@ -0,0 +1,85 @@ + + + + + \ No newline at end of file diff --git a/src/components/DirectChatWelcomeHeader.vue b/src/components/welcome_headers/WelcomeHeaderDirectChat.vue similarity index 95% rename from src/components/DirectChatWelcomeHeader.vue rename to src/components/welcome_headers/WelcomeHeaderDirectChat.vue index 11da8c7..6ae80c9 100644 --- a/src/components/DirectChatWelcomeHeader.vue +++ b/src/components/welcome_headers/WelcomeHeaderDirectChat.vue @@ -7,10 +7,10 @@