diff --git a/html/src/app.js b/html/src/app.js index 5be10706..561f7b91 100644 --- a/html/src/app.js +++ b/html/src/app.js @@ -1650,6 +1650,26 @@ speechSynthesis.getVoices(); $app.updateCurrentUserLocation(); }; + API.applyPresenceGroups = function (ref) { + var groups = ref.presence?.groups; + if (!groups) { + console.error('API.applyPresenceGroups: invalid groups', ref); + return; + } + + // update group list + for (var groupId of groups) { + if (!this.currentUserGroups.has(groupId)) { + $app.onGroupJoined(groupId); + } + } + for (var groupId of this.currentUserGroups.keys()) { + if (!groups.includes(groupId)) { + $app.onGroupLeft(groupId); + } + } + }; + API.applyCurrentUser = function (json) { var ref = this.currentUser; if (this.isLoggedIn) { @@ -1673,19 +1693,7 @@ speechSynthesis.getVoices(); this.applyUserLanguage(ref); this.applyPresenceLocation(ref); this.applyQueuedInstance(ref.queuedInstance); - // update group list - if (json.presence?.groups) { - for (var groupId of json.presence.groups) { - if (!this.currentUserGroups.has(groupId)) { - $app.onGroupJoined(groupId); - } - } - for (var groupId of this.currentUserGroups.keys()) { - if (!json.presence.groups.includes(groupId)) { - $app.onGroupLeft(groupId); - } - } - } + this.applyPresenceGroups(ref); } else { ref = { acceptedPrivacyVersion: 0, @@ -1790,6 +1798,7 @@ speechSynthesis.getVoices(); this.applyUserTrustLevel(ref); this.applyUserLanguage(ref); this.applyPresenceLocation(ref); + this.applyPresenceGroups(ref); this.currentUser = ref; this.isLoggedIn = true; this.$emit('LOGIN', { @@ -3478,6 +3487,11 @@ speechSynthesis.getVoices(); API.$on('NOTIFICATION:V2', function (args) { var json = args.json; json.created_at = json.createdAt; + if (json.title && json.message) { + json.message = `${json.title}, ${json.message}`; + } else if (json.title) { + json.message = json.title; + } this.$emit('NOTIFICATION', { json, params: { @@ -3585,6 +3599,21 @@ speechSynthesis.getVoices(); }); }; + API.sendInviteGalleryPhoto = function (params, receiverUserId) { + return this.call(`invite/${receiverUserId}/photo`, { + method: 'POST', + params + }).then((json) => { + var args = { + json, + params, + receiverUserId + }; + this.$emit('NOTIFICATION:INVITE:GALLERYPHOTO:SEND', args); + return args; + }); + }; + API.sendRequestInvite = function (params, receiverUserId) { return this.call(`requestInvite/${receiverUserId}`, { method: 'POST', @@ -4987,6 +5016,7 @@ speechSynthesis.getVoices(); case 'group-role-updated': var groupId = content.role.groupId; + API.getGroup({ groupId, includeRoles: true }); console.log('group-role-updated', content); // content { @@ -5006,6 +5036,7 @@ speechSynthesis.getVoices(); case 'group-member-updated': var groupId = content.member.groupId; + API.getGroup({ groupId, includeRoles: true }); $app.onGroupJoined(groupId); console.log('group-member-updated', content); @@ -6665,6 +6696,9 @@ speechSynthesis.getVoices(); `${noty.previousDisplayName} changed their name to ${noty.displayName}` ); break; + case 'groupChange': + this.speak(`${noty.senderUsername} ${noty.message}`); + break; case 'group.announcement': this.speak(noty.message); break; @@ -6890,6 +6924,14 @@ speechSynthesis.getVoices(); image ); break; + case 'groupChange': + AppApi.XSNotification( + 'VRCX', + `${noty.senderUsername}: ${noty.message}`, + timeout, + image + ); + break; case 'group.announcement': AppApi.XSNotification('VRCX', noty.message, timeout, image); break; @@ -7214,6 +7256,16 @@ speechSynthesis.getVoices(); image ); break; + case 'groupChange': + AppApi.OVRTNotification( + playOvrtHudNotifications, + playOvrtWristNotifications, + 'VRCX', + `${noty.senderUsername}: ${noty.message}`, + timeout, + image + ); + break; case 'group.announcement': AppApi.OVRTNotification( playOvrtHudNotifications, @@ -7559,6 +7611,13 @@ speechSynthesis.getVoices(); image ); break; + case 'groupChange': + AppApi.DesktopNotification( + noty.senderUsername, + noty.message, + image + ); + break; case 'group.announcement': AppApi.DesktopNotification( 'Group Announcement', @@ -9863,6 +9922,7 @@ speechSynthesis.getVoices(); // eslint-disable-next-line require-atomic-updates $app.notificationTable.data = await database.getNotifications(); await this.refreshNotifications(); + await $app.loadCurrentUserGroups(); await $app.getCurrentUserGroups(); try { if ( @@ -15530,6 +15590,7 @@ speechSynthesis.getVoices(); Unfriend: 'On', DisplayName: 'VIP', TrustLevel: 'VIP', + groupChange: 'On', 'group.announcement': 'On', 'group.informative': 'On', 'group.invite': 'On', @@ -15569,6 +15630,7 @@ speechSynthesis.getVoices(); Unfriend: 'On', DisplayName: 'Friends', TrustLevel: 'Friends', + groupChange: 'On', 'group.announcement': 'On', 'group.informative': 'On', 'group.invite': 'On', @@ -15632,6 +15694,10 @@ speechSynthesis.getVoices(); $app.data.sharedFeedFilters.noty.External = 'On'; $app.data.sharedFeedFilters.wrist.External = 'On'; } + if (!$app.data.sharedFeedFilters.noty.groupChange) { + $app.data.sharedFeedFilters.noty.groupChange = 'On'; + $app.data.sharedFeedFilters.wrist.groupChange = 'On'; + } $app.data.trustColor = JSON.parse( await configRepository.getString( @@ -21069,7 +21135,7 @@ speechSynthesis.getVoices(); API.$on('VRCPLUSICON:ADD', function (args) { if (Object.keys($app.VRCPlusIconsTable).length !== 0) { - $app.VRCPlusIconsTable.push(args.json); + $app.VRCPlusIconsTable.unshift(args.json); } }); @@ -21105,6 +21171,7 @@ speechSynthesis.getVoices(); }; $app.methods.clearInviteImageUpload = function () { + this.clearImageGallerySelect(); var buttonList = document.querySelectorAll('.inviteImageUploadButton'); buttonList.forEach((button) => (button.value = '')); this.uploadImage = ''; @@ -24330,18 +24397,15 @@ speechSynthesis.getVoices(); mutualGroups: [], remainingGroups: [] }; - var params = { - n: 100, - offset: 0, - userId - }; - var args = await API.getGroups(params); + var args = await API.getGroups({ userId }); if (userId === API.currentUser.id) { // update current user groups API.currentUserGroups.clear(); args.json.forEach((group) => { - API.currentUserGroups.set(group.id, group); + var ref = API.applyGroup(group); + API.currentUserGroups.set(group.id, ref); }); + this.saveCurrentUserGroups(); } this.userGroups.groups = args.json; for (var i = 0; i < args.json.length; ++i) { @@ -24370,11 +24434,13 @@ speechSynthesis.getVoices(); }; $app.methods.getCurrentUserGroups = async function () { - var args = await API.getGroups({ n: 100, userId: API.currentUser.id }); + var args = await API.getGroups({ userId: API.currentUser.id }); API.currentUserGroups.clear(); args.json.forEach((group) => { - API.currentUserGroups.set(group.id, group); + var ref = API.applyGroup(group); + API.currentUserGroups.set(group.id, ref); }); + this.saveCurrentUserGroups(); }; $app.methods.sortCurrentUserGroups = function () { @@ -24556,7 +24622,7 @@ speechSynthesis.getVoices(); API.$on('GALLERYIMAGE:ADD', function (args) { if (Object.keys($app.galleryTable).length !== 0) { - $app.galleryTable.push(args.json); + $app.galleryTable.unshift(args.json); } }); @@ -24673,11 +24739,12 @@ speechSynthesis.getVoices(); API.$on('EMOJI:ADD', function (args) { if (Object.keys($app.emojiTable).length !== 0) { - $app.emojiTable.push(args.json); + $app.emojiTable.unshift(args.json); } }); - $app.data.emojiAnimFps = 5; + $app.data.emojiFrameCountOptions = [4, 16, 64]; + $app.data.emojiAnimFps = 15; $app.data.emojiAnimFrameCount = 4; $app.data.emojiAnimType = false; $app.data.emojiAnimationStyle = 'Stop'; @@ -28661,6 +28728,39 @@ speechSynthesis.getVoices(); }; this.cachedGroups.set(ref.id, ref); } else { + if (this.currentUserGroups.has(ref.id)) { + // compare group props + if (ref.ownerId && ref.ownerId !== json.ownerId) { + // owner changed + $app.groupOwnerChange(json, ref.ownerId, json.ownerId); + } + if (ref.name && ref.name !== json.name) { + // name changed + $app.groupChange( + json, + `Name changed from ${ref.name} to ${json.name}` + ); + } + if (ref.myMember?.roleIds && json.myMember?.roleIds) { + var oldRoleIds = ref.myMember.roleIds; + var newRoleIds = json.myMember.roleIds; + if ( + oldRoleIds.length !== newRoleIds.length || + !oldRoleIds.every( + (value, index) => value === newRoleIds[index] + ) + ) { + // roleIds changed + $app.groupRoleChange( + json, + ref.roles, + json.roles, + oldRoleIds, + newRoleIds + ); + } + } + } Object.assign(ref, json); } ref.rules = $app.replaceBioSymbols(ref.rules); @@ -28671,6 +28771,130 @@ speechSynthesis.getVoices(); return ref; }; + $app.methods.groupOwnerChange = async function (ref, oldUserId, newUserId) { + var oldUser = await API.getCachedUser({ + userId: oldUserId + }); + var newUser = await API.getCachedUser({ + userId: newUserId + }); + var oldDisplayName = oldUser?.ref?.displayName; + var newDisplayName = newUser?.ref?.displayName; + + this.groupChange( + ref, + `Owner changed from ${oldDisplayName} to ${newDisplayName}` + ); + }; + + $app.methods.groupRoleChange = function ( + ref, + oldRoles, + newRoles, + oldRoleIds, + newRoleIds + ) { + // check for removed/added roleIds + for (var roleId of oldRoleIds) { + if (!newRoleIds.includes(roleId)) { + var roleName = ''; + var role = oldRoles.find((fineRole) => fineRole.id === roleId); + if (role) { + roleName = role.name; + } + this.groupChange(ref, `Role ${roleName} removed`); + } + } + for (var roleId of newRoleIds) { + if (!oldRoleIds.includes(roleId)) { + var roleName = ''; + var role = newRoles.find((fineRole) => fineRole.id === roleId); + if (role) { + roleName = role.name; + } + this.groupChange(ref, `Role ${roleName} added`); + } + } + }; + + $app.methods.groupChange = function (ref, message) { + // oh the level of cursed for compibility + var json = { + id: Math.random().toString(36), + type: 'groupChange', + senderUserId: ref.id, + senderUsername: ref.name, + message, + created_at: new Date().toJSON() + }; + API.$emit('NOTIFICATION', { + json, + params: { + notificationId: json.id + } + }); + + // delay to wait for json to be assigned to ref + workerTimers.setTimeout(this.saveCurrentUserGroups, 100); + }; + + $app.methods.saveCurrentUserGroups = function () { + var groups = []; + for (var ref of API.currentUserGroups.values()) { + groups.push({ + id: ref.id, + name: ref.name, + ownerId: ref.ownerId, + roles: ref.roles, + roleIds: ref.myMember?.roleIds + }); + } + configRepository.setString( + `VRCX_currentUserGroups_${API.currentUser.id}`, + JSON.stringify(groups) + ); + }; + + $app.methods.loadCurrentUserGroups = async function () { + if ( + !(await configRepository.getString( + `VRCX_currentUserGroups_${API.currentUser.id}` + )) + ) { + // fetch every group with roles for storing and comparing later + for (var group of API.currentUserGroups.values()) { + await API.getGroup({ + groupId: group.id, + includeRoles: true + }); + } + this.saveCurrentUserGroups(); + return; + } + var groups = JSON.parse( + await configRepository.getString( + `VRCX_currentUserGroups_${API.currentUser.id}`, + '[]' + ) + ); + API.cachedGroups.clear(); + API.currentUserGroups.clear(); + for (var group of groups) { + var ref = { + id: group.id, + name: group.name, + iconUrl: '', + ownerId: group.ownerId, + roles: group.roles, + myMember: { + roleIds: group.roleIds + } + }; + API.cachedGroups.set(group.id, ref); + API.currentUserGroups.set(group.id, ref); + } + }; + API.applyGroupMember = function (json) { if (typeof json.user !== 'undefined') { var ref = this.cachedUsers.get(json.user.id); @@ -29058,7 +29282,12 @@ speechSynthesis.getVoices(); iconUrl: '' }); if (this.friendLogInitStatus) { - API.getGroup({ groupId }); + API.getGroup({ groupId, includeRoles: true }).then((args) => { + var ref = API.applyGroup(args.json); + API.currentUserGroups.set(groupId, ref); + this.saveCurrentUserGroups(); + return args; + }); } } }; @@ -29068,6 +29297,9 @@ speechSynthesis.getVoices(); this.showGroupDialog(groupId); } API.currentUserGroups.delete(groupId); + API.getCachedGroup({ groupId }).then((args) => { + this.groupChange(args.ref, 'Left group'); + }); }; // group search @@ -29473,6 +29705,9 @@ speechSynthesis.getVoices(); return; } var data = link.split(':'); + if (!data.length) { + return; + } switch (data[0]) { case 'group': this.showGroupDialog(data[1]); @@ -29773,23 +30008,31 @@ speechSynthesis.getVoices(); $app.data.gallerySelectDialog = { visible: false, - destenationFeild: '' + selectedFileId: '', + selectedImageUrl: '' }; - $app.methods.showGallerySelectDialog = function (destenationFeild) { + $app.methods.showGallerySelectDialog = function () { this.$nextTick(() => adjustDialogZ(this.$refs.gallerySelectDialog.$el)); var D = this.gallerySelectDialog; - D.destenationFeild = destenationFeild; D.visible = true; this.refreshGalleryTable(); }; $app.methods.selectImageGallerySelect = function (imageUrl, fileId) { var D = this.gallerySelectDialog; + D.selectedFileId = fileId; + D.selectedImageUrl = imageUrl; D.visible = false; console.log(imageUrl, fileId); }; + $app.methods.clearImageGallerySelect = function () { + var D = this.gallerySelectDialog; + D.selectedFileId = ''; + D.selectedImageUrl = ''; + }; + $app.methods.reportUserForHacking = function (userId) { API.reportUser({ userId, diff --git a/html/src/index.pug b/html/src/index.pug index c8b3d162..ba488cf9 100644 --- a/html/src/index.pug +++ b/html/src/index.pug @@ -862,7 +862,7 @@ html el-button(:type="avatarDialog.isBlocked ? 'danger' : 'default'" icon="el-icon-more" circle style="margin-left:5px") el-dropdown-menu(#default="dropdown") el-dropdown-item(icon="el-icon-refresh" command="Refresh") {{ $t('dialog.avatar.actions.refresh') }} - el-dropdown-item(icon="el-icon-check" command="Select Avatar") {{ $t('dialog.avatar.actions.select') }} + el-dropdown-item(icon="el-icon-check" :disabled="API.currentUser.currentAvatar === avatarDialog.id" command="Select Avatar") {{ $t('dialog.avatar.actions.select') }} el-dropdown-item(v-if="/quest/.test(avatarDialog.ref.tags)" icon="el-icon-check" command="Select Fallback Avatar") {{ $t('dialog.avatar.actions.select_fallback') }} el-dropdown-item(v-if="avatarDialog.isBlocked" icon="el-icon-circle-check" command="Unblock Avatar" style="color:#F56C6C") {{ $t('dialog.avatar.actions.unblock') }} el-dropdown-item(v-else icon="el-icon-circle-close" command="Block Avatar") {{ $t('dialog.avatar.actions.block') }} @@ -1839,6 +1839,11 @@ html el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} el-radio-button(label="VIP") {{ $t('dialog.shared_feed_filters.favorite') }} el-radio-button(label="Friends") {{ $t('dialog.shared_feed_filters.friends') }} + .toggle-item + span.toggle-name Group Change + el-radio-group(v-model="sharedFeedFilters.noty.groupChange" size="mini" @change="saveSharedFeedFilters") + el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} + el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} .toggle-item span.toggle-name Group Announcement el-radio-group(v-model="sharedFeedFilters.noty['group.announcement']" size="mini" @change="saveSharedFeedFilters") @@ -2066,6 +2071,11 @@ html el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} el-radio-button(label="VIP") {{ $t('dialog.shared_feed_filters.favorite') }} el-radio-button(label="Friends") {{ $t('dialog.shared_feed_filters.friends') }} + .toggle-item + span.toggle-name Group Change + el-radio-group(v-model="sharedFeedFilters.wrist.groupChange" size="mini" @change="saveSharedFeedFilters") + el-radio-button(label="Off") {{ $t('dialog.shared_feed_filters.off') }} + el-radio-button(label="On") {{ $t('dialog.shared_feed_filters.on') }} .toggle-item span.toggle-name Group Announcement el-radio-group(v-model="sharedFeedFilters.wrist['group.announcement']" size="mini" @change="saveSharedFeedFilters") @@ -2253,6 +2263,14 @@ html //- dialog Table: Send Invite Message el-dialog.x-dialog(:before-close="beforeDialogClose" @mousedown.native="dialogMouseDown" @mouseup.native="dialogMouseUp" ref="sendInviteDialog" :visible.sync="sendInviteDialogVisible" :title="$t('dialog.invite_message.header')" width="800px") template(v-if="API.currentUser.$isVRCPlus") + //- template(v-if="gallerySelectDialog.selectedFileId") + //- div(style="display:inline-block;flex:none;margin-right:5px") + //- el-popover(placement="right" width="500px" trigger="click") + //- img.x-link(slot="reference" v-lazy="gallerySelectDialog.selectedImageUrl" style="flex:none;width:60px;height:60px;border-radius:4px;object-fit:cover") + //- img.x-link(v-lazy="gallerySelectDialog.selectedImageUrl" style="height:500px" @click="showFullscreenImageDialog(gallerySelectDialog.selectedImageUrl)") + //- el-button(size="mini" @click="clearImageGallerySelect" style="vertical-align:top") {{ $t('dialog.invite_message.clear_selected_image') }} + //- template(v-else) + //- el-button(size="mini" @click="showGallerySelectDialog" style="margin-right:5px") {{ $t('dialog.invite_message.select_image') }} input.inviteImageUploadButton(type="file" accept="image/png" @change="inviteImageUpload") data-tables(v-if="sendInviteDialogVisible" v-bind="inviteMessageTable" @row-click="showSendInviteConfirmDialog" style="margin-top:10px;cursor:pointer") el-table-column(:label="$t('table.profile.invite_messages.slot')" prop="slot" sortable="custom" width="70") @@ -2394,8 +2412,11 @@ html template(v-if="emojiAnimType") span(style="margin-right:10px") {{ $t('dialog.gallery_icons.emoji_animation_fps') }} el-input(v-model="emojiAnimFps" :min="1" :max="64" size="small" style="width:48px;margin-right:10px") - span(style="margin-right:10px") {{ $t('dialog.gallery_icons.emoji_animation_frame_count') }} - el-input(v-model="emojiAnimFrameCount" :min="1" :max="64" size="small" style="width:48px;margin-right:10px") + el-dropdown(@click.native.stop trigger="click" size="small" style="margin-right:5px") + el-button(size="mini") + span {{ $t('dialog.gallery_icons.emoji_animation_frame_count') }} {{ emojiAnimFrameCount }} #[i.el-icon-arrow-down.el-icon--right] + el-dropdown-menu(#default="dropdown") + el-dropdown-item(v-for="(item) in emojiFrameCountOptions" v-text="item" @click.native="emojiAnimFrameCount = item") br span {{ $t('dialog.gallery_icons.flipbook_info') }} br diff --git a/html/src/localization/en/en.json b/html/src/localization/en/en.json index c7bbf847..e666eeac 100644 --- a/html/src/localization/en/en.json +++ b/html/src/localization/en/en.json @@ -1168,6 +1168,8 @@ "invite_message": { "header": "Send Invite Message", "confirmation": "Are you sure you want to send?", + "select_image": "Select Image", + "clear_selected_image": "Clear Selected Image", "cancel": "Cancel", "refresh": "Refresh", "confirm": "Confirm" @@ -1214,7 +1216,7 @@ "emoji_animation_type": "Animated Emoji", "emoji_animation_fps": "FPS:", "emoji_animation_frame_count": "Frame Count:", - "flipbook_info": "Select a flipbook texture PNG to use as an animated emoji" + "flipbook_info": "Select a 1024x1024 PNG spritesheet to use as an animated emoji (max FPS 64)" }, "change_content_image": { "avatar": "Change Avatar Image", @@ -1571,7 +1573,7 @@ "notification": { "date": "Date", "type": "Type", - "user": "User", + "user_group": "User/Group", "photo": "Photo", "message": "Message", "action": "Action" diff --git a/html/src/mixins/tabs/notifications.pug b/html/src/mixins/tabs/notifications.pug index edf9f9d7..5d2c5bf0 100644 --- a/html/src/mixins/tabs/notifications.pug +++ b/html/src/mixins/tabs/notifications.pug @@ -4,7 +4,7 @@ mixin notificationsTab() template(#tool) div(style="margin:0 0 10px;display:flex;align-items:center") el-select(v-model="notificationTable.filters[0].value" @change="saveTableFilters" multiple clearable collapse-tags style="flex:1" :placeholder="$t('view.notification.filter_placeholder')") - el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.queueReady', 'moderation.warning.group', 'instance.closed']" :key="type" :label="type" :value="type") + el-option(v-once v-for="type in ['requestInvite', 'invite', 'requestInviteResponse', 'inviteResponse', 'friendRequest', 'hiddenFriendRequest', 'message', 'groupChange', 'group.announcement', 'group.informative', 'group.invite', 'group.joinRequest', 'group.queueReady', 'moderation.warning.group', 'instance.closed']" :key="type" :label="type" :value="type") el-input(v-model="notificationTable.filters[1].value" :placeholder="$t('view.notification.search_placeholder')" style="flex:none;width:150px;margin:0 10px") el-tooltip(placement="bottom" :content="$t('view.notification.refresh_tooltip')" :disabled="hideTooltips") el-button(type="default" :loading="API.isNotificationsLoading" @click="API.refreshNotifications()" icon="el-icon-refresh" circle style="flex:none") @@ -28,9 +28,16 @@ mixin notificationsTab() el-tooltip(placement="top" :content="scope.row.linkText" :disabled="hideTooltips") span.x-link(v-text="scope.row.type" @click="openNotificationLink(scope.row.link)") span(v-else v-text="scope.row.type") - el-table-column(:label="$t('table.notification.user')" prop="senderUsername" width="150") + el-table-column(:label="$t('table.notification.user_group')" prop="senderUsername" width="150") template(v-once #default="scope") - span.x-link(v-text="scope.row.senderUsername" @click="showUserDialog(scope.row.senderUserId)") + template(v-if="scope.row.type === 'groupChange'") + span.x-link(v-text="scope.row.senderUsername" @click="showGroupDialog(scope.row.senderUserId)") + template(v-else-if="scope.row.senderUserId") + span.x-link(v-text="scope.row.senderUsername" @click="showUserDialog(scope.row.senderUserId)") + template(v-else-if="scope.row.link && scope.row.data?.groupName") + span.x-link(v-text="scope.row.data?.groupName" @click="openNotificationLink(scope.row.link)") + template(v-else-if="scope.row.link") + span.x-link(v-text="scope.row.linkText" @click="openNotificationLink(scope.row.link)") el-table-column(:label="$t('table.notification.photo')" width="100" prop="photo") template(v-once #default="scope") template(v-if="scope.row.details && scope.row.details.imageUrl") @@ -46,8 +53,7 @@ mixin notificationsTab() span.x-link(v-if="scope.row.type === 'invite'" @click="showWorldDialog(scope.row.details.worldId)") location(v-if="scope.row.details" :location="scope.row.details.worldId" :hint="scope.row.details.worldName" :grouphint="scope.row.details.groupName" :link="false") br - span(v-if="scope.row.message && scope.row.message !== `This is a generated invite to ${scope.row.details?.worldName}`" v-text="scope.row.message") - span(v-else-if="scope.row.title") {{ scope.row.title }}, {{ scope.row.message }} + span(v-else-if="scope.row.message && scope.row.message !== `This is a generated invite to ${scope.row.details?.worldName}`" v-text="scope.row.message") span(v-else-if='scope.row.details && scope.row.details.inviteMessage' v-text="scope.row.details.inviteMessage") span(v-else-if='scope.row.details && scope.row.details.requestMessage' v-text="scope.row.details.requestMessage") span(v-else-if='scope.row.details && scope.row.details.responseMessage' v-text="scope.row.details.responseMessage") @@ -88,7 +94,7 @@ mixin notificationsTab() template(v-else-if="scope.row.type === 'group.informative'") el-tooltip(placement="top" content="Dismiss" :disabled="hideTooltips") el-button(type="text" icon="el-icon-check" size="mini" style="margin-left:5px" @click="sendNotificationResponse(scope.row.id, scope.row.responses, 'delete')") - template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.') && !scope.row.type.includes('instance.')") + template(v-if="scope.row.type !== 'requestInviteResponse' && scope.row.type !== 'inviteResponse' && scope.row.type !== 'message' && scope.row.type !== 'groupChange' && !scope.row.type.includes('group.') && !scope.row.type.includes('moderation.') && !scope.row.type.includes('instance.')") el-tooltip(placement="top" content="Decline" :disabled="hideTooltips") el-button(type="text" icon="el-icon-close" size="mini" style="margin-left:5px" @click="hideNotification(scope.row)") template(v-if="scope.row.type === 'group.queueReady'") diff --git a/html/src/vr.js b/html/src/vr.js index d07eee95..01eef3a7 100644 --- a/html/src/vr.js +++ b/html/src/vr.js @@ -582,6 +582,9 @@ Vue.component('marquee-text', MarqueeText); case 'DisplayName': text = `${noty.previousDisplayName} changed their name to ${noty.displayName}`; break; + case 'groupChange': + text = `${noty.senderUsername} ${noty.message}`; + break; case 'group.announcement': text = noty.message; break; diff --git a/html/src/vr.pug b/html/src/vr.pug index 7592922c..2441777e 100644 --- a/html/src/vr.pug +++ b/html/src/vr.pug @@ -119,6 +119,11 @@ html span.extra span.time {{ feed.created_at | formatDate }} | 🤝 #[span.name(v-text="feed.displayName")] {{ feed.previousTrustLevel }} #[i.el-icon-right] {{ feed.trustLevel }} + div(v-else-if="feed.type === 'groupChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate }} + | 🏷️ #[span.name(v-text="feed.senderUsername")] #[span.name(v-text="feed.message")] div(v-else-if="feed.type === 'group.announcement'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra @@ -334,6 +339,11 @@ html span.extra span.time {{ feed.created_at | formatDate }} | #[span.name(v-text="feed.displayName")] trust level is now {{ feed.trustLevel }} + div(v-else-if="feed.type === 'groupChange'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") + .detail + span.extra + span.time {{ feed.created_at | formatDate }} + | #[span.name(v-text="feed.senderUsername")] #[span.name(v-text="feed.message")] div(v-else-if="feed.type === 'group.announcement'" class="x-friend-item" :class="{ friend: feed.isFriend, favorite: feed.isFavorite }") .detail span.extra