parent
f3b37f7479
commit
91dfb0bc8e
6 changed files with 196 additions and 7 deletions
|
|
@ -420,7 +420,7 @@ $admin-fg: white;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-operations-strut {
|
.message-operations-strut, .avatar-operations-strut {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
|
@ -442,6 +442,16 @@ $admin-fg: white;
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar-operations {
|
||||||
|
position: absolute;
|
||||||
|
width: fit-content;
|
||||||
|
background-color: white;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 0px 20px;
|
||||||
|
box-shadow: 4px 4px 8px #888888;
|
||||||
|
}
|
||||||
|
|
||||||
.message-operations-picker {
|
.message-operations-picker {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@
|
||||||
ref="chatContainer"
|
ref="chatContainer"
|
||||||
style="overflow-x: hidden; overflow-y: auto"
|
style="overflow-x: hidden; overflow-y: auto"
|
||||||
v-on:scroll="onScroll"
|
v-on:scroll="onScroll"
|
||||||
@click="closeContextMenuIfOpen"
|
@click="closeContextMenusIfOpen"
|
||||||
>
|
>
|
||||||
<div ref="messageOperationsStrut" class="message-operations-strut">
|
<div ref="messageOperationsStrut" class="message-operations-strut">
|
||||||
<message-operations
|
<message-operations
|
||||||
ref="messageOperations"
|
ref="messageOperations"
|
||||||
:style="opStyle"
|
:style="opStyle"
|
||||||
:emojis="recentEmojis"
|
:emojis="recentEmojis"
|
||||||
v-on:close="showContextMenu = false"
|
v-on:close="showContextMenu = false;showContextMenuAnchor = null;"
|
||||||
v-if="selectedEvent && showContextMenu"
|
v-if="selectedEvent && showContextMenu"
|
||||||
v-on:addreaction="addReaction"
|
v-on:addreaction="addReaction"
|
||||||
v-on:addquickreaction="addQuickReaction"
|
v-on:addquickreaction="addQuickReaction"
|
||||||
|
|
@ -29,6 +29,18 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div ref="avatarOperationsStrut" class="avatar-operations-strut">
|
||||||
|
<avatar-operations
|
||||||
|
ref="avatarOperations"
|
||||||
|
:style="avatarOpStyle"
|
||||||
|
v-on:close="showAvatarMenu = false;showAvatarMenuAnchor = null;"
|
||||||
|
v-on:start-private-chat="startPrivateChat($event)"
|
||||||
|
v-if="selectedEvent && showAvatarMenu"
|
||||||
|
:room="room"
|
||||||
|
:event="selectedEvent"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
<!-- Handle resizes, e.g. when soft keyboard is shown/hidden -->
|
||||||
<resize-observer
|
<resize-observer
|
||||||
ref="chatContainerResizer"
|
ref="chatContainerResizer"
|
||||||
|
|
@ -82,6 +94,7 @@
|
||||||
v-on:send-quick-reaction="sendQuickReaction"
|
v-on:send-quick-reaction="sendQuickReaction"
|
||||||
v-on:context-menu="showContextMenuForEvent($event)"
|
v-on:context-menu="showContextMenuForEvent($event)"
|
||||||
v-on:own-avatar-clicked="viewProfile"
|
v-on:own-avatar-clicked="viewProfile"
|
||||||
|
v-on:other-avatar-clicked="showAvatarMenuForEvent($event)"
|
||||||
v-on:download="download(event)"
|
v-on:download="download(event)"
|
||||||
/>
|
/>
|
||||||
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
<!-- <div v-if="debugging" style="user-select:text">EventID: {{ event.getId() }}</div> -->
|
||||||
|
|
@ -355,6 +368,7 @@ import DebugEvent from "./messages/DebugEvent.vue";
|
||||||
import util from "../plugins/utils";
|
import util from "../plugins/utils";
|
||||||
import MessageOperations from "./messages/MessageOperations.vue";
|
import MessageOperations from "./messages/MessageOperations.vue";
|
||||||
import MessageOperationsPicker from "./messages/MessageOperationsPicker.vue";
|
import MessageOperationsPicker from "./messages/MessageOperationsPicker.vue";
|
||||||
|
import AvatarOperations from "./messages/AvatarOperations.vue";
|
||||||
import ChatHeader from "./ChatHeader";
|
import ChatHeader from "./ChatHeader";
|
||||||
import VoiceRecorder from "./VoiceRecorder";
|
import VoiceRecorder from "./VoiceRecorder";
|
||||||
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
|
import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
|
||||||
|
|
@ -430,6 +444,7 @@ export default {
|
||||||
MessageOperationsBottomSheet,
|
MessageOperationsBottomSheet,
|
||||||
StickerPickerBottomSheet,
|
StickerPickerBottomSheet,
|
||||||
BottomSheet,
|
BottomSheet,
|
||||||
|
AvatarOperations,
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -456,6 +471,8 @@ export default {
|
||||||
replyToEvent: null,
|
replyToEvent: null,
|
||||||
showContextMenu: false,
|
showContextMenu: false,
|
||||||
showContextMenuAnchor: null,
|
showContextMenuAnchor: null,
|
||||||
|
showAvatarMenu: false,
|
||||||
|
showAvatarMenuAnchor: null,
|
||||||
initialLoadDone: false,
|
initialLoadDone: false,
|
||||||
loading: false, // Set this to true during long operations to show a "spinner" overlay
|
loading: false, // Set this to true during long operations to show a "spinner" overlay
|
||||||
showRecorder: false,
|
showRecorder: false,
|
||||||
|
|
@ -586,6 +603,25 @@ export default {
|
||||||
}
|
}
|
||||||
return "top:" + top + "px;left:" + left + "px";
|
return "top:" + top + "px;left:" + left + "px";
|
||||||
},
|
},
|
||||||
|
avatarOpStyle() {
|
||||||
|
// Calculate where to show the context menu.
|
||||||
|
//
|
||||||
|
const ref = this.selectedEvent && this.$refs[this.selectedEvent.getId()];
|
||||||
|
var top = 0;
|
||||||
|
var left = 0;
|
||||||
|
if (ref && ref[0]) {
|
||||||
|
if (this.showAvatarMenuAnchor) {
|
||||||
|
var rectAnchor = this.showAvatarMenuAnchor.getBoundingClientRect();
|
||||||
|
var rectChat = this.$refs.avatarOperationsStrut.getBoundingClientRect();
|
||||||
|
top = rectAnchor.top - rectChat.top;
|
||||||
|
left = rectAnchor.left - rectChat.left;
|
||||||
|
// if (left + 250 > rectChat.right) {
|
||||||
|
// left = rectChat.right - 250; // Pretty ugly, but we want to make sure it does not escape the screen, and we don't have the exakt width of it (yet)!
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "top:" + top + "px;left:" + left + "px";
|
||||||
|
},
|
||||||
canRecordAudio() {
|
canRecordAudio() {
|
||||||
return util.browserCanRecordAudio();
|
return util.browserCanRecordAudio();
|
||||||
},
|
},
|
||||||
|
|
@ -1258,13 +1294,44 @@ export default {
|
||||||
this.showContextMenuAnchor = e.anchor;
|
this.showContextMenuAnchor = e.anchor;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showAvatarMenuForEvent(e) {
|
||||||
|
const event = e.event;
|
||||||
|
this.selectedEvent = event;
|
||||||
|
this.showAvatarMenu = true;
|
||||||
|
this.showAvatarMenuAnchor = e.anchor;
|
||||||
|
},
|
||||||
|
|
||||||
viewProfile() {
|
viewProfile() {
|
||||||
this.$navigation.push({ name: "Profile" }, 1);
|
this.$navigation.push({ name: "Profile" }, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
closeContextMenuIfOpen(e) {
|
startPrivateChat(e) {
|
||||||
|
this.$matrix.getOrCreatePrivateChat(e.event.getSender())
|
||||||
|
.then(room => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$navigation.push(
|
||||||
|
{
|
||||||
|
name: "Chat",
|
||||||
|
params: { roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId) },
|
||||||
|
},
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
closeContextMenusIfOpen(e) {
|
||||||
if (this.showContextMenu) {
|
if (this.showContextMenu) {
|
||||||
this.showContextMenu = false;
|
this.showContextMenu = false;
|
||||||
|
this.showContextMenuAnchor = null;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
if (this.showAvatarMenu) {
|
||||||
|
this.showAvatarMenu = false;
|
||||||
|
this.showAvatarMenuAnchor = null;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
37
src/components/messages/AvatarOperations.vue
Normal file
37
src/components/messages/AvatarOperations.vue
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="{
|
||||||
|
'avatar-operations': true,
|
||||||
|
incoming: incoming,
|
||||||
|
outgoing: !incoming,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<v-btn v-if="incoming" text @click.stop="startPrivateChat" class="ma-0 pa-0"
|
||||||
|
>Private chat with this user</v-btn
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import messageMixin from "./messageMixin";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [messageMixin],
|
||||||
|
mounted() {
|
||||||
|
// Any items to show?
|
||||||
|
if (this.room && this.event && this.$matrix.isDirectRoomWith(this.room, this.event.getSender())) {
|
||||||
|
this.$emit("close");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
startPrivateChat() {
|
||||||
|
this.$emit("close");
|
||||||
|
this.$emit("start-private-chat", { event: this.event });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/assets/css/chat.scss";
|
||||||
|
</style>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- BASE CLASS FOR INCOMING MESSAGE -->
|
<!-- BASE CLASS FOR INCOMING MESSAGE -->
|
||||||
<div :class="messageClasses">
|
<div :class="messageClasses">
|
||||||
<v-avatar class="avatar" size="32" color="#ededed">
|
<v-avatar class="avatar" ref="avatar" size="32" color="#ededed" @click.stop="otherAvatarClicked($refs.avatar.$el)">
|
||||||
<img v-if="messageEventAvatar(event)" :src="messageEventAvatar(event)" />
|
<img v-if="messageEventAvatar(event)" :src="messageEventAvatar(event)" />
|
||||||
<span v-else class="white--text headline">{{
|
<span v-else class="white--text headline">{{
|
||||||
messageEventDisplayName(event).substring(0, 1).toUpperCase()
|
messageEventDisplayName(event).substring(0, 1).toUpperCase()
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,10 @@ export default {
|
||||||
this.$emit("own-avatar-clicked", {event: this.event});
|
this.$emit("own-avatar-clicked", {event: this.event});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
otherAvatarClicked(avatarRef) {
|
||||||
|
this.$emit("other-avatar-clicked", {event: this.event, anchor: avatarRef});
|
||||||
|
},
|
||||||
|
|
||||||
showContextMenu(buttonRef) {
|
showContextMenu(buttonRef) {
|
||||||
this.$emit("context-menu", {event: this.event,anchor: buttonRef});
|
this.$emit("context-menu", {event: this.event,anchor: buttonRef});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ export default {
|
||||||
login(user) {
|
login(user) {
|
||||||
const tempMatrixClient = sdk.createClient(User.homeServerUrl(user.home_server));
|
const tempMatrixClient = sdk.createClient(User.homeServerUrl(user.home_server));
|
||||||
var promiseLogin;
|
var promiseLogin;
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
if (user.access_token) {
|
if (user.access_token) {
|
||||||
// Logged in on "real" account
|
// Logged in on "real" account
|
||||||
|
|
@ -520,7 +520,7 @@ export default {
|
||||||
var joined = room.getMembersWithMembership("join");
|
var joined = room.getMembersWithMembership("join");
|
||||||
var invited = room.getMembersWithMembership("invite");
|
var invited = room.getMembersWithMembership("invite");
|
||||||
var members = joined.concat(invited);
|
var members = joined.concat(invited);
|
||||||
|
|
||||||
var kickPromises = [];
|
var kickPromises = [];
|
||||||
members.forEach(member => {
|
members.forEach(member => {
|
||||||
if (member.userId != self.currentUserId) {
|
if (member.userId != self.currentUserId) {
|
||||||
|
|
@ -539,6 +539,77 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a private chat room with the given user. Searches through our rooms to see
|
||||||
|
* if a suitable room already exists. If not, one is created.
|
||||||
|
* @param {*} userId The user to chat with.
|
||||||
|
*/
|
||||||
|
getOrCreatePrivateChat(userId) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
for (const room of this.rooms) {
|
||||||
|
// Is the other member the one we are looking for?
|
||||||
|
if (this.isDirectRoomWith(room, userId)) {
|
||||||
|
var member = room.getMember(userId);
|
||||||
|
if (member && member.membership == "invite") {
|
||||||
|
// TODO Resend invite
|
||||||
|
}
|
||||||
|
resolve(room);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No room found, create one
|
||||||
|
//
|
||||||
|
const createRoomOptions = {
|
||||||
|
visibility: "private", // Not listed!
|
||||||
|
//name: this.roomName,
|
||||||
|
preset: "private_chat",
|
||||||
|
initial_state: [
|
||||||
|
{
|
||||||
|
type: "m.room.encryption",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "m.room.guest_access",
|
||||||
|
state_key: "",
|
||||||
|
content: {
|
||||||
|
guest_access: "forbidden",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
invite: [userId]
|
||||||
|
};
|
||||||
|
return this.matrixClient
|
||||||
|
.createRoom(createRoomOptions)
|
||||||
|
.then(({ room_id, room_alias }) => {
|
||||||
|
resolve(this.getRoom(room_alias || room_id));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if this room is a direct room with the given user.
|
||||||
|
* @param { } room
|
||||||
|
* @param {*} userId
|
||||||
|
*/
|
||||||
|
isDirectRoomWith(room, userId) {
|
||||||
|
if (room._selfMembership == "join" && room.getInvitedAndJoinedMemberCount() == 2) {
|
||||||
|
// Is the other member the one we are looking for?
|
||||||
|
if (room.getMembersWithMembership("join").some(item => item.userId == userId)) {
|
||||||
|
return true;
|
||||||
|
} else if (room.getMembersWithMembership("invite").some(item => item.userId == userId)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
on(event, handler) {
|
on(event, handler) {
|
||||||
if (this.matrixClient) {
|
if (this.matrixClient) {
|
||||||
this.matrixClient.on(event, handler);
|
this.matrixClient.on(event, handler);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue