2020-11-09 10:26:56 +01:00
< template >
< div class = "chat-root fill-height d-flex flex-column" ma -0 pa -0 >
2021-03-11 13:55:10 +01:00
< ChatHeader
2021-03-18 11:30:30 +01:00
class = "chat-header flex-grow-0 flex-shrink-0"
2021-03-11 13:55:10 +01:00
v - on : header - click = "$refs.roomInfoSheet.open()"
/ >
2020-11-09 10:26:56 +01:00
< div
class = "chat-content flex-grow-1 flex-shrink-1"
ref = "chatContainer"
style = "overflow-x: hidden; overflow-y: auto"
2020-12-10 12:51:04 +01:00
v - on : scroll = "onScroll"
2021-05-10 16:11:03 +02:00
@ click = "closeContextMenusIfOpen"
2020-11-09 10:26:56 +01:00
>
2021-01-11 17:42:58 +01:00
< div ref = "messageOperationsStrut" class = "message-operations-strut" >
2021-02-17 11:59:07 +01:00
< message-operations
2021-04-09 14:03:40 +02:00
ref = "messageOperations"
2021-02-17 11:59:07 +01:00
: style = "opStyle"
2021-04-09 14:03:40 +02:00
: emojis = "recentEmojis"
2021-05-10 16:11:03 +02:00
v - on : close = "showContextMenu = false;showContextMenuAnchor = null;"
2021-02-17 11:59:07 +01:00
v - if = "selectedEvent && showContextMenu"
v - on : addreaction = "addReaction"
2021-04-09 14:03:40 +02:00
v - on : addquickreaction = "addQuickReaction"
2021-02-17 11:59:07 +01:00
v - on : addreply = "addReply(selectedEvent)"
v - on : edit = "edit(selectedEvent)"
v - on : redact = "redact(selectedEvent)"
v - on : download = "download(selectedEvent)"
2021-04-09 14:03:40 +02:00
v - on : more = "showMoreMessageOperations"
2021-02-17 11:59:07 +01:00
: event = "selectedEvent"
/ >
< / div >
2020-12-14 16:11:45 +01:00
2021-05-10 16:11:03 +02:00
< 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 >
2020-12-10 12:51:04 +01:00
<!-- Handle resizes , e . g . when soft keyboard is shown / hidden -- >
< resize-observer
ref = "chatContainerResizer"
@ notify = "handleChatContainerResize"
/ >
2020-12-14 16:11:45 +01:00
2021-04-01 22:59:19 +02:00
< CreatedRoomWelcomeHeader v-if = "showCreatedRoomWelcomeHeader" v-on:close="closeCreateRoomWelcomeHeader" / >
2021-02-17 11:59:07 +01:00
< div
v - for = "(event, index) in events"
: key = "event.getId()"
: eventId = "event.getId()"
>
<!-- DAY Marker , shown for every new day in the timeline -- >
< div
v - if = "showDayMarkerBeforeEvent(event)"
class = "day-marker"
: title = "dayForEvent(event)"
/ >
2020-12-04 10:44:46 +01:00
< div
v - if = "
! event . isRelation ( ) && ! event . isRedacted ( ) && ! event . isRedaction ( )
"
2020-12-14 16:11:45 +01:00
: ref = "event.getId()"
2020-12-04 10:44:46 +01:00
>
< div
style = "position: relative; user-select: none"
v - on : touchstart = "
( e ) => {
touchStart ( e , event ) ;
}
"
v - on : touchend = "touchEnd"
v - on : touchcancel = "touchCancel"
v - on : touchmove = "touchMove"
2020-12-03 22:12:50 +01:00
>
2020-11-25 14:42:50 +01:00
< component
: is = "componentForEvent(event)"
: room = "room"
2020-12-04 10:44:46 +01:00
: event = "event"
2020-12-16 11:21:29 +01:00
: nextEvent = "events[index + 1]"
2020-11-25 14:42:50 +01:00
: reactions = "
timelineWindow . _timelineSet . getRelationsForEvent (
event . getId ( ) ,
'm.annotation' ,
'm.reaction'
)
"
2020-12-15 17:06:26 +01:00
: timelineSet = "timelineWindow._timelineSet"
2020-11-25 14:42:50 +01:00
v - on : send - quick - reaction = "sendQuickReaction"
2020-12-14 16:11:45 +01:00
v - on : context - menu = "showContextMenuForEvent($event)"
2021-03-04 12:17:21 +01:00
v - on : own - avatar - clicked = "viewProfile"
2021-05-10 16:11:03 +02:00
v - on : other - avatar - clicked = "showAvatarMenuForEvent($event)"
2021-04-13 21:55:25 +02:00
v - on : download = "download(event)"
2020-11-25 14:42:50 +01:00
/ >
2021-04-13 19:30:35 +02:00
<!-- < div v-if = "debugging" style="user-select:text" > EventID : {{ event.getId ( ) }} < / div > - - >
2021-04-13 21:55:25 +02:00
<!-- < div v-if = "debugging" style="user-select:text" > Event : {{ JSON.stringify ( event ) }} < / div > - - >
2021-02-17 11:59:07 +01:00
< div
v - if = "event.getId() == readMarker && index < events.length - 1"
class = "read-marker"
title = "Unread messages"
/ >
2020-11-25 14:42:50 +01:00
< / div >
2020-12-03 10:00:23 +01:00
< / div >
2020-11-09 10:26:56 +01:00
< / div >
< / div >
<!-- Input area -- >
2020-12-04 12:15:47 +01:00
< v-container v-if = "room" fluid class="input-area-outer" >
2021-02-17 11:59:07 +01:00
<!-- "Scroll to end" - button -- >
< v-btn
class = "scroll-to-end"
v - show = "showScrollToEnd"
fab
small
elevation = "0"
color = "black"
2021-04-09 16:20:57 +02:00
@ click . stop = "scrollToEndOfTimeline"
2021-02-17 11:59:07 +01:00
>
< v-icon color = "white" > arrow _downward < / v-icon >
< / v-btn >
2020-12-04 12:15:47 +01:00
< v-row class = "ma-0 pa-0" >
2021-02-17 11:59:07 +01:00
< div v-if = "replyToEvent" >
REPLYING TO EVENT : { { replyToEvent . getContent ( ) . body } }
< / div >
2020-12-15 17:06:26 +01:00
2020-12-04 12:15:47 +01:00
<!-- CONTACT IS TYPING -- >
< div class = "typing" >
2020-12-09 21:50:53 +01:00
{ { typingMembersString } }
2020-12-04 12:15:47 +01:00
< / div >
< / v-row >
2020-12-10 12:00:07 +01:00
< v-row class = "input-area-inner align-center" >
2020-12-04 12:15:47 +01:00
< v-col class = "flex-grow-1 flex-shrink-1 ma-0 pa-0" >
< v-textarea
height = "undefined"
ref = "messageInput"
full - width
auto - grow
rows = "1"
v - model = "currentInput"
no - resize
class = "input-area-text"
2021-02-23 22:07:57 +01:00
placeholder = "Your message..."
2020-12-04 12:15:47 +01:00
hide - details
background - color = "white"
2021-02-17 11:59:07 +01:00
v - on : keydown . enter . prevent = "
( ) => {
2021-03-10 17:24:48 +01:00
sendCurrentTextMessage ( ) ;
2021-02-17 11:59:07 +01:00
}
"
2020-12-04 12:15:47 +01:00
/ >
< / v-col >
2021-02-22 16:34:19 +01:00
< v-col
class = "input-area-button text-center flex-grow-0 flex-shrink-1"
2021-02-23 22:07:57 +01:00
v - if = "editedEvent || replyToEvent"
2021-02-22 16:34:19 +01:00
>
< v-btn
fab
small
elevation = "0"
2021-02-23 22:07:57 +01:00
color = "black"
@ click . stop = "cancelEditReply"
2021-02-22 16:34:19 +01:00
>
2021-02-23 22:07:57 +01:00
< v-icon color = "white" > cancel < / v-icon >
2021-02-22 16:34:19 +01:00
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2021-02-17 11:59:07 +01:00
< v-col
class = "input-area-button text-center flex-grow-0 flex-shrink-1"
2021-03-10 17:24:48 +01:00
v - if = "!currentInput || currentInput.length == 0 || showRecorder"
2021-02-17 11:59:07 +01:00
>
< v-btn
2021-03-05 22:34:00 +01:00
class = "mic-button"
2021-02-23 22:07:57 +01:00
ref = "mic_button"
2021-02-17 11:59:07 +01:00
fab
small
elevation = "0"
2021-02-23 22:07:57 +01:00
v - blur
2021-03-03 12:29:55 +01:00
style = "z-index: 10"
2021-03-18 11:58:46 +01:00
: disabled = "!canRecordAudio"
2021-03-11 13:55:10 +01:00
v - longTap : 250 = "[showRecordingUI, startRecording]"
2021-02-17 11:59:07 +01:00
>
2021-02-23 22:07:57 +01:00
< v-icon : color = "showRecorder ? 'white' : 'black'" > mic < / v-icon >
2020-12-14 17:12:29 +01:00
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2021-03-03 12:29:55 +01:00
< v-col
class = "input-area-button text-center flex-grow-0 flex-shrink-1"
v - else
2021-02-23 22:07:57 +01:00
>
2021-02-17 11:59:07 +01:00
< v-btn
fab
small
elevation = "0"
color = "black"
2021-03-10 17:24:48 +01:00
@ click . stop = "sendCurrentTextMessage"
2021-02-17 11:59:07 +01:00
: disabled = "sendButtonDisabled"
>
< v-icon color = "white" > { {
editedEvent ? "save" : "arrow_upward"
} } < / v-icon >
2020-12-04 12:15:47 +01:00
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2021-04-15 11:44:58 +02:00
< v-col v-if = "config.useShortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1" >
< v-btn
v - if = "!showRecorder"
icon
large
color = "black"
@ click = "showStickerPicker"
: disabled = "attachButtonDisabled"
>
< v-icon large > face < / v-icon >
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
< v-col class = "input-area-button text-center flex-grow-0 flex-shrink-1" >
< label icon flat ref = "attachmentLabel" >
< v-btn
2021-03-10 17:24:48 +01:00
v - if = "!showRecorder"
2021-02-23 22:07:57 +01:00
icon
large
color = "black"
@ click = "showAttachmentPicker"
: disabled = "attachButtonDisabled"
>
< v-icon x -large > add _circle _outline < / v-icon >
< / v-btn >
< input
ref = "attachment"
type = "file"
name = "attachment"
@ change = "handlePickedAttachment($event)"
accept = "image/*|audio/*|video/*|application/pdf"
style = "display: none"
/ >
< / label >
< / v-col >
2020-12-04 12:15:47 +01:00
< / v-row >
2021-03-03 12:29:55 +01:00
< VoiceRecorder
: micButtonRef = "$refs.mic_button"
2021-03-05 22:34:00 +01:00
: ptt = "showRecorderPTT"
2021-03-03 12:29:55 +01:00
: show = "showRecorder"
v - on : close = "showRecorder = false"
v - on : file = "onVoiceRecording"
/ >
2020-12-04 12:15:47 +01:00
< / v-container >
2020-11-17 20:02:42 +01:00
2021-03-10 17:24:48 +01:00
< div v-if = "currentImageInputPath" >
< v-dialog v-model = "currentImageInputPath" class="ma-0 pa-0" width="50%" >
2020-11-17 20:02:42 +01:00
< v-card class = "ma-0 pa-0" >
< v-card-text class = "ma-0 pa-0" >
< v-img
2021-03-10 17:24:48 +01:00
v - if = "currentImageInput"
2020-11-17 20:02:42 +01:00
: aspect - ratio = "1"
: src = "currentImageInput"
contain
style = "max-height: 50vh"
/ >
< div v-if = "currentSendError" > {{ currentSendError }} < / div >
< div v-else > {{ currentSendProgress }} < / div >
< / v-card-text >
< v-divider > < / v-divider >
< v-card-actions >
< v-spacer > < / v-spacer >
2020-11-21 14:57:43 +01:00
< v-btn color = "primary" text @click ="cancelSendAttachment"
2020-11-17 20:02:42 +01:00
> Cancel < / v - b t n
>
2020-11-25 14:42:50 +01:00
< v-btn
color = "primary"
text
@ click = "sendAttachment"
2021-03-10 17:24:48 +01:00
v - if = "currentSendShowSendButton"
2020-11-25 14:42:50 +01:00
: disabled = "currentSendOperation != null"
> Send < / v - b t n
>
2020-11-17 20:02:42 +01:00
< / v-card-actions >
< / v-card >
< / v-dialog >
< / div >
2020-11-25 14:42:50 +01:00
2021-04-09 14:27:27 +02:00
< MessageOperationsBottomSheet ref = "messageOperationsSheet" xv -show = " showEmojiPicker " >
2021-04-09 14:03:40 +02:00
< MessageOperationsPicker
v - on : close = "showEmojiPicker = false"
v - if = "selectedEvent"
v - on : addreaction = "addReaction"
v - on : addquickreaction = "addQuickReaction"
v - on : addreply = "addReply(selectedEvent)"
v - on : edit = "edit(selectedEvent)"
v - on : redact = "redact(selectedEvent)"
v - on : download = "download(selectedEvent)"
: event = "selectedEvent"
/ >
< VEmojiPicker ref = "emojiPicker" style = "width: 100%" @select ="emojiSelected" / >
2021-04-09 14:27:27 +02:00
< / MessageOperationsBottomSheet >
2020-12-10 12:37:06 +01:00
2021-04-15 11:44:58 +02:00
< StickerPickerBottomSheet ref = "stickerPickerSheet" style = "z-index:10" v -on :selectSticker = "sendSticker" / >
2020-12-10 12:37:06 +01:00
<!-- "NOT ALLOWED FOR GUEST ACCOUNTS" dialog -- >
2021-02-17 11:59:07 +01:00
< v-dialog v-model = "showNotAllowedForGuests" class="ma-0 pa-0" width="50%" >
< v-card >
< v-card-title > You are logged in as a guest < / v-card-title >
< v-card-text >
< div > Unfortunately guests are not allowed to upload files . < / div >
< / v-card-text >
< v-divider > < / v-divider >
< v-card-actions >
< v-spacer > < / v-spacer >
< v-btn color = "primary" text @ click = "showNotAllowedForGuests = false"
> Ok < / v - b t n
>
< / v-card-actions >
< / v-card >
< / v-dialog >
2021-02-17 17:12:16 +01:00
<!-- Loading indicator -- >
2021-03-03 12:29:55 +01:00
< v-container
fluid
fill - height
2021-04-09 16:20:57 +02:00
style = "position: absolute;background-color:rgba(0,0,0,0.2)"
v - if = "!initialLoadDone || loading"
2021-03-03 12:29:55 +01:00
>
2021-02-17 17:12:16 +01:00
< v-row align = "center" justify = "center" >
< v-col class = "text-center" >
< v-progress-circular
indeterminate
color = "primary"
> < / v-progress-circular >
< / v-col >
< / v-row >
< / v-container >
2021-03-11 13:55:10 +01:00
< RoomInfoBottomSheet ref = "roomInfoSheet" / >
2020-11-09 10:26:56 +01:00
< / div >
< / template >
< script >
2020-11-11 17:35:14 +01:00
import { TimelineWindow , EventTimeline } from "matrix-js-sdk" ;
2020-11-19 22:48:08 +01:00
import MessageIncomingText from "./messages/MessageIncomingText" ;
2021-04-13 21:55:25 +02:00
import MessageIncomingFile from "./messages/MessageIncomingFile" ;
2020-12-03 10:00:23 +01:00
import MessageIncomingImage from "./messages/MessageIncomingImage.vue" ;
import MessageIncomingAudio from "./messages/MessageIncomingAudio.vue" ;
2021-03-17 12:13:53 +01:00
import MessageIncomingVideo from "./messages/MessageIncomingVideo.vue" ;
2021-04-15 11:44:58 +02:00
import MessageIncomingSticker from "./messages/MessageIncomingSticker.vue" ;
2020-11-19 22:48:08 +01:00
import MessageOutgoingText from "./messages/MessageOutgoingText" ;
2021-04-13 21:55:25 +02:00
import MessageOutgoingFile from "./messages/MessageOutgoingFile" ;
2020-12-03 10:00:23 +01:00
import MessageOutgoingImage from "./messages/MessageOutgoingImage.vue" ;
import MessageOutgoingAudio from "./messages/MessageOutgoingAudio.vue" ;
2021-03-17 12:13:53 +01:00
import MessageOutgoingVideo from "./messages/MessageOutgoingVideo.vue" ;
2021-04-15 11:44:58 +02:00
import MessageOutgoingSticker from "./messages/MessageOutgoingSticker.vue" ;
2020-11-19 22:48:08 +01:00
import ContactJoin from "./messages/ContactJoin.vue" ;
import ContactLeave from "./messages/ContactLeave.vue" ;
import ContactInvited from "./messages/ContactInvited.vue" ;
2021-03-04 17:19:39 +01:00
import ContactChanged from "./messages/ContactChanged.vue" ;
2020-11-19 22:48:08 +01:00
import RoomNameChanged from "./messages/RoomNameChanged.vue" ;
import RoomTopicChanged from "./messages/RoomTopicChanged.vue" ;
import RoomAvatarChanged from "./messages/RoomAvatarChanged.vue" ;
2021-03-03 12:29:55 +01:00
import RoomHistoryVisibility from "./messages/RoomHistoryVisibility.vue" ;
2021-03-26 12:16:13 +01:00
import RoomJoinRules from "./messages/RoomJoinRules.vue" ;
2020-11-17 20:32:37 +01:00
import DebugEvent from "./messages/DebugEvent.vue" ;
2020-11-21 14:57:43 +01:00
import util from "../plugins/utils" ;
2020-11-25 14:42:50 +01:00
import MessageOperations from "./messages/MessageOperations.vue" ;
2021-04-09 14:03:40 +02:00
import MessageOperationsPicker from "./messages/MessageOperationsPicker.vue" ;
2021-05-10 16:11:03 +02:00
import AvatarOperations from "./messages/AvatarOperations.vue" ;
2020-12-04 17:15:18 +01:00
import ChatHeader from "./ChatHeader" ;
2021-02-22 16:34:19 +01:00
import VoiceRecorder from "./VoiceRecorder" ;
2021-03-11 13:55:10 +01:00
import RoomInfoBottomSheet from "./RoomInfoBottomSheet" ;
2021-04-01 22:59:19 +02:00
import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader" ;
2021-04-09 14:27:27 +02:00
import MessageOperationsBottomSheet from './MessageOperationsBottomSheet' ;
2021-04-15 11:44:58 +02:00
import stickers from '../plugins/stickers' ;
import StickerPickerBottomSheet from './StickerPickerBottomSheet' ;
import BottomSheet from './BottomSheet.vue' ;
import config from "../assets/config" ;
2020-11-11 17:35:14 +01:00
2021-01-14 16:17:05 +01:00
const READ _RECEIPT _TIMEOUT = 5000 ; /* How long a message must have been visible before the read marker is updated */
2021-04-09 16:40:03 +02:00
const WINDOW _BUFFER _SIZE = 0.3 /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */
2021-01-14 16:17:05 +01:00
2020-11-11 17:35:14 +01:00
// from https://kirbysayshi.com/2013/08/19/maintaining-scroll-position-knockoutjs-list.html
function ScrollPosition ( node ) {
this . node = node ;
this . previousScrollHeightMinusTop = 0 ;
2020-11-17 20:02:42 +01:00
this . previousScrollTop = 0 ;
2020-11-11 17:35:14 +01:00
this . readyFor = "up" ;
}
ScrollPosition . prototype . restore = function ( ) {
if ( this . readyFor === "up" ) {
this . node . scrollTop =
this . node . scrollHeight - this . previousScrollHeightMinusTop ;
2020-11-17 20:02:42 +01:00
} else {
this . node . scrollTop = this . previousScrollTop ;
2020-11-11 17:35:14 +01:00
}
} ;
ScrollPosition . prototype . prepareFor = function ( direction ) {
this . readyFor = direction || "up" ;
2020-11-17 20:02:42 +01:00
if ( this . readyFor === "up" ) {
this . previousScrollHeightMinusTop =
this . node . scrollHeight - this . node . scrollTop ;
} else {
this . previousScrollTop = this . node . scrollTop ;
}
2020-11-11 17:35:14 +01:00
} ;
2020-11-09 10:26:56 +01:00
export default {
name : "Chat" ,
2020-11-17 20:32:37 +01:00
components : {
2020-12-04 17:15:18 +01:00
ChatHeader ,
2020-11-17 20:32:37 +01:00
MessageIncomingText ,
2021-04-13 21:55:25 +02:00
MessageIncomingFile ,
2020-12-03 10:00:23 +01:00
MessageIncomingImage ,
MessageIncomingAudio ,
2021-03-17 12:13:53 +01:00
MessageIncomingVideo ,
2021-04-15 11:44:58 +02:00
MessageIncomingSticker ,
2020-11-17 20:32:37 +01:00
MessageOutgoingText ,
2021-04-13 21:55:25 +02:00
MessageOutgoingFile ,
2020-12-03 10:00:23 +01:00
MessageOutgoingImage ,
MessageOutgoingAudio ,
2021-03-17 12:13:53 +01:00
MessageOutgoingVideo ,
2021-04-15 11:44:58 +02:00
MessageOutgoingSticker ,
2020-11-17 20:32:37 +01:00
ContactJoin ,
ContactLeave ,
ContactInvited ,
2021-03-04 17:19:39 +01:00
ContactChanged ,
2020-11-17 20:32:37 +01:00
RoomNameChanged ,
RoomTopicChanged ,
RoomAvatarChanged ,
2021-03-03 12:29:55 +01:00
RoomHistoryVisibility ,
2021-03-26 12:16:13 +01:00
RoomJoinRules ,
2020-11-17 21:24:03 +01:00
DebugEvent ,
2020-11-25 14:42:50 +01:00
MessageOperations ,
2021-04-09 14:03:40 +02:00
MessageOperationsPicker ,
2021-03-04 12:48:32 +01:00
VoiceRecorder ,
2021-03-26 12:16:13 +01:00
RoomInfoBottomSheet ,
2021-04-09 14:27:27 +02:00
CreatedRoomWelcomeHeader ,
2021-04-15 11:44:58 +02:00
MessageOperationsBottomSheet ,
StickerPickerBottomSheet ,
BottomSheet ,
2021-05-10 16:11:03 +02:00
AvatarOperations ,
2020-11-17 20:32:37 +01:00
} ,
2020-11-19 22:48:08 +01:00
data ( ) {
2021-04-15 11:44:58 +02:00
return {
config : config ,
2020-11-19 22:48:08 +01:00
events : [ ] ,
currentInput : "" ,
2020-12-09 21:50:53 +01:00
typingMembers : [ ] ,
2020-11-19 22:48:08 +01:00
timelineWindow : null ,
2021-04-09 16:40:03 +02:00
/** true if we are currently paginating */
timelineWindowPaginating : false ,
2020-11-19 22:48:08 +01:00
scrollPosition : null ,
currentImageInput : null ,
currentImageInputPath : null ,
currentSendOperation : null ,
currentSendProgress : null ,
2021-03-10 17:24:48 +01:00
currentSendShowSendButton : true ,
2020-11-19 22:48:08 +01:00
currentSendError : null ,
2020-11-25 14:42:50 +01:00
showEmojiPicker : false ,
selectedEvent : null ,
2020-12-14 16:11:45 +01:00
editedEvent : null ,
2020-12-15 17:06:26 +01:00
replyToEvent : null ,
2020-12-04 10:44:46 +01:00
showContextMenu : false ,
2021-01-11 17:42:58 +01:00
showContextMenuAnchor : null ,
2021-05-10 16:11:03 +02:00
showAvatarMenu : false ,
showAvatarMenuAnchor : null ,
2021-01-14 16:17:05 +01:00
initialLoadDone : false ,
2021-04-09 16:20:57 +02:00
loading : false , // Set this to true during long operations to show a "spinner" overlay
2021-02-22 16:34:19 +01:00
showRecorder : false ,
2021-03-05 22:34:00 +01:00
showRecorderPTT : false , // True to open the voice recorder in push-to-talk mode.
2021-01-11 17:42:58 +01:00
2020-12-04 10:44:46 +01:00
/ * *
* Current chat container size . We need to keep track of this so that if and when
* a soft keyboard is shown / hidden we can restore the scroll position correctly .
* If we don ' t , the keyboard will simply overflow the message we are answering to etc .
* /
chatContainerSize : 0 ,
2020-12-10 12:37:06 +01:00
2021-02-17 11:59:07 +01:00
/ * *
* True if we should show the "scroll to end" marker in the chat . For now at least , we use a simple
* method here , basically just "if we can scroll, show it" .
* /
showScrollToEnd : false ,
2020-12-10 12:37:06 +01:00
/** Shows a dialog with info about an operation being disallowed for guests */
showNotAllowedForGuests : false ,
2021-01-14 16:17:05 +01:00
/** A timer for read receipts. */
rrTimer : null ,
2021-01-15 11:50:21 +01:00
/** Last event we sent a Read Receipt/Read Marker for */
lastRR : null ,
2021-04-01 22:59:19 +02:00
/** If we just created this room, show a small welcome header with info */
2021-04-09 14:03:40 +02:00
showCreatedRoomWelcomeHeader : false ,
/** An array of recent emojis. Used in the "message operations" popup. */
recentEmojis : [ ]
2020-11-19 22:48:08 +01:00
} ;
} ,
2020-11-09 10:26:56 +01:00
2020-11-11 17:35:14 +01:00
mounted ( ) {
const container = this . $refs . chatContainer ;
this . scrollPosition = new ScrollPosition ( container ) ;
2020-12-04 10:44:46 +01:00
this . chatContainerSize = this . $refs . chatContainerResizer . $el . clientHeight ;
2020-11-11 17:35:14 +01:00
} ,
2021-01-15 11:50:21 +01:00
beforeDestroy ( ) {
this . stopRRTimer ( ) ;
} ,
2020-11-11 17:35:14 +01:00
destroyed ( ) {
this . $matrix . off ( "Room.timeline" , this . onEvent ) ;
this . $matrix . off ( "RoomMember.typing" , this . onUserTyping ) ;
} ,
2020-11-09 10:26:56 +01:00
computed : {
2021-01-11 17:42:58 +01:00
currentUser ( ) {
return this . $store . state . auth . user ;
} ,
2020-12-09 15:20:50 +01:00
room ( ) {
return this . $matrix . currentRoom ;
} ,
2020-11-09 10:26:56 +01:00
roomId ( ) {
2021-02-25 14:21:21 +01:00
if ( ! this . $matrix . ready && this . currentUser ) {
// If we have a user already, wait for ready state. If not, we
// dont want to return here, because we want to redirect to "join".
2021-02-17 17:12:16 +01:00
return null ; // Not ready yet...
}
2021-01-15 11:50:21 +01:00
if ( this . room ) {
return this . room . roomId ;
}
2021-01-11 17:42:58 +01:00
return this . $matrix . currentRoomId ;
2020-11-09 10:26:56 +01:00
} ,
2021-01-28 22:13:08 +01:00
roomAliasOrId ( ) {
if ( this . room ) {
return this . room . getCanonicalAlias ( ) || this . room . roomId ;
}
return this . $matrix . currentRoomId ;
} ,
2021-01-14 16:17:05 +01:00
readMarker ( ) {
2021-01-15 11:50:21 +01:00
if ( this . lastRR ) {
// If we have sent a RR, use that as read marker (so we don't have to wait for server round trip)
return this . lastRR . getId ( ) ;
}
2021-02-17 11:59:07 +01:00
return (
this . fullyReadMarker ||
this . room . getEventReadUpTo ( this . $matrix . currentUserId , false )
) ;
2021-01-14 16:17:05 +01:00
} ,
fullyReadMarker ( ) {
2021-02-17 11:59:07 +01:00
const readEvent = this . room . getAccountData ( "m.fully_read" ) ;
2021-01-14 16:17:05 +01:00
if ( readEvent ) {
return readEvent . getContent ( ) . event _id ;
}
return null ;
} ,
2020-12-14 17:12:29 +01:00
attachButtonDisabled ( ) {
2021-02-17 11:59:07 +01:00
return (
this . editedEvent != null ||
this . replyToEvent != null ||
this . currentInput . length > 0
) ;
2020-12-14 17:12:29 +01:00
} ,
2020-11-09 10:26:56 +01:00
sendButtonDisabled ( ) {
return this . currentInput . length == 0 ;
} ,
2020-12-09 21:50:53 +01:00
typingMembersString ( ) {
const count = this . typingMembers . length ;
if ( count > 1 ) {
return "" + count + " members are typing" ;
} else if ( count > 0 ) {
return this . typingMembers [ 0 ] . name + " is typing" ;
} else {
return "" ;
}
2020-12-14 16:11:45 +01:00
} ,
opStyle ( ) {
// Calculate where to show the context menu.
//
const ref = this . selectedEvent && this . $refs [ this . selectedEvent . getId ( ) ] ;
2021-01-11 17:42:58 +01:00
var top = 0 ;
var left = 0 ;
2020-12-14 16:11:45 +01:00
if ( ref && ref [ 0 ] ) {
2021-01-11 17:42:58 +01:00
if ( this . showContextMenuAnchor ) {
var rectAnchor = this . showContextMenuAnchor . getBoundingClientRect ( ) ;
var rectChat = this . $refs . messageOperationsStrut . getBoundingClientRect ( ) ;
top = rectAnchor . top - rectChat . top ;
left = rectAnchor . left - rectChat . left ;
2021-04-09 14:03:40 +02:00
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)!
}
2021-01-11 17:42:58 +01:00
}
2020-12-14 16:11:45 +01:00
}
2021-01-11 17:42:58 +01:00
return "top:" + top + "px;left:" + left + "px" ;
2021-02-17 11:59:07 +01:00
} ,
2021-05-10 16:11:03 +02:00
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" ;
} ,
2021-03-18 11:58:46 +01:00
canRecordAudio ( ) {
return util . browserCanRecordAudio ( ) ;
2021-03-26 12:16:13 +01:00
} ,
2021-04-13 19:30:35 +02:00
debugging ( ) {
return ( window . location . host || "" ) . startsWith ( "localhost" ) ;
}
2020-11-09 10:26:56 +01:00
} ,
watch : {
2021-01-28 22:13:08 +01:00
roomId : {
2020-12-15 17:06:26 +01:00
immediate : true ,
2021-01-28 22:13:08 +01:00
handler ( value , oldValue ) {
if ( value && value == oldValue ) {
2021-01-12 11:26:01 +01:00
return ; // No change.
}
2021-02-17 11:59:07 +01:00
console . log (
"Chat: Current room changed to " + ( value ? value : "null" )
) ;
2020-11-09 10:26:56 +01:00
2020-11-25 14:42:50 +01:00
// Clear old events
2021-03-04 11:31:21 +01:00
this . $matrix . off ( "Room.timeline" , this . onEvent ) ;
this . $matrix . off ( "RoomMember.typing" , this . onUserTyping ) ;
2020-11-25 14:42:50 +01:00
this . events = [ ] ;
this . timelineWindow = null ;
2020-12-09 21:50:53 +01:00
this . typingMembers = [ ] ;
2021-01-14 16:17:05 +01:00
this . initialLoadDone = false ;
2021-01-15 11:50:21 +01:00
// Stop RR timer
this . stopRRTimer ( ) ;
this . lastRR = null ;
2021-01-28 22:13:08 +01:00
if ( ! this . room ) {
2021-01-11 17:42:58 +01:00
// Public room?
2021-02-17 11:59:07 +01:00
if ( this . roomId && this . roomId . startsWith ( "#" ) ) {
2021-01-11 17:42:58 +01:00
this . onRoomNotJoined ( ) ;
2021-03-04 11:31:21 +01:00
} else if ( this . roomId ) {
this . onRoomNotJoined ( ) ; // Private room we are not joined to. What to do? We redirect to join
// screen, maybe the user has an invite already?
2021-01-11 17:42:58 +01:00
}
2021-03-04 11:31:21 +01:00
this . initialLoadDone = true ;
2020-11-25 14:42:50 +01:00
return ; // no room
}
2020-11-11 17:35:14 +01:00
2021-02-17 11:59:07 +01:00
// Joined?
if ( this . room . hasMembershipState ( this . currentUser . user _id , "join" ) ) {
// Yes, load everything
2021-03-01 21:47:22 +01:00
this . onRoomJoined ( this . readMarker ) ;
2021-02-17 11:59:07 +01:00
} else {
this . onRoomNotJoined ( ) ;
}
} ,
2021-01-11 17:42:58 +01:00
} ,
} ,
methods : {
2021-03-01 21:47:22 +01:00
onRoomJoined ( initialEventId ) {
2021-04-01 22:59:19 +02:00
// Was this room just created (by you)? Show a small info header in
// that case!
const createEvent = this . room . currentState . getStateEvents ( "m.room.create" , "" ) ;
if ( createEvent ) {
const creatorId = createEvent . getContent ( ) . creator ;
2021-04-02 10:06:25 +02:00
if ( creatorId == this . $matrix . currentUserId && createEvent . getLocalAge ( ) < ( 5 * 60000 ) /* 5 minutes */ ) {
2021-04-01 22:59:19 +02:00
this . showCreatedRoomWelcomeHeader = true ;
}
}
2021-03-04 11:31:21 +01:00
// Listen to events
this . $matrix . on ( "Room.timeline" , this . onEvent ) ;
this . $matrix . on ( "RoomMember.typing" , this . onUserTyping ) ;
2021-01-14 16:17:05 +01:00
console . log ( "Read up to " + initialEventId ) ;
2021-04-09 16:40:03 +02:00
//initialEventId = null;
2021-02-17 11:59:07 +01:00
2021-01-11 17:42:58 +01:00
this . timelineWindow = new TimelineWindow (
2021-02-17 11:59:07 +01:00
this . $matrix . matrixClient ,
this . room . getUnfilteredTimelineSet ( ) ,
{ }
2021-01-11 17:42:58 +01:00
) ;
2021-01-28 22:13:08 +01:00
const self = this ;
2021-03-04 12:48:32 +01:00
this . timelineWindow
. load ( initialEventId , 20 )
. then ( ( ) => {
console . log ( "This is" , self ) ;
self . events = self . timelineWindow . getEvents ( ) ;
const getMoreIfNeeded = function _getMoreIfNeeded ( ) {
const container = self . $refs . chatContainer ;
if (
2021-04-09 16:40:03 +02:00
container . scrollHeight <= ( 1 + 2 * WINDOW _BUFFER _SIZE ) * container . clientHeight &&
2021-03-04 12:48:32 +01:00
self . timelineWindow &&
self . timelineWindow . canPaginate ( EventTimeline . BACKWARDS )
) {
return self . timelineWindow
. paginate ( EventTimeline . BACKWARDS , 10 , true , 5 )
. then ( ( success ) => {
self . events = self . timelineWindow . getEvents ( ) ;
if ( success ) {
return _getMoreIfNeeded . call ( self ) ;
} else {
return Promise . reject ( "Failed to paginate" ) ;
}
} ) ;
} else {
return Promise . resolve ( "Done" ) ;
}
} . bind ( self ) ;
getMoreIfNeeded ( )
. catch ( ( err ) => {
console . log ( "ERROR " + err ) ;
} )
. finally ( ( ) => {
self . initialLoadDone = true ;
2021-04-01 22:59:19 +02:00
if ( initialEventId && ! this . showCreatedRoomWelcomeHeader ) {
2021-03-04 12:48:32 +01:00
self . scrollToEvent ( initialEventId ) ;
2021-04-01 22:59:19 +02:00
} else if ( this . showCreatedRoomWelcomeHeader ) {
self . onScroll ( ) ;
2021-03-04 12:48:32 +01:00
}
self . restartRRTimer ( ) ;
} ) ;
} )
. catch ( ( err ) => {
console . log ( "Error fetching events!" , err , this ) ;
if ( err . errcode == "M_UNKNOWN" && initialEventId ) {
// Try again without initial event!
this . onRoomJoined ( null ) ;
2021-02-17 11:59:07 +01:00
} else {
2021-03-04 12:48:32 +01:00
// Error. Done loading.
this . events = this . timelineWindow . getEvents ( ) ;
this . initialLoadDone = true ;
2021-02-17 11:59:07 +01:00
}
2021-03-04 12:48:32 +01:00
} ) ;
2020-11-09 10:26:56 +01:00
} ,
2021-01-11 17:42:58 +01:00
onRoomNotJoined ( ) {
2021-02-17 11:59:07 +01:00
this . $navigation . push (
{
name : "Join" ,
params : { roomId : util . sanitizeRoomId ( this . roomAliasOrId ) } ,
} ,
0
) ;
2021-01-11 17:42:58 +01:00
} ,
2021-04-09 16:20:57 +02:00
scrollToEndOfTimeline ( ) {
if ( this . timelineWindow && this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) ) {
this . loading = true ;
// Instead of paging though ALL history, just reload a timeline at the live marker...
var timelineWindow = new TimelineWindow ( this . $matrix . matrixClient , this . room . getUnfilteredTimelineSet ( ) , { } ) ;
const self = this ;
timelineWindow
. load ( null , 20 )
. then ( ( ) => {
self . timelineWindow = timelineWindow ;
self . events = self . timelineWindow . getEvents ( ) ;
} )
. finally ( ( ) => {
this . loading = false ;
} ) ;
} else {
// Can't paginate, just scroll to bottom of window!
this . smoothScrollToEnd ( ) ;
}
} ,
2020-12-03 22:12:50 +01:00
touchX ( event ) {
2020-12-04 10:44:46 +01:00
if ( event . type . indexOf ( "mouse" ) !== - 1 ) {
2020-12-03 22:12:50 +01:00
return event . clientX ;
2020-12-03 10:00:23 +01:00
}
2020-12-03 22:12:50 +01:00
return event . touches [ 0 ] . clientX ;
2020-12-03 10:00:23 +01:00
} ,
2020-12-03 22:12:50 +01:00
touchY ( event ) {
2020-12-04 10:44:46 +01:00
if ( event . type . indexOf ( "mouse" ) !== - 1 ) {
2020-12-03 22:12:50 +01:00
return event . clientY ;
}
return event . touches [ 0 ] . clientY ;
} ,
touchStart ( e , event ) {
if ( this . selectedEvent != event ) {
this . showContextMenu = false ;
}
2020-12-03 10:00:23 +01:00
this . selectedEvent = event ;
2020-12-03 22:12:50 +01:00
this . touchStartX = this . touchX ( e ) ;
this . touchStartY = this . touchY ( e ) ;
this . touchTimer = setTimeout ( this . touchTimerElapsed , 500 ) ;
} ,
touchEnd ( ) {
this . touchTimer && clearTimeout ( this . touchTimer ) ;
} ,
touchCancel ( ) {
this . touchTimer && clearTimeout ( this . touchTimer ) ;
} ,
touchMove ( e ) {
this . touchCurrentX = this . touchX ( e ) ;
this . touchCurrentY = this . touchY ( e ) ;
var tapTolerance = 4 ;
2020-12-04 10:44:46 +01:00
var touchMoved =
Math . abs ( this . touchStartX - this . touchCurrentX ) > tapTolerance ||
Math . abs ( this . touchStartY - this . touchCurrentY ) > tapTolerance ;
if ( touchMoved ) {
2020-12-03 22:12:50 +01:00
this . touchTimer && clearTimeout ( this . touchTimer ) ;
}
} ,
2020-12-04 10:44:46 +01:00
/ * *
2021-03-05 22:34:00 +01:00
* Triggered when our "long tap" timer hits .
2020-12-04 10:44:46 +01:00
* /
2020-12-03 22:12:50 +01:00
touchTimerElapsed ( ) {
2021-04-09 14:03:40 +02:00
this . updateRecentEmojis ( ) ;
2020-12-03 10:00:23 +01:00
this . showContextMenu = true ;
} ,
2020-12-04 10:44:46 +01:00
/ * *
* If chat container is shrunk ( probably because soft keyboard is shown ) adjust
* the scroll position so that e . g . if we were looking at the last message when
* moving focus to the input field , we would still see the last message . Otherwise
* if would be hidden behind the keyboard .
* /
2020-12-04 12:15:47 +01:00
handleChatContainerResize ( { ignoredWidth , height } ) {
2020-12-04 10:44:46 +01:00
const delta = height - this . chatContainerSize ;
this . chatContainerSize = height ;
const container = this . $refs . chatContainer ;
if ( delta < 0 ) {
container . scrollTop -= delta ;
}
} ,
2020-11-17 21:24:03 +01:00
componentForEvent ( event ) {
switch ( event . getType ( ) ) {
2020-11-19 22:48:08 +01:00
case "m.room.member" :
2020-11-25 14:42:50 +01:00
if ( event . getContent ( ) . membership == "join" ) {
2021-03-11 13:55:10 +01:00
if (
event . getPrevContent ( ) &&
event . getPrevContent ( ) . membership == "join"
) {
2021-03-04 17:19:39 +01:00
// We we already joined, so this must be a display name and/or avatar update!
return ContactChanged ;
} else {
return ContactJoin ;
}
2020-11-25 14:42:50 +01:00
} else if ( event . getContent ( ) . membership == "leave" ) {
return ContactLeave ;
} else if ( event . getContent ( ) . membership == "invite" ) {
return ContactInvited ;
2020-11-17 21:24:03 +01:00
}
2020-12-04 10:44:46 +01:00
break ;
2020-11-17 21:24:03 +01:00
2020-11-19 22:48:08 +01:00
case "m.room.message" :
2020-11-25 14:42:50 +01:00
if ( event . getSender ( ) != this . $matrix . currentUserId ) {
2020-11-19 22:48:08 +01:00
if ( event . getContent ( ) . msgtype == "m.image" ) {
2021-05-06 17:09:03 +02:00
// For SVG, make downloadable
if ( event . getContent ( ) . info && event . getContent ( ) . info . mimetype && event . getContent ( ) . info . mimetype . startsWith ( "image/svg" ) ) {
return MessageIncomingFile ;
}
2020-11-17 21:24:03 +01:00
return MessageIncomingImage ;
2020-12-03 10:00:23 +01:00
} else if ( event . getContent ( ) . msgtype == "m.audio" ) {
return MessageIncomingAudio ;
2021-03-17 12:13:53 +01:00
} else if ( event . getContent ( ) . msgtype == "m.video" ) {
return MessageIncomingVideo ;
2021-04-13 21:55:25 +02:00
} else if ( event . getContent ( ) . msgtype == "m.file" ) {
return MessageIncomingFile ;
2021-04-15 11:44:58 +02:00
} else if ( stickers . isStickerShortcode ( event . getContent ( ) . body ) ) {
return MessageIncomingSticker ;
2020-11-17 21:24:03 +01:00
}
return MessageIncomingText ;
} else {
2020-11-19 22:48:08 +01:00
if ( event . getContent ( ) . msgtype == "m.image" ) {
2021-05-06 17:09:03 +02:00
// For SVG, make downloadable
if ( event . getContent ( ) . info && event . getContent ( ) . info . mimetype && event . getContent ( ) . info . mimetype . startsWith ( "image/svg" ) ) {
return MessageOutgoingImage ;
}
2020-11-17 21:24:03 +01:00
return MessageOutgoingImage ;
2020-12-03 10:00:23 +01:00
} else if ( event . getContent ( ) . msgtype == "m.audio" ) {
return MessageOutgoingAudio ;
2021-03-17 12:13:53 +01:00
} else if ( event . getContent ( ) . msgtype == "m.video" ) {
return MessageOutgoingVideo ;
2021-04-13 21:55:25 +02:00
} else if ( event . getContent ( ) . msgtype == "m.file" ) {
return MessageOutgoingFile ;
2021-04-15 11:44:58 +02:00
} else if ( stickers . isStickerShortcode ( event . getContent ( ) . body ) ) {
return MessageOutgoingSticker ;
2020-11-17 21:24:03 +01:00
}
return MessageOutgoingText ;
}
2020-11-19 22:48:08 +01:00
case "m.room.name" :
2020-11-17 21:24:03 +01:00
return RoomNameChanged ;
2020-11-19 22:48:08 +01:00
case "m.room.topic" :
2020-11-17 21:24:03 +01:00
return RoomTopicChanged ;
2020-11-19 22:48:08 +01:00
case "m.room.avatar" :
2020-11-17 21:24:03 +01:00
return RoomAvatarChanged ;
2021-03-03 12:29:55 +01:00
case "m.room.history_visibility" :
return RoomHistoryVisibility ;
2021-03-26 12:16:13 +01:00
case "m.room.join_rules" :
return RoomJoinRules ;
2020-11-17 21:24:03 +01:00
}
2021-04-13 19:30:35 +02:00
return this . debugging ? DebugEvent : null ;
2020-11-17 21:24:03 +01:00
} ,
2020-11-11 17:35:14 +01:00
paginateBackIfNeeded ( ) {
2020-11-17 20:02:42 +01:00
this . $nextTick ( ( ) => {
const container = this . $refs . chatContainer ;
if ( container . scrollHeight <= container . clientHeight ) {
this . handleScrolledToTop ( ) ;
}
} ) ;
2020-11-11 17:35:14 +01:00
} ,
onScroll ( ignoredevent ) {
const container = this . $refs . chatContainer ;
2021-01-15 11:50:21 +01:00
if ( ! container ) {
return ;
}
2021-04-09 16:40:03 +02:00
const bufferHeight = container . clientHeight * WINDOW _BUFFER _SIZE ;
if ( container . scrollTop <= bufferHeight ) {
2020-11-11 17:35:14 +01:00
// Scrolled to top
this . handleScrolledToTop ( ) ;
} else if (
2021-04-09 16:40:03 +02:00
container . scrollHeight - container . scrollTop . toFixed ( 0 ) - container . clientHeight <= bufferHeight
2020-11-11 17:35:14 +01:00
) {
2020-11-17 20:02:42 +01:00
this . handleScrolledToBottom ( false ) ;
2020-11-11 17:35:14 +01:00
}
2021-02-17 11:59:07 +01:00
this . showScrollToEnd =
container . scrollHeight - container . scrollTop . toFixed ( 0 ) >
2021-04-09 17:04:30 +02:00
container . clientHeight || ( this . timelineWindow &&
this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) ) ;
2021-02-17 11:59:07 +01:00
2021-01-14 16:17:05 +01:00
this . restartRRTimer ( ) ;
2020-11-11 17:35:14 +01:00
} ,
2020-11-09 15:08:36 +01:00
onEvent ( event ) {
2020-12-10 12:37:06 +01:00
console . log ( "OnEvent" , JSON . stringify ( event ) ) ;
2020-11-09 15:08:36 +01:00
if ( event . getRoomId ( ) !== this . roomId ) {
return ; // Not for this room
}
2021-01-14 16:17:05 +01:00
if ( this . initialLoadDone ) {
this . paginateBackIfNeeded ( ) ;
}
2020-11-17 20:02:42 +01:00
// If we are at bottom, scroll to see new events...
const container = this . $refs . chatContainer ;
2020-12-04 10:44:46 +01:00
var scrollToSeeNew = event . getSender ( ) == this . $matrix . currentUserId ; // When we sent, scroll
2020-11-17 20:02:42 +01:00
if (
2020-12-03 22:12:50 +01:00
container . scrollHeight - container . scrollTop . toFixed ( 0 ) ==
2020-11-17 20:02:42 +01:00
container . clientHeight
) {
2020-12-03 22:12:50 +01:00
scrollToSeeNew = true ;
}
2021-01-14 16:17:05 +01:00
if ( this . initialLoadDone && event . forwardLooking && ! event . isRelation ( ) ) {
2020-12-03 22:12:50 +01:00
this . handleScrolledToBottom ( scrollToSeeNew ) ;
2020-11-17 20:02:42 +01:00
}
2020-11-09 15:08:36 +01:00
} ,
2020-12-09 21:50:53 +01:00
onUserTyping ( event , member ) {
if ( member . roomId !== this . roomId ) {
2020-11-11 17:35:14 +01:00
return ; // Not for this room
2020-11-09 10:26:56 +01:00
}
2020-12-09 21:50:53 +01:00
if ( member . typing ) {
if ( ! this . typingMembers . includes ( member ) ) {
this . typingMembers . push ( member ) ;
}
} else {
const index = this . typingMembers . indexOf ( member ) ;
if ( index > - 1 ) {
2021-02-17 11:59:07 +01:00
this . typingMembers . splice ( index , 1 ) ;
2020-12-09 21:50:53 +01:00
}
}
console . log ( "Typing: " , this . typingMembers ) ;
2020-11-09 10:26:56 +01:00
} ,
2021-03-10 17:24:48 +01:00
sendCurrentTextMessage ( ) {
// DOn't have "enter" send messages while in recorder.
if ( this . currentInput . length > 0 && ! this . showRecorder ) {
this . sendMessage ( this . currentInput ) ;
this . currentInput = "" ;
this . editedEvent = null ; //TODO - Is this a good place to reset this?
this . replyToEvent = null ;
}
} ,
sendMessage ( text ) {
if ( text && text . length > 0 ) {
2020-11-25 14:42:50 +01:00
util
. sendTextMessage (
this . $matrix . matrixClient ,
this . roomId ,
2021-03-10 17:24:48 +01:00
text ,
2020-12-15 17:06:26 +01:00
this . editedEvent ,
this . replyToEvent
2020-11-25 14:42:50 +01:00
)
. then ( ( ) => {
console . log ( "Sent message" ) ;
} )
. catch ( ( err ) => {
console . log ( "Failed to send:" , err ) ;
} ) ;
2020-11-09 10:26:56 +01:00
}
} ,
2020-11-17 20:02:42 +01:00
/ * *
2020-12-10 12:37:06 +01:00
* Show attachment picker to select file
* /
showAttachmentPicker ( ) {
// Guests not currently allowed to send attachments (=actually upload them)
// See https://matrix.org/docs/spec/client_server/r0.2.0#guest-access
2021-01-20 09:42:13 +01:00
// if (this.$matrix.currentUser && this.$matrix.currentUser.is_guest) {
// this.showNotAllowedForGuests = true;
// return;
// }
2021-02-17 11:59:07 +01:00
this . $refs . attachment . click ( ) ;
2020-12-10 12:37:06 +01:00
} ,
/ * *
* Handle picked attachment
2020-11-17 20:02:42 +01:00
* /
2020-12-10 12:37:06 +01:00
handlePickedAttachment ( event ) {
2020-11-17 20:02:42 +01:00
if ( event . target . files && event . target . files [ 0 ] ) {
var reader = new FileReader ( ) ;
reader . onload = ( e ) => {
2021-03-10 17:24:48 +01:00
this . currentSendShowSendButton = true ;
2020-11-17 20:02:42 +01:00
this . currentImageInput = e . target . result ;
this . currentImageInputPath = event . target . files [ 0 ] ;
} ;
reader . readAsDataURL ( event . target . files [ 0 ] ) ;
}
} ,
2021-04-15 11:44:58 +02:00
showStickerPicker ( ) {
this . $refs . stickerPickerSheet . open ( ) ;
} ,
2020-11-17 20:02:42 +01:00
onUploadProgress ( p ) {
if ( p . total ) {
this . currentSendProgress =
"Uploaded " + ( p . loaded || 0 ) + " of " + p . total ;
} else {
this . currentSendProgress = "Uploaded " + ( p . loaded || 0 ) ;
}
} ,
2021-03-10 17:24:48 +01:00
sendAttachment ( withText ) {
2020-11-17 20:02:42 +01:00
if ( this . currentImageInputPath ) {
2021-03-10 13:40:32 +01:00
this . currentSendProgress = null ;
2020-11-25 14:42:50 +01:00
this . currentSendOperation = util . sendImage (
this . $matrix . matrixClient ,
this . roomId ,
this . currentImageInputPath ,
this . onUploadProgress
) ;
2020-11-17 20:02:42 +01:00
this . currentSendOperation
2020-11-25 14:42:50 +01:00
. then ( ( ) => {
this . currentSendOperation = null ;
this . currentImageInput = null ;
2021-03-10 17:24:48 +01:00
this . currentImageInputPath = null ;
2021-03-10 13:40:32 +01:00
this . currentSendProgress = null ;
2021-03-10 17:24:48 +01:00
if ( withText ) {
this . sendMessage ( withText ) ;
}
2020-11-25 14:42:50 +01:00
} )
. catch ( ( err ) => {
this . currentSendError = err . toLocaleString ( ) ;
this . currentSendOperation = null ;
2021-03-10 13:40:32 +01:00
this . currentSendProgress = null ;
2020-11-25 14:42:50 +01:00
} ) ;
2020-11-17 20:02:42 +01:00
}
} ,
2020-11-21 14:57:43 +01:00
cancelSendAttachment ( ) {
if ( this . currentSendOperation ) {
this . currentSendOperation . reject ( "Canceled" ) ;
}
2020-12-03 10:00:23 +01:00
this . currentSendOperation = null ;
2020-11-21 14:57:43 +01:00
this . currentImageInput = null ;
2021-03-10 17:24:48 +01:00
this . currentImageInputPath = null ;
2021-03-10 13:40:32 +01:00
this . currentSendProgress = null ;
2020-12-03 10:00:23 +01:00
this . currentSendError = null ;
2020-11-09 10:26:56 +01:00
} ,
2020-11-11 17:35:14 +01:00
handleScrolledToTop ( ) {
console . log ( "@top" ) ;
if (
this . timelineWindow &&
2021-04-09 16:40:03 +02:00
this . timelineWindow . canPaginate ( EventTimeline . BACKWARDS ) &&
! this . timelineWindowPaginating
2020-11-11 17:35:14 +01:00
) {
2021-04-09 16:40:03 +02:00
this . timelineWindowPaginating = true ;
2020-11-11 17:35:14 +01:00
this . timelineWindow
. paginate ( EventTimeline . BACKWARDS , 10 , true )
. then ( ( success ) => {
if ( success ) {
this . scrollPosition . prepareFor ( "up" ) ;
this . events = this . timelineWindow . getEvents ( ) ;
this . $nextTick ( ( ) => {
// restore scroll position!
console . log ( "Restore scroll!" ) ;
this . scrollPosition . restore ( ) ;
} ) ;
}
2021-04-09 16:40:03 +02:00
} )
. finally ( ( ) => {
this . timelineWindowPaginating = false ;
2020-11-11 17:35:14 +01:00
} ) ;
}
} ,
2020-11-17 20:02:42 +01:00
handleScrolledToBottom ( scrollToEnd ) {
2020-11-11 17:35:14 +01:00
console . log ( "@bottom" ) ;
2020-11-17 20:02:42 +01:00
if (
this . timelineWindow &&
2021-04-09 16:40:03 +02:00
this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) &&
! this . timelineWindowPaginating
2020-11-17 20:02:42 +01:00
) {
2021-04-09 16:40:03 +02:00
this . timelineWindowPaginating = true ;
2020-11-17 20:02:42 +01:00
this . timelineWindow
. paginate ( EventTimeline . FORWARDS , 10 , true )
. then ( ( success ) => {
if ( success ) {
this . scrollPosition . prepareFor ( "down" ) ;
this . events = this . timelineWindow . getEvents ( ) ;
this . $nextTick ( ( ) => {
// restore scroll position!
console . log ( "Restore scroll!" ) ;
this . scrollPosition . restore ( ) ;
if ( scrollToEnd ) {
this . smoothScrollToEnd ( ) ;
}
} ) ;
}
2021-04-09 16:40:03 +02:00
} )
. finally ( ( ) => {
this . timelineWindowPaginating = false ;
2020-11-17 20:02:42 +01:00
} ) ;
}
} ,
2021-01-14 16:17:05 +01:00
/ * *
2021-02-17 11:59:07 +01:00
* Scroll so that the given event is at the middle of the chat view ( if more events ) or else at the bottom .
* /
2021-01-14 16:17:05 +01:00
scrollToEvent ( eventId ) {
const container = this . $refs . chatContainer ;
const ref = this . $refs [ eventId ] ;
if ( container && ref ) {
const targetY = container . clientHeight / 2 ;
const sourceY = ref [ 0 ] . offsetTop ;
container . scrollTo ( 0 , sourceY - targetY ) ;
}
} ,
2020-11-17 20:02:42 +01:00
smoothScrollToEnd ( ) {
this . $nextTick ( function ( ) {
const container = this . $refs . chatContainer ;
if ( container . children . length > 0 ) {
const lastChild = container . children [ container . children . length - 1 ] ;
console . log ( "Scroll into view" , lastChild ) ;
window . requestAnimationFrame ( ( ) => {
lastChild . scrollIntoView ( {
behavior : "smooth" ,
block : "start" ,
inline : "nearest" ,
} ) ;
} ) ;
}
} ) ;
2020-11-11 17:35:14 +01:00
} ,
2020-11-25 14:42:50 +01:00
2021-04-09 14:03:40 +02:00
showMoreMessageOperations ( e ) {
this . addReaction ( e ) ;
} ,
2020-11-25 14:42:50 +01:00
addReaction ( e ) {
const event = e . event ;
// Store the event we are reacting to, so that we know where to
// send when the picker closes.
this . selectedEvent = event ;
2021-04-09 14:27:27 +02:00
this . $refs . messageOperationsSheet . open ( ) ;
2020-11-25 14:42:50 +01:00
this . showEmojiPicker = true ;
} ,
2021-04-09 14:03:40 +02:00
addQuickReaction ( e ) {
this . sendQuickReaction ( { reaction : e . emoji , event : e . event } ) ;
} ,
2020-12-15 17:06:26 +01:00
addReply ( event ) {
this . replyToEvent = event ;
this . $refs . messageInput . focus ( ) ;
} ,
2020-12-14 16:11:45 +01:00
edit ( event ) {
this . editedEvent = event ;
this . currentInput = event . getContent ( ) . body ;
2020-12-14 16:30:27 +01:00
this . $refs . messageInput . focus ( ) ;
2020-12-14 16:11:45 +01:00
} ,
2021-02-08 15:31:09 +01:00
redact ( event ) {
2021-02-17 11:59:07 +01:00
this . $matrix . matrixClient
. redactEvent ( event . getRoomId ( ) , event . getId ( ) )
. then ( ( ) => {
console . log ( "Message redacted" ) ;
} )
. catch ( ( err ) => {
console . log ( "Redaction failed: " , err ) ;
} ) ;
2021-02-08 15:31:09 +01:00
} ,
2021-01-12 09:25:39 +01:00
download ( event ) {
util
. getAttachment ( this . $matrix . matrixClient , event )
. then ( ( url ) => {
const link = document . createElement ( "a" ) ;
link . href = url ;
2021-01-28 22:13:08 +01:00
link . target = "_blank" ;
2021-01-12 09:25:39 +01:00
link . download = event . getContent ( ) . body || "Download" ;
link . click ( ) ;
URL . revokeObjectURL ( url ) ;
} )
. catch ( ( err ) => {
console . log ( "Failed to fetch attachment: " , err ) ;
} ) ;
} ,
2020-12-15 17:06:26 +01:00
cancelEditReply ( ) {
2020-12-14 17:12:29 +01:00
this . currentInput = "" ;
this . editedEvent = null ;
2020-12-15 17:06:26 +01:00
this . replyToEvent = null ;
2020-12-14 17:12:29 +01:00
} ,
2020-11-25 14:42:50 +01:00
emojiSelected ( e ) {
this . showEmojiPicker = false ;
if ( this . selectedEvent ) {
const event = this . selectedEvent ;
this . selectedEvent = null ;
2020-12-04 10:44:46 +01:00
this . sendQuickReaction ( { reaction : e . data , event : event } ) ;
2020-11-25 14:42:50 +01:00
}
} ,
sendQuickReaction ( e ) {
util
2020-12-04 10:44:46 +01:00
. sendQuickReaction (
this . $matrix . matrixClient ,
this . roomId ,
e . reaction ,
e . event
)
. then ( ( ) => {
console . log ( "Quick reaction message" ) ;
} )
. catch ( ( err ) => {
console . log ( "Failed to send quick reaction:" , err ) ;
} ) ;
} ,
2020-12-14 16:11:45 +01:00
2021-04-15 11:44:58 +02:00
sendSticker ( stickerShortCode ) {
this . sendMessage ( stickerShortCode ) ;
} ,
2021-01-11 17:42:58 +01:00
showContextMenuForEvent ( e ) {
const event = e . event ;
2020-12-14 16:11:45 +01:00
const ref = this . $refs [ event . getId ( ) ] ;
if ( ref ) {
console . log ( "Got the ref" , ref ) ;
}
this . selectedEvent = event ;
2021-04-09 14:03:40 +02:00
this . updateRecentEmojis ( ) ;
2020-12-14 16:11:45 +01:00
this . showContextMenu = true ;
2021-01-11 17:42:58 +01:00
this . showContextMenuAnchor = e . anchor ;
2020-12-14 16:11:45 +01:00
} ,
2021-05-10 16:11:03 +02:00
showAvatarMenuForEvent ( e ) {
const event = e . event ;
this . selectedEvent = event ;
this . showAvatarMenu = true ;
this . showAvatarMenuAnchor = e . anchor ;
} ,
2021-03-04 12:17:21 +01:00
viewProfile ( ) {
this . $navigation . push ( { name : "Profile" } , 1 ) ;
} ,
2021-05-10 16:11:03 +02:00
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 ) {
2020-12-14 16:11:45 +01:00
if ( this . showContextMenu ) {
this . showContextMenu = false ;
2021-05-10 16:11:03 +02:00
this . showContextMenuAnchor = null ;
e . preventDefault ( ) ;
}
if ( this . showAvatarMenu ) {
this . showAvatarMenu = false ;
this . showAvatarMenuAnchor = null ;
2020-12-14 16:11:45 +01:00
e . preventDefault ( ) ;
}
2021-01-14 16:17:05 +01:00
} ,
2021-01-15 11:50:21 +01:00
/** Stop Read Receipt timer */
stopRRTimer ( ) {
2021-01-14 16:17:05 +01:00
if ( this . rrTimer ) {
2021-03-03 12:29:55 +01:00
clearTimeout ( this . rrTimer ) ;
2021-01-14 16:17:05 +01:00
this . rrTimer = null ;
}
2021-01-15 11:50:21 +01:00
} ,
2021-02-17 11:59:07 +01:00
2021-01-15 11:50:21 +01:00
/ * *
2021-02-17 11:59:07 +01:00
* Start / restart the timer to Read Receipts .
* /
2021-01-15 11:50:21 +01:00
restartRRTimer ( ) {
this . stopRRTimer ( ) ;
2021-03-03 12:29:55 +01:00
this . rrTimer = setTimeout ( this . rrTimerElapsed , READ _RECEIPT _TIMEOUT ) ;
2021-01-14 16:17:05 +01:00
} ,
rrTimerElapsed ( ) {
2021-03-03 12:29:55 +01:00
this . rrTimer = null ;
2021-01-14 16:17:05 +01:00
const container = this . $refs . chatContainer ;
2021-03-03 12:29:55 +01:00
const elFirst = util . getFirstVisibleElement ( container ) ;
const elLast = util . getLastVisibleElement ( container ) ;
if ( elFirst && elLast ) {
const eventIdFirst = elFirst . getAttribute ( "eventId" ) ;
const eventIdLast = elLast . getAttribute ( "eventId" ) ;
if ( eventIdLast && this . room ) {
var event = this . room . findEventById ( eventIdLast ) ;
const index = this . events . indexOf ( event ) ;
// Walk backwards through visible events to the first one that is incoming
//
var lastTimestamp = 0 ;
if ( this . lastRR ) {
lastTimestamp = this . lastRR . getTs ( ) ;
}
for ( var i = index ; i >= 0 ; i -- ) {
event = this . events [ i ] ;
if ( event == this . lastRR || event . getTs ( ) <= lastTimestamp ) {
// Already sent this or too old...
break ;
}
2021-03-04 12:48:32 +01:00
// Make sure it's not a local echo event...
if ( ! event . getId ( ) . startsWith ( "~" ) ) {
// Send read receipt
this . $matrix . matrixClient
. sendReadReceipt ( event )
. then ( ( ) => {
this . $matrix . matrixClient . setRoomReadMarkers (
this . room . roomId ,
event . getId ( )
) ;
} )
. then ( ( ) => {
console . log ( "RR sent for event: " + event . getId ( ) ) ;
this . lastRR = event ;
} )
. catch ( ( err ) => {
console . log ( "Failed to update read marker: " , err ) ;
} )
. finally ( ( ) => {
this . restartRRTimer ( ) ;
} ) ;
2021-03-03 12:29:55 +01:00
return ; // Bail out here
}
// Stop iterating at first visible
if ( event . getId ( ) == eventIdFirst ) {
break ;
}
2021-01-14 16:17:05 +01:00
}
}
}
2021-03-03 12:29:55 +01:00
this . restartRRTimer ( ) ;
2021-01-20 11:32:21 +01:00
} ,
showDayMarkerBeforeEvent ( event ) {
const idx = this . events . indexOf ( event ) ;
if ( idx <= 0 ) {
return true ;
}
const previousEvent = this . events [ idx - 1 ] ;
return util . dayDiff ( previousEvent . getTs ( ) , event . getTs ( ) ) > 0 ;
} ,
dayForEvent ( event ) {
return util . formatDay ( event . getTs ( ) ) ;
2021-02-17 11:59:07 +01:00
} ,
2021-02-22 16:34:19 +01:00
2021-03-05 22:34:00 +01:00
showRecordingUI ( ) {
this . showRecorderPTT = false ;
this . showRecorder = true ;
} ,
2021-02-22 16:34:19 +01:00
startRecording ( ) {
2021-03-05 22:34:00 +01:00
this . showRecorderPTT = true ;
2021-02-22 16:34:19 +01:00
this . showRecorder = true ;
} ,
onVoiceRecording ( event ) {
2021-03-10 17:24:48 +01:00
this . currentSendShowSendButton = false ;
this . currentImageInputPath = event . file ;
var text = undefined ;
if ( this . currentInput && this . currentInput . length > 0 ) {
text = this . currentInput ;
this . currentInput = "" ;
}
this . sendAttachment ( text ) ;
this . showRecorder = false ;
2021-04-15 17:06:11 +02:00
// Log event to Clean Insights
this . $ci . event ( "Audio" , "Voice message sent" ) ;
2021-03-04 12:48:32 +01:00
} ,
2021-04-01 22:59:19 +02:00
closeCreateRoomWelcomeHeader ( ) {
this . showCreatedRoomWelcomeHeader = false ;
this . $nextTick ( ( ) => {
// We change the layout when removing the welcome header, so call
// onScroll here to handle updates (e.g. remove the "scroll to last" if we now
// can see all messages).
this . onScroll ( ) ;
} ) ;
2021-04-09 14:03:40 +02:00
} ,
updateRecentEmojis ( ) {
if ( this . $refs . emojiPicker ) {
this . recentEmojis = this . $refs . emojiPicker . mapEmojis [ "Frequently" ] ;
return ;
}
this . recentEmojis = [ ] ;
2021-04-01 22:59:19 +02:00
}
2021-04-09 14:03:40 +02:00
2020-11-09 10:26:56 +01:00
} ,
} ;
< / script >
< style lang = "scss" >
@ import "@/assets/css/chat.scss" ;
< / style >