Merge branch '482-show-list-of-seenBy' into 'dev'

Show member list of seen by

See merge request keanuapp/keanuapp-weblite!207
This commit is contained in:
N Pex 2023-06-26 12:22:35 +00:00
commit 791fa5936a
8 changed files with 91 additions and 43 deletions

View file

@ -208,7 +208,6 @@ body {
@media #{map-get($display-breakpoints, 'sm-and-down')} {
margin-top: 72px;
margin-bottom: 70px;
z-index: 9;
}
}

View file

@ -96,7 +96,8 @@
"incoming_message_deleted_text": "This message was deleted.",
"not_allowed_to_send": "Only admins and moderators are allowed to send to the room",
"reaction_count_more": "{reactionCount} more",
"seen_by": "Seen by no members | Seen by 1 member | Seen by {count} members",
"seen_by_count": "Seen by no members | Seen by 1 member | Seen by {count} members",
"seen_by": "Seen by",
"file": "File",
"files": "Files",
"images": "Images",

View file

@ -182,7 +182,7 @@ export default {
bottom: 0;
overflow-x: hidden;
overflow-y: hidden;
z-index: 10;
z-index: 20;
}
.bottom-sheet-bg {

View file

@ -4,7 +4,7 @@
<div v-if="showSenderAndTime" class="senderAndTime">
<div class="sender">{{ eventSenderDisplayName(event) }}</div>
<div class="time">
{{ formatTime(event.event.origin_server_ts) }}
{{ utils.formatTime(event.event.origin_server_ts) }}
</div>
</div>
<v-avatar class="avatar" ref="avatar" size="32" color="#ededed" @click.stop="otherAvatarClicked($refs.avatar.$el)">

View file

@ -3,7 +3,7 @@
<div class="messageOut">
<div class="senderAndTime">
<div class="time">
{{ formatTime(event.event.origin_server_ts) }}
{{ utils.formatTime(event.event.origin_server_ts) }}
</div>
<div class="status">{{ event.status }}</div>
</div>

View file

@ -1,27 +1,57 @@
<template>
<div class="seen-by-container">
<v-tooltip top open-delay="500" v-if="seenBy.length > 0">
<template v-slot:activator="{ on, attrs }">
<div v-bind="attrs" v-on="on" class="clickable">
<div class="more" v-if="seenBy.length > 0">{{ moreItems }}</div>
<transition-group name="list" tag="div" v-if="seenBy.length > 0">
<v-avatar v-for="(member, index) in seenBy" :key="member.userId" class="seen-by-user" size="16" color="grey"
v-show="index < SHOW_LIMIT">
<img v-if="memberAvatar(member)" :src="memberAvatar(member)" />
<div>
<div class="seen-by-container">
<v-tooltip top open-delay="500" v-if="seenBy.length > 0">
<template v-slot:activator="{ on, attrs }">
<div v-bind="attrs" v-on="on" class="clickable">
<div class="more" v-if="seenBy.length > 0">{{ moreItems }}</div>
<transition-group name="list" tag="div" v-if="seenBy.length > 0">
<v-avatar v-for="(member, index) in seenBy" :key="member.roomMember.userId" class="seen-by-user" size="16" color="grey"
v-show="index < SHOW_LIMIT" @click="open">
<img v-if="memberAvatar(member.roomMember)" :src="memberAvatar(member.roomMember)" />
<span v-else class="white--text headline">{{
member.roomMember.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
</transition-group>
</div>
</template>
<span>{{ $tc("message.seen_by_count", seenBy.length) }}</span>
</v-tooltip>
</div>
<BottomSheet
:halfY="0.12"
ref="seenByListBottomSheet"
>
<v-list>
<v-subheader class="text-uppercase"> {{ $tc("message.seen_by") }}</v-subheader>
<v-list-item v-for="(member, index) in seenBy" :key="index">
<v-list-item-icon>
<v-avatar size="40" color="grey">
<img v-if="memberAvatar(member.roomMember)" :src="memberAvatar(member.roomMember)" />
<span v-else class="white--text headline">{{
member.name.substring(0, 1).toUpperCase()
member.roomMember.name.substring(0, 1).toUpperCase()
}}</span>
</v-avatar>
</transition-group>
</div>
</template>
<span>{{ $tc("message.seen_by", seenBy.length) }}</span>
</v-tooltip>
</v-list-item-icon>
<v-list-item-content class="text-left">
<v-list-item-title>{{member.roomMember.name}}</v-list-item-title>
<v-list-item-subtitle>{{ seenByTimeStamp(member.readTimestamp) }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</BottomSheet>
</div>
</template>
<script>
import BottomSheet from "../BottomSheet.vue"
import utils from "../../plugins/utils.js";
export default {
components: {
BottomSheet
},
props: {
room: {
type: Object,
@ -40,6 +70,7 @@ export default {
return {
seenBy: [],
SHOW_LIMIT: 5,
utils
}
},
mounted() {
@ -62,6 +93,17 @@ export default {
}
},
methods: {
seenByTimeStamp(timestamp) {
let dayDiff = utils.dayDiffToday(timestamp);
if (dayDiff < 3) {
return this.$tc("message.time_ago", dayDiff) + ' '+utils.formatTime(timestamp);
} else {
return utils.formatTime(timestamp);
}
},
open() {
this.$refs.seenByListBottomSheet.open();
},
onReceipt(ignoredevent) {
this.update();
},
@ -80,7 +122,10 @@ export default {
update() {
this.seenBy = ((this.room && this.event) ? this.room.getReceiptsForEvent(this.event) : [])
.filter(receipt => receipt.type == 'm.read' && receipt.userId !== this.$matrix.currentUserId)
.map(receipt => this.room.getMember(receipt.userId));
.map(receipt => {
return { readTimestamp: receipt.data.ts, roomMember: this.room.getMember(receipt.userId) }
}
);
},
},
watch: {

View file

@ -1,6 +1,8 @@
import QuickReactions from "./QuickReactions.vue";
import * as linkify from 'linkifyjs';
import linkifyHtml from 'linkify-html';
import utils from "../../plugins/utils"
linkify.options.defaults.className = "link";
linkify.options.defaults.target = { url: "_blank" };
@ -39,6 +41,7 @@ export default {
event: {},
inReplyToEvent: null,
inReplyToSender: null,
utils
};
},
mounted() {
@ -231,22 +234,6 @@ export default {
return false;
},
formatTime(time) {
const date = new Date();
date.setTime(time);
const today = new Date();
if (
date.getDate() == today.getDate() &&
date.getMonth() == today.getMonth() &&
date.getFullYear() == today.getFullYear()
) {
// For today, skip the date part
return date.toLocaleTimeString();
}
return date.toLocaleString();
},
formatTimeAgo(time) {
const date = new Date();
date.setTime(time);

View file

@ -67,7 +67,7 @@ class Util {
}
});
}
getAttachment(matrixClient, event, progressCallback, asBlob = false, abortController = undefined) {
return new Promise((resolve, reject) => {
const content = event.getContent();
@ -317,7 +317,7 @@ class Util {
// Find the exact match (= object equality)
return e.error === err
});
}
}
}
matrixClient.resendEvent(event, matrixClient.getRoom(event.getRoomId()))
.then((result) => {
@ -353,7 +353,7 @@ class Util {
mimetype: file.type,
size: file.size
};
// If audio, send duration in ms as well
if (file.duration) {
info.duration = file.duration;
@ -463,11 +463,11 @@ class Util {
/**
* Return 'true' if we should use voice mode for the given room.
*
*
* The default value is given by the room itself. If the "type" of the
* room is set to 'im.keanu.room_type_voice' then we default to voice mode,
* else not. The user can then override this default by flipping the "voice mode"
* swicth on room settings (it will be persisted as a user specific tag on the room)
* swicth on room settings (it will be persisted as a user specific tag on the room)
*/
useVoiceMode(roomOrNull) {
if (roomOrNull) {
@ -478,7 +478,7 @@ class Util {
if (tags && tags["ui_options"]) {
return tags["ui_options"]["voice_mode"] === 1;
}
// Was the room created with a voice mode type?
const createEvent = room.currentState.getStateEvents(
"m.room.create",
@ -779,6 +779,22 @@ class Util {
return then.format('L');
}
formatTime(time) {
const date = new Date();
date.setTime(time);
const today = new Date();
if (
date.getDate() == today.getDate() &&
date.getMonth() == today.getMonth() &&
date.getFullYear() == today.getFullYear()
) {
// For today, skip the date part
return date.toLocaleTimeString();
}
return date.toLocaleString();
}
formatRecordDuration(ms) {
return dayjs.duration(ms).format("HH:mm:ss");
}