Lots of channel related fixes and updates
This commit is contained in:
parent
e3bfede77e
commit
ca777a83be
17 changed files with 508 additions and 59 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<!-- BASE CLASS FOR INCOMING MESSAGE -->
|
||||
<div :class="messageClasses">
|
||||
<div v-if="showSenderAndTime" class="senderAndTime">
|
||||
<div v-if="showSenderAndTime || room.displayType == ROOM_TYPE_CHANNEL" class="senderAndTime">
|
||||
<div class="sender">{{ eventSenderDisplayName(event) }}</div>
|
||||
<div class="time">
|
||||
{{ utils.formatTime(event.event.origin_server_ts) }}
|
||||
|
|
@ -17,24 +17,31 @@
|
|||
<span ref="messageInOutRef" class="content">
|
||||
<slot></slot>
|
||||
</span>
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted()">
|
||||
<div class="pin-icon" v-if="event.isPinned"><v-icon>$vuetify.icons.ic_pin_filled</v-icon></div>
|
||||
<div class="op-button" ref="opbutton" v-if="!event.isRedacted() && room.displayType != ROOM_TYPE_CHANNEL">
|
||||
<v-btn id="btn-more" icon @click.stop="showContextMenu($refs.opbutton)">
|
||||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<QuickReactions :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<SeenBy :room="room" :event="event"/>
|
||||
<QuickReactionsChannel v-if="room.displayType == ROOM_TYPE_CHANNEL" :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<QuickReactions v-else :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<SeenBy v-if="room.displayType != ROOM_TYPE_CHANNEL" :room="room" :event="event"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeenBy from "./SeenBy.vue";
|
||||
import messageMixin from "./messageMixin";
|
||||
import util from "../../plugins/utils";
|
||||
import util, { ROOM_TYPE_CHANNEL } from "../../plugins/utils";
|
||||
import QuickReactions from "./QuickReactions.vue";
|
||||
import QuickReactionsChannel from "./channel/QuickReactionsChannel.vue";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin],
|
||||
components: { SeenBy },
|
||||
components: { QuickReactions, QuickReactionsChannel, SeenBy },
|
||||
data() {
|
||||
return { ROOM_TYPE_CHANNEL: ROOM_TYPE_CHANNEL }
|
||||
},
|
||||
mounted() {
|
||||
if(util.isMobileOrTabletBrowser() && this.$refs.messageInOutRef) {
|
||||
this.initMsgHammerJs(this.$refs.messageInOutRef);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<template>
|
||||
<!-- BASE CLASS FOR OUTGOING MESSAGE -->
|
||||
<div class="messageOut">
|
||||
<div :class="messageClasses">
|
||||
<div class="senderAndTime">
|
||||
<div class="sender" v-if="room.displayType == ROOM_TYPE_CHANNEL">{{ eventSenderDisplayName(event) }}</div>
|
||||
<div class="time">
|
||||
{{ utils.formatTime(event.event.origin_server_ts) }}
|
||||
</div>
|
||||
|
|
@ -13,6 +14,8 @@
|
|||
<v-icon>more_vert</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
<div class="pin-icon" v-if="event.isPinned"><v-icon>$vuetify.icons.ic_pin_filled</v-icon></div>
|
||||
|
||||
<!-- SLOT FOR CONTENT -->
|
||||
<span ref="messageInOutRef" class="content">
|
||||
<slot></slot>
|
||||
|
|
@ -26,19 +29,25 @@
|
|||
<img v-if="userAvatar" :src="userAvatar" />
|
||||
<span v-else class="white--text headline">{{ userAvatarLetter }}</span>
|
||||
</v-avatar>
|
||||
<QuickReactions :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<SeenBy :room="room" :event="event"/>
|
||||
<QuickReactionsChannel v-if="room.displayType == ROOM_TYPE_CHANNEL" :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<QuickReactions v-else :event="eventForReactions" :timelineSet="timelineSet" v-on="$listeners"/>
|
||||
<SeenBy v-if="room.displayType != ROOM_TYPE_CHANNEL" :room="room" :event="event"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SeenBy from "./SeenBy.vue";
|
||||
import messageMixin from "./messageMixin";
|
||||
import util from "../../plugins/utils";
|
||||
import util, { ROOM_TYPE_CHANNEL } from "../../plugins/utils";
|
||||
import QuickReactions from "./QuickReactions.vue";
|
||||
import QuickReactionsChannel from "./channel/QuickReactionsChannel.vue";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin],
|
||||
components: { SeenBy },
|
||||
components: { QuickReactions, QuickReactionsChannel, SeenBy },
|
||||
data() {
|
||||
return { ROOM_TYPE_CHANNEL: ROOM_TYPE_CHANNEL }
|
||||
},
|
||||
mounted() {
|
||||
if(util.isMobileOrTabletBrowser() && this.$refs.messageInOutRef) {
|
||||
this.initMsgHammerJs(this.$refs.messageInOutRef);
|
||||
|
|
|
|||
17
src/components/messages/ReadMarker.vue
Normal file
17
src/components/messages/ReadMarker.vue
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<template>
|
||||
<div class="read-marker">
|
||||
<div class="line"></div>
|
||||
<div class="text">{{ $t('message.unread_messages') }}</div>
|
||||
<div class="line"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
34
src/components/messages/channel/MessageOperationsChannel.vue
Normal file
34
src/components/messages/channel/MessageOperationsChannel.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div :class="{'message-operations':true,'incoming':incoming,'outgoing':!incoming}">
|
||||
<v-btn id="btn-pin" icon @click.stop="pin" class="ma-0 pa-0" v-if="userCanPin && !event.isPinned">
|
||||
<v-icon small>$vuetify.icons.ic_pin_filled</v-icon>
|
||||
</v-btn>
|
||||
<v-btn id="btn-unpin" icon @click.stop="unpin" class="ma-0 pa-0" v-if="userCanPin && event.isPinned">
|
||||
<v-icon small>$vuetify.icons.ic_pin</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import messageMixin from "../messageMixin";
|
||||
import messageOperationsMixin from "../messageOperationsMixin";
|
||||
|
||||
export default {
|
||||
mixins: [messageMixin, messageOperationsMixin],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
userCanPin: {
|
||||
type: Boolean,
|
||||
default: function () {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
127
src/components/messages/channel/QuickReactionsChannel.vue
Normal file
127
src/components/messages/channel/QuickReactionsChannel.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<div class="quick-reaction-container">
|
||||
<div
|
||||
class="emoji"
|
||||
v-for="(value, name) in reactionMap"
|
||||
:key="name"
|
||||
v-show="name == '❤️'"
|
||||
>
|
||||
<v-tooltip top v-if="value.includes($matrix.currentUserId)">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-icon class="ma-1 ml-0 clickable" v-bind="attrs" v-on="on" @click="onClickEmoji(name)">$vuetify.icons.ic_like_filled</v-icon>
|
||||
</template>
|
||||
<span>{{ $t("global.click_to_remove") }}</span>
|
||||
</v-tooltip>
|
||||
<v-icon v-else class="ma-1 ml-0 clickable" @click="onClickEmoji(name)">$vuetify.icons.ic_like</v-icon> {{ value.length }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import messageOperationsMixin from "../messageOperationsMixin";
|
||||
|
||||
export default {
|
||||
mixins: [messageOperationsMixin],
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
timelineSet: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return null
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reactionMap: {"❤️": []},
|
||||
reactions: null,
|
||||
REACTION_LIMIT: 5,
|
||||
showAllReaction: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.reactions = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), 'm.annotation', 'm.reaction');
|
||||
this.event.on("Event.relationsCreated", this.onRelationsCreated);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.event.off("Event.relationsCreated", this.onRelationsCreated);
|
||||
if (this.reactions) {
|
||||
this.reactions.off('Relations.add', this.onAddRelation);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
totalReaction() {
|
||||
return Object.keys(this.reactionMap).length
|
||||
},
|
||||
otherReactionText() {
|
||||
return this.showAllReaction ? this.$t("global.show_less") : this.$t("message.reaction_count_more", { reactionCount: this.totalReaction - this.REACTION_LIMIT })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onRelationsCreated() {
|
||||
this.reactions = this.timelineSet.relations.getChildEventsForEvent(this.event.getId(), 'm.annotation', 'm.reaction');
|
||||
},
|
||||
onClickEmoji(emoji) {
|
||||
this.$bubble('send-quick-reaction', {reaction:emoji, event:this.event});
|
||||
},
|
||||
onAddRelation(ignoredevent) {
|
||||
this.processReactions();
|
||||
},
|
||||
onRemoveRelation(ignoredevent) {
|
||||
this.processReactions();
|
||||
},
|
||||
onRedactRelation(ignoredevent) {
|
||||
this.processReactions();
|
||||
},
|
||||
processReactions() {
|
||||
var reactionMap = {"❤️": []};
|
||||
if (this.reactions && this.reactions._eventsCount > 0) {
|
||||
const relations = this.reactions.getRelations();
|
||||
for (const r of relations) {
|
||||
const emoji = r.getRelation().key;
|
||||
const sender = r.getSender();
|
||||
if (reactionMap[emoji]) {
|
||||
const array = reactionMap[emoji];
|
||||
if (r.isRedacted()) {
|
||||
delete array[sender];
|
||||
}
|
||||
if (!array.includes(sender)) {
|
||||
array.push(sender)
|
||||
}
|
||||
} else if (!r.isRedacted()) {
|
||||
reactionMap[emoji] = [sender];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.reactionMap = reactionMap;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
reactions: {
|
||||
handler(newValue, oldValue) {
|
||||
if (oldValue) {
|
||||
oldValue.off('Relations.add', this.onAddRelation);
|
||||
oldValue.off('Relations.remove', this.onRemoveRelation);
|
||||
oldValue.off('Relations.redaction', this.onRedactRelation);
|
||||
}
|
||||
if (newValue) {
|
||||
newValue.on('Relations.add', this.onAddRelation);
|
||||
newValue.on('Relations.remove', this.onRemoveRelation);
|
||||
newValue.on('Relations.redaction', this.onRedactRelation);
|
||||
}
|
||||
this.processReactions();
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -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.event.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
|
||||
|
|
@ -181,11 +181,15 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* 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.event.isPinned };
|
||||
} else {
|
||||
return { messageOut: true, "pinned": this.event.isPinned };
|
||||
}
|
||||
},
|
||||
|
||||
userAvatar() {
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue