2020-11-09 10:26:56 +01:00
< template >
2021-11-24 00:27:09 +02:00
< div class = "chat-root fill-height d-flex flex-column" >
2022-04-13 09:02:51 +00:00
< div class = "chat-room-invitations clickable" v-if = "invitationCount > 0" @click.stop="onInvitationsClick" >
2022-05-17 15:16:53 +00:00
{ { $tc ( "room.invitations" , invitationCount ) } }
2022-04-13 09:02:51 +00:00
< / div >
2022-05-17 15:16:53 +00:00
< ChatHeader class = "chat-header flex-grow-0 flex-shrink-0" v -on :header-click = "onHeaderClick" / >
2020-11-09 10:26:56 +01:00
< div
class = "chat-content flex-grow-1 flex-shrink-1"
ref = "chatContainer"
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-11 21:03:54 +02:00
v - on : close = "
showContextMenu = false ;
showContextMenuAnchor = null ;
"
2021-07-19 10:33:25 +02:00
v - if = "showMessageOperations"
2021-02-17 11:59:07 +01:00
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"
2021-05-11 21:03:54 +02:00
v - on : close = "
showAvatarMenu = false ;
showAvatarMenuAnchor = null ;
"
2021-05-10 16:11:03 +02:00
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 -- >
2022-05-17 15:16:53 +00:00
< resize-observer ref = "chatContainerResizer" @notify ="handleChatContainerResize" / >
< CreatedRoomWelcomeHeader v-if = "showCreatedRoomWelcomeHeader" v-on:close="closeCreateRoomWelcomeHeader" / >
< div v-for = "(event, index) in events" :key="event.getId()" :eventId="event.getId()" >
2021-02-17 11:59:07 +01:00
<!-- DAY Marker , shown for every new day in the timeline -- >
2022-05-17 15:16:53 +00:00
< div v-if = "showDayMarkerBeforeEvent(event)" class="day-marker" :title="dayForEvent(event)" / >
2021-02-17 11:59:07 +01:00
2022-05-17 15:16:53 +00:00
< div v-if = "!event.isRelation() && !event.isRedacted() && !event.isRedaction()" :ref="event.getId()" >
2020-12-04 10:44:46 +01:00
< div
2022-02-23 11:52:09 +00:00
class = "message-wrapper"
2020-12-04 10:44:46 +01:00
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]"
2021-09-14 11:57:49 +02:00
: timelineSet = "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"
2021-05-20 12:33:59 +02:00
: title = "$t('message.unread_messages')"
2021-02-17 11:59:07 +01:00
/ >
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 -- >
2022-05-17 15:16:53 +00:00
< v-container v-if = "room" fluid :class="['input-area-outer', replyToEvent ? 'reply-to' : '']" >
< div : class = "[replyToEvent ? 'iput-area-inner-box' : '']" >
2022-02-11 09:58:36 +00:00
<!-- "Scroll to end" - button -- >
< v-btn
class = "scroll-to-end"
v - show = "showScrollToEnd"
fab
2022-05-16 14:11:55 +00:00
x - small
2022-02-11 09:58:36 +00:00
elevation = "0"
color = "black"
@ click . stop = "scrollToEndOfTimeline"
>
< v-icon color = "white" > arrow _downward < / v-icon >
< / v-btn >
< v-row class = "ma-0 pa-0" >
< div v-if = "replyToEvent" class="row" >
< div class = "col" >
< div class = "font-weight-medium" > { { $t ( "message.replying_to" , { user : replyToEvent . sender . name } ) } } < / div >
2022-05-17 15:16:53 +00:00
< div v-if = "replyToContentType === 'm.text'" class="reply-text" :title="replyToEvent.getContent().body" >
{ { replyToEvent . getContent ( ) . body | latestReply } }
< / div >
< div v-if = "replyToContentType === 'm.image'" > {{ $ t ( " message.reply_image " ) }} < / div >
< div v-if = "replyToContentType === 'm.audio'" > {{ $ t ( " message.reply_audio_message " ) }} < / div >
< div v-if = "replyToContentType === 'm.video'" > {{ $ t ( " message.reply_video " ) }} < / div >
2022-02-11 09:58:36 +00:00
< / div >
< div class = "col col-auto" v-if = "replyToContentType !== 'm.text'" >
2022-05-17 15:16:53 +00:00
< img v-if = "replyToContentType === 'm.image'" width="150px" :src="replyToImg" class="rounded" / >
< v-img v-if = "replyToContentType === 'm.audio'" src="@/assets/icons/audio_message.svg" / >
< v-img v-if = "replyToContentType === 'm.video'" src="@/assets/icons/video_message.svg" / >
2022-02-11 09:58:36 +00:00
< / div >
2022-03-06 14:28:16 +02:00
< div class = "col col-auto" >
2022-05-17 15:16:53 +00:00
< v-btn fab x -small elevation = "0" color = "black" @click.stop ="cancelEditReply" >
2022-03-06 14:28:16 +02:00
< v-icon color = "white" > cancel < / v-icon >
< / v-btn >
< / div >
2022-02-11 09:58:36 +00:00
< / div >
2020-12-15 17:06:26 +01:00
2022-02-11 09:58:36 +00:00
<!-- CONTACT IS TYPING -- >
< div class = "typing" >
{ { typingMembersString } }
< / div >
< / v-row >
< v-row class = "input-area-inner align-center" >
< 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"
: placeholder = "$t('message.your_message')"
hide - details
background - color = "white"
v - on : keydown . enter . prevent = "
( ) => {
sendCurrentTextMessage ( ) ;
}
"
/ >
< / v-col >
2020-12-04 12:15:47 +01:00
2022-05-17 15:16:53 +00:00
< v-col class = "input-area-button text-center flex-grow-0 flex-shrink-1" v-if = "editedEvent" >
< v-btn fab small elevation = "0" color = "black" @click.stop ="cancelEditReply" >
2022-02-11 09:58:36 +00:00
< v-icon color = "white" > cancel < / v-icon >
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2022-02-11 09:58:36 +00:00
< v-col
class = "input-area-button text-center flex-grow-0 flex-shrink-1"
v - if = "!currentInput || currentInput.length == 0 || showRecorder"
2021-05-25 12:04:51 +02:00
>
2022-02-11 09:58:36 +00:00
< v-btn
v - if = "canRecordAudio"
class = "mic-button"
ref = "mic_button"
fab
small
elevation = "0"
v - blur
v - longTap : 250 = "[showRecordingUI, startRecording]"
>
< v-icon : color = "showRecorder ? 'white' : 'black'" > mic < / v-icon >
< / v-btn >
< v-btn
v - else
class = "mic-button"
ref = "mic_button"
fab
small
elevation = "0"
v - blur
@ click . stop = "showNoRecordingAvailableDialog = true"
>
< v-icon : color = "showRecorder ? 'white' : 'black'" > mic < / v-icon >
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2022-05-17 15:16:53 +00:00
< v-col class = "input-area-button text-center flex-grow-0 flex-shrink-1" v-else >
2022-02-11 09:58:36 +00:00
< v-btn
fab
small
elevation = "0"
color = "black"
@ click . stop = "sendCurrentTextMessage"
: disabled = "sendButtonDisabled"
>
2022-05-17 15:16:53 +00:00
< v-icon color = "white" > { { editedEvent ? "save" : "arrow_upward" } } < / v-icon >
2022-02-11 09:58:36 +00:00
< / v-btn >
< / v-col >
2021-02-23 22:07:57 +01:00
2022-05-17 15:16:53 +00:00
< v-col v-if = "$config.shortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1" >
2021-02-23 22:07:57 +01:00
< v-btn
2021-03-10 17:24:48 +01:00
v - if = "!showRecorder"
2022-01-27 14:56:48 +00:00
id = "btn-attach"
2021-02-23 22:07:57 +01:00
icon
large
color = "black"
2022-02-11 09:58:36 +00:00
@ click = "showStickerPicker"
2021-02-23 22:07:57 +01:00
: disabled = "attachButtonDisabled"
>
2022-02-11 09:58:36 +00:00
< v-icon large > face < / v-icon >
2021-02-23 22:07:57 +01:00
< / v-btn >
2022-02-11 09:58:36 +00:00
< / v-col >
< v-col class = "input-area-button text-center flex-grow-0 flex-shrink-1" >
< label icon flat ref = "attachmentLabel" >
< v-btn
v - if = "!showRecorder"
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"
2022-02-23 11:52:09 +00:00
class = "d-none"
2022-02-11 09:58:36 +00:00
/ >
< / label >
< / v-col >
2022-05-03 09:40:02 +00:00
< v-col
v - if = "!showRecorder && canCreatePoll"
ref = "sendOptions"
2022-05-17 15:16:53 +00:00
class = "input-area-button text-center flex-grow-0 flex-shrink-1 send-options"
2022-05-03 09:40:02 +00:00
>
< v-menu close -on -click >
< template v -slot : activator = "{ on, attrs }" >
< v-btn icon v-bind = "attrs" v-on="on" >
< v-icon > more _vert < / v-icon >
< / v-btn >
< / template >
< v-list >
2022-05-17 15:16:53 +00:00
< v-list-item @ click = "showCreatePollDialog = true" >
< v-list-item-icon >
< svg width = "17" height = "19" viewBox = "0 0 17 19" fill = "none" xmlns = "http://www.w3.org/2000/svg" >
< path
d = "M3.31462 16.4718C3.31462 16.9496 3.70026 17.3368 4.17609 17.3368L16.1385 17.3368C16.6144 17.3368 17 16.9496 17 16.4718L16.9998 13.6229C16.9998 13.1452 16.6142 12.7579 16.1383 12.7579L4.1764 12.7579C3.70056 12.7579 3.31492 13.1452 3.31492 13.6229L3.31512 16.4718L3.31462 16.4718Z"
fill = "currentColor"
/ >
< path
d = "M3.31462 10.4557C3.31462 10.9335 3.70026 11.3208 4.17609 11.3208L11.3428 11.3208C11.8186 11.3208 12.2043 10.9335 12.2043 10.4557L12.2043 7.60711C12.2043 7.12931 11.8186 6.74208 11.3428 6.74208L4.17609 6.74208C3.70026 6.74208 3.31462 7.12932 3.31462 7.60711L3.31462 10.4557Z"
fill = "currentColor"
/ >
< path
d = "M3.31451 1.59127L3.31451 4.44011C3.31451 4.91791 3.70016 5.30514 4.17598 5.30514L6.99509 5.30514C7.47093 5.30514 7.85657 4.91791 7.85657 4.44011L7.85637 1.59127C7.85637 1.11347 7.47073 0.726242 6.9949 0.726242L4.17599 0.726242C3.70035 0.726242 3.31452 1.11348 3.31452 1.59127L3.31451 1.59127Z"
fill = "currentColor"
/ >
< path
d = "M-2.00529e-05 0.587841L-2.0791e-05 17.4747C-2.08052e-05 17.7995 0.262306 18.0625 0.585404 18.0625L1.38198 18.0625C1.70528 18.0625 1.96741 17.7995 1.96741 17.4747L1.96741 0.587841C1.96741 0.263208 1.70508 -1.14667e-08 1.38198 -2.55897e-08L0.585405 -6.04092e-08C0.261911 -7.45496e-08 -2.00387e-05 0.263213 -2.00529e-05 0.587841Z"
fill = "currentColor"
/ >
< / svg >
< / v-list-item-icon >
< v-list-item-title > { { $t ( "poll_create.create_poll_menu_option" ) } } < / v-list-item-title > < / v - l i s t - i t e m
>
2022-05-03 09:40:02 +00:00
< / v-list >
< / v-menu >
< / v-col >
2022-02-11 09:58:36 +00:00
< / v-row >
< VoiceRecorder
: micButtonRef = "$refs.mic_button"
: ptt = "showRecorderPTT"
: show = "showRecorder"
v - on : close = "showRecorder = false"
v - on : file = "onVoiceRecording"
/ >
< / div >
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" >
2022-05-17 15:16:53 +00:00
< v-dialog v-model = "currentImageInputPath" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'" >
2020-11-17 20:02:42 +01:00
< v-card class = "ma-0 pa-0" >
2021-05-11 21:03:54 +02:00
< v-card-text class = "ma-0 pa-2" >
2020-11-17 20:02:42 +01:00
< v-img
2021-05-11 21:03:54 +02:00
v - if = "currentImageInput && currentImageInput.image"
2020-11-17 20:02:42 +01:00
: aspect - ratio = "1"
2021-05-11 21:03:54 +02:00
: src = "currentImageInput.image"
2020-11-17 20:02:42 +01:00
contain
2022-02-23 11:52:09 +00:00
class = "current-image-input-path"
2020-11-17 20:02:42 +01:00
/ >
2021-05-11 21:03:54 +02:00
< div >
file : { { currentImageInputPath . name } }
2022-05-17 15:16:53 +00:00
< span v-if = "currentImageInput && currentImageInput.scaled && currentImageInput.useScaled" >
{ { currentImageInput . scaledDimensions . width } } x { { currentImageInput . scaledDimensions . height } } < / s p a n
2021-05-11 21:03:54 +02:00
>
2022-05-17 15:16:53 +00:00
< span v -else -if = " currentImageInput & & currentImageInput.dimensions " >
{ { currentImageInput . dimensions . width } } x { { currentImageInput . dimensions . height } } < / s p a n
2021-05-11 21:03:54 +02:00
>
2022-05-17 15:16:53 +00:00
< span v-if = "currentImageInput && currentImageInput.scaled && currentImageInput.useScaled" >
2021-05-11 21:03:54 +02:00
( { { formatBytes ( currentImageInput . scaledSize ) } } ) < / s p a n
>
2022-05-17 15:16:53 +00:00
< span v-else > ( {{ formatBytes ( currentImageInputPath.size ) }} ) < / span >
2021-05-11 21:03:54 +02:00
< v-switch
v - if = "currentImageInput && currentImageInput.scaled"
2021-05-25 11:14:29 +02:00
: label = "$t('message.scale_image')"
2021-05-11 21:03:54 +02:00
v - model = "currentImageInput.useScaled"
/ >
< / div >
2020-11-17 20:02:42 +01:00
< 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 >
2022-01-27 14:56:48 +00:00
< v-btn color = "primary" text @click ="cancelSendAttachment" id = "btn-attachment-cancel" > { {
2021-05-25 12:04:51 +02:00
$t ( "menu.cancel" )
} } < / v-btn >
2020-11-25 14:42:50 +01:00
< v-btn
2022-01-27 14:56:48 +00:00
id = "btn-attachment-send"
2020-11-25 14:42:50 +01:00
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"
2021-05-25 12:04:51 +02:00
> { { $t ( "menu.send" ) } } < / v - b t n
2020-11-25 14:42:50 +01:00
>
2020-11-17 20:02:42 +01:00
< / v-card-actions >
< / v-card >
< / v-dialog >
< / div >
2020-11-25 14:42:50 +01:00
2022-05-17 15:16:53 +00:00
< MessageOperationsBottomSheet ref = "messageOperationsSheet" >
< VEmojiPicker ref = "emojiPicker" @select ="emojiSelected" / >
2021-04-09 14:27:27 +02:00
< / MessageOperationsBottomSheet >
2020-12-10 12:37:06 +01:00
2022-05-17 15:16:53 +00:00
< StickerPickerBottomSheet ref = "stickerPickerSheet" v -on :selectSticker = "sendSticker" / >
2021-04-15 11:44:58 +02:00
2021-02-17 17:12:16 +01:00
<!-- Loading indicator -- >
2022-05-17 15:16:53 +00:00
< v-container fluid class = "loading-indicator" fill -height v-if = "!initialLoadDone || loading" >
2021-02-17 17:12:16 +01:00
< v-row align = "center" justify = "center" >
< v-col class = "text-center" >
2022-05-17 15:16:53 +00:00
< v-progress-circular indeterminate color = "primary" > < / v-progress-circular >
2021-02-17 17:12:16 +01:00
< / v-col >
< / v-row >
< / v-container >
2021-03-11 13:55:10 +01:00
< RoomInfoBottomSheet ref = "roomInfoSheet" / >
2021-05-25 12:04:51 +02:00
<!-- Dialog for audio recording not supported ! -- >
2022-05-17 15:16:53 +00:00
< v-dialog v-model = "showNoRecordingAvailableDialog" class="ma-0 pa-0" width="80%" >
2021-05-25 12:04:51 +02:00
< v-card >
2022-05-17 15:16:53 +00:00
< v-card-title > { { $t ( "voice_recorder.not_supported_title" ) } } < / v-card-title >
< v-card-text > { { $t ( "voice_recorder.not_supported_text" ) } } < / v-card-text >
2021-05-25 12:04:51 +02:00
< v-divider > < / v-divider >
< v-card-actions >
< v-spacer > < / v-spacer >
2022-05-17 15:16:53 +00:00
< v-btn id = "btn-ok" color = "primary" text @ click = "showNoRecordingAvailableDialog = false" > { {
$t ( "menu.ok" )
} } < / v-btn >
2021-05-25 12:04:51 +02:00
< / v-card-actions >
< / v-card >
< / v-dialog >
2022-05-03 09:40:02 +00:00
2022-05-17 15:16:53 +00:00
< CreatePollDialog :show = "showCreatePollDialog" @ close = "showCreatePollDialog = false" / >
2020-11-09 10:26:56 +01:00
< / div >
< / template >
< script >
2021-05-11 21:03:54 +02:00
import Vue from "vue" ;
2020-11-11 17:35:14 +01:00
import { TimelineWindow , EventTimeline } from "matrix-js-sdk" ;
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-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-05-11 21:03:54 +02:00
import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet" ;
import StickerPickerBottomSheet from "./StickerPickerBottomSheet" ;
import BottomSheet from "./BottomSheet.vue" ;
import ImageResize from "image-resize" ;
2022-05-03 09:40:02 +00:00
import CreatePollDialog from "./CreatePollDialog.vue" ;
2022-05-23 15:19:55 +00:00
import chatMixin from "./chatMixin" ;
2022-05-03 09:40:02 +00:00
2021-05-11 21:03:54 +02:00
const sizeOf = require ( "image-size" ) ;
const dataUriToBuffer = require ( "data-uri-to-buffer" ) ;
const prettyBytes = require ( "pretty-bytes" ) ;
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-05-11 21:03:54 +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" ;
}
2022-05-17 15:16:53 +00:00
ScrollPosition . prototype . restore = function ( ) {
2020-11-11 17:35:14 +01:00
if ( this . readyFor === "up" ) {
2022-05-17 15:16:53 +00:00
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
}
} ;
2022-05-17 15:16:53 +00:00
ScrollPosition . prototype . prepareFor = function ( direction ) {
2020-11-11 17:35:14 +01:00
this . readyFor = direction || "up" ;
2020-11-17 20:02:42 +01:00
if ( this . readyFor === "up" ) {
2022-05-17 15:16:53 +00:00
this . previousScrollHeightMinusTop = this . node . scrollHeight - this . node . scrollTop ;
2020-11-17 20:02:42 +01:00
} 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" ,
2022-05-23 15:19:55 +00:00
mixins : [ chatMixin ] ,
2020-11-17 20:32:37 +01:00
components : {
2020-12-04 17:15:18 +01:00
ChatHeader ,
2020-11-25 14:42:50 +01:00
MessageOperations ,
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 ,
2022-05-17 15:16:53 +00:00
CreatePollDialog ,
2020-11-17 20:32:37 +01:00
} ,
2020-11-19 22:48:08 +01:00
data ( ) {
2021-05-11 21:03:54 +02:00
return {
2020-11-19 22:48:08 +01:00
events : [ ] ,
currentInput : "" ,
2020-12-09 21:50:53 +01:00
typingMembers : [ ] ,
2021-09-14 11:57:49 +02:00
timelineSet : null ,
2020-11-19 22:48:08 +01:00
timelineWindow : null ,
2021-05-11 21:03:54 +02:00
/** true if we are currently paginating */
2021-04-09 16:40:03 +02:00
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 ,
2022-02-11 09:58:36 +00:00
replyToImg : null ,
replyToContentType : null ,
2022-05-03 09:40:02 +00:00
showCreatePollDialog : false ,
2021-05-25 12:04:51 +02:00
showNoRecordingAvailableDialog : false ,
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 ,
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. */
2021-05-11 21:03:54 +02:00
recentEmojis : [ ] ,
2021-07-19 10:33:25 +02:00
/** Calculated style for message operations. We position the "popup" at the selected message. */
opStyle : "" ,
2020-11-19 22:48:08 +01:00
} ;
} ,
2020-11-09 10:26:56 +01:00
2022-04-10 10:32:25 +03:00
filters : {
latestReply ( contents ) {
2022-05-17 15:16:53 +00:00
const contentArr = contents . split ( "\n" ) . reverse ( ) ;
if ( contentArr [ 0 ] === "" ) {
2022-04-10 10:32:25 +03:00
contentArr . shift ( ) ;
}
2022-05-17 15:16:53 +00:00
return contentArr [ 0 ] . replace ( /^> (<.*> )?/g , "" ) ;
} ,
2022-04-10 10:32:25 +03: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 ( ) ;
}
2022-05-17 15:16:53 +00: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 ( ) {
2022-05-17 15:16:53 +00: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 ) {
2021-05-25 12:04:51 +02:00
return this . $t ( "message.users_are_typing" , { count : count } ) ;
2020-12-09 21:50:53 +01:00
} else if ( count > 0 ) {
2021-05-25 12:04:51 +02:00
return this . $t ( "message.user_is_typing" , {
user : this . typingMembers [ 0 ] . name ,
} ) ;
2020-12-09 21:50:53 +01:00
} else {
return "" ;
}
2020-12-14 16:11:45 +01:00
} ,
2021-07-19 10:33:25 +02:00
showMessageOperations ( ) {
return this . selectedEvent && this . showContextMenu ;
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 ( ) ;
2022-05-17 15:16:53 +00:00
var rectChat = this . $refs . avatarOperationsStrut . getBoundingClientRect ( ) ;
2021-05-10 16:11:03 +02:00
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" ) ;
2021-05-11 21:03:54 +02:00
} ,
2022-04-13 09:02:51 +00:00
invitationCount ( ) {
return this . $matrix . invites . length ;
2022-05-03 09:40:02 +00:00
} ,
canCreatePoll ( ) {
// We say that if you can redact events, you are allowed to create polls.
const me = this . room && this . room . getMember ( this . $matrix . currentUserId ) ;
2022-05-17 15:16:53 +00:00
let isAdmin =
me && this . room . currentState && this . room . currentState . hasSufficientPowerLevelFor ( "redact" , me . powerLevel ) ;
2022-05-03 09:40:02 +00:00
return isAdmin ;
2022-05-17 15:16:53 +00:00
} ,
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.
}
2022-05-17 15:16:53 +00: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 ;
2022-04-21 09:41:52 +00:00
this . showCreatedRoomWelcomeHeader = false ;
2021-01-14 16:17:05 +01:00
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
} ,
2021-07-19 10:33:25 +02:00
showMessageOperations ( ) {
if ( this . showMessageOperations ) {
this . $nextTick ( ( ) => {
// Calculate where to show the context menu.
//
2022-05-17 15:16:53 +00:00
const ref = this . selectedEvent && this . $refs [ this . selectedEvent . getId ( ) ] ;
2021-07-19 10:33:25 +02:00
var top = 0 ;
var left = 0 ;
if ( ref && ref [ 0 ] ) {
if ( this . showContextMenuAnchor ) {
2022-05-17 15:16:53 +00:00
var rectAnchor = this . showContextMenuAnchor . getBoundingClientRect ( ) ;
var rectChat = this . $refs . messageOperationsStrut . getBoundingClientRect ( ) ;
var rectOps = this . $refs . messageOperations . $el . getBoundingClientRect ( ) ;
2022-04-25 08:43:27 +00:00
top = rectAnchor . top - rectChat . top - 50 ;
2022-05-16 14:11:55 +00:00
left = rectAnchor . left - rectChat . left - 50 ;
2021-07-19 10:33:25 +02:00
if ( left + rectOps . width >= rectChat . right ) {
left = rectChat . right - rectOps . width - 10 ; // No overflow
}
}
}
this . opStyle = "top:" + top + "px;left:" + left + "px" ;
} ) ;
}
} ,
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!
2022-05-17 15:16:53 +00:00
const createEvent = this . room . currentState . getStateEvents ( "m.room.create" , "" ) ;
2021-04-01 22:59:19 +02:00
if ( createEvent ) {
const creatorId = createEvent . getContent ( ) . creator ;
2022-05-17 15:16:53 +00: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-09-14 11:57:49 +02:00
this . timelineSet = this . room . getUnfilteredTimelineSet ( ) ;
2022-05-17 15:16:53 +00:00
this . timelineWindow = new TimelineWindow ( this . $matrix . matrixClient , this . timelineSet , { } ) ;
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 ( ( ) => {
self . events = self . timelineWindow . getEvents ( ) ;
const getMoreIfNeeded = function _getMoreIfNeeded ( ) {
const container = self . $refs . chatContainer ;
if (
2022-05-17 15:16:53 +00: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 )
) {
2022-05-17 15:16:53 +00:00
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" ) ;
}
} ) ;
2021-03-04 12:48:32 +01:00
} 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
}
2022-03-23 14:11:50 +01:00
} )
. finally ( ( ) => {
for ( var event of this . events ) {
this . $matrix . matrixClient . decryptEventIfNeeded ( event , { } ) ;
}
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 ( ) {
2022-05-17 15:16:53 +00:00
if ( this . timelineWindow && this . timelineWindow . canPaginate ( EventTimeline . FORWARDS ) ) {
2021-04-09 16:20:57 +02:00
this . loading = true ;
2021-05-11 21:03:54 +02:00
// Instead of paging though ALL history, just reload a timeline at the live marker...
2021-09-14 11:57:49 +02:00
var timelineSet = this . room . getUnfilteredTimelineSet ( ) ;
2022-05-17 15:16:53 +00:00
var timelineWindow = new TimelineWindow ( this . $matrix . matrixClient , timelineSet , { } ) ;
2021-05-11 21:03:54 +02:00
const self = this ;
timelineWindow
. load ( null , 20 )
. then ( ( ) => {
2021-09-14 11:57:49 +02:00
self . timelineSet = timelineSet ;
2021-05-11 21:03:54 +02:00
self . timelineWindow = timelineWindow ;
self . events = self . timelineWindow . getEvents ( ) ;
} )
. finally ( ( ) => {
this . loading = false ;
} ) ;
2021-04-09 16:20:57 +02:00
} 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-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 ( ) ;
2022-05-17 15:16:53 +00:00
} else if ( container . scrollHeight - container . scrollTop . toFixed ( 0 ) - container . clientHeight <= bufferHeight ) {
2020-11-17 20:02:42 +01:00
this . handleScrolledToBottom ( false ) ;
2020-11-11 17:35:14 +01:00
}
2022-05-23 14:24:43 +00:00
this . showScrollToEnd = container . scrollHeight === container . clientHeight ? false : container . scrollHeight - container . scrollTop . toFixed ( 0 ) > 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 ) {
2021-05-20 12:33:59 +02: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
2022-03-23 14:11:50 +01:00
this . $matrix . matrixClient . decryptEventIfNeeded ( event , { } ) ;
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
2022-05-17 15:16:53 +00:00
if ( container . scrollHeight - container . scrollTop . toFixed ( 0 ) == 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
}
}
2021-05-20 12:33:59 +02: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
2022-05-17 15:16:53 +00:00
. sendTextMessage ( this . $matrix . matrixClient , this . roomId , text , 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 ( ) {
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-05-11 21:03:54 +02:00
const file = event . target . files [ 0 ] ;
2021-03-10 17:24:48 +01:00
this . currentSendShowSendButton = true ;
2021-05-11 21:03:54 +02:00
if ( file . type . startsWith ( "image/" ) ) {
this . currentImageInput = {
image : e . target . result ,
dimensions : null ,
} ;
try {
2022-05-17 15:16:53 +00:00
this . currentImageInput . dimensions = sizeOf ( dataUriToBuffer ( e . target . result ) ) ;
2021-05-11 21:03:54 +02:00
// Need to resize?
const w = this . currentImageInput . dimensions . width ;
const h = this . currentImageInput . dimensions . height ;
if ( w > 640 || h > 640 ) {
var aspect = w / h ;
var newWidth = parseInt ( ( w > h ? 640 : 640 * aspect ) . toFixed ( ) ) ;
2022-05-17 15:16:53 +00:00
var newHeight = parseInt ( ( w > h ? 640 / aspect : 640 ) . toFixed ( ) ) ;
2021-05-11 21:03:54 +02:00
var imageResize = new ImageResize ( {
format : "png" ,
width : newWidth ,
height : newHeight ,
outputType : "blob" ,
} ) ;
imageResize
. play ( event . target )
. then ( ( img ) => {
Vue . set (
this . currentImageInput ,
"scaled" ,
new File ( [ img ] , file . name , {
type : img . type ,
lastModified : Date . now ( ) ,
} )
) ;
Vue . set ( this . currentImageInput , "useScaled" , true ) ;
Vue . set ( this . currentImageInput , "scaledSize" , img . size ) ;
Vue . set ( this . currentImageInput , "scaledDimensions" , {
width : newWidth ,
height : newHeight ,
} ) ;
} )
. catch ( ( err ) => {
console . error ( "Resize failed:" , err ) ;
} ) ;
}
} catch ( error ) {
console . error ( "Failed to get image dimensions: " + error ) ;
}
}
console . log ( this . currentImageInput ) ;
this . currentImageInputPath = file ;
2020-11-17 20:02:42 +01:00
} ;
reader . readAsDataURL ( event . target . files [ 0 ] ) ;
}
} ,
2021-04-15 11:44:58 +02:00
showStickerPicker ( ) {
2021-05-11 21:03:54 +02:00
this . $refs . stickerPickerSheet . open ( ) ;
2021-04-15 11:44:58 +02:00
} ,
2020-11-17 20:02:42 +01:00
onUploadProgress ( p ) {
if ( p . total ) {
2022-05-17 15:16:53 +00:00
this . currentSendProgress = this . $t ( "message.upload_progress_with_total" , {
count : p . loaded || 0 ,
total : p . total ,
} ) ;
2020-11-17 20:02:42 +01:00
} else {
2021-05-25 12:04:51 +02:00
this . currentSendProgress = this . $t ( "message.upload_progress" , {
count : p . loaded || 0 ,
} ) ;
2020-11-17 20:02:42 +01:00
}
} ,
2021-03-10 17:24:48 +01:00
sendAttachment ( withText ) {
2021-05-11 21:03:54 +02:00
this . $refs . attachment . value = null ;
2020-11-17 20:02:42 +01:00
if ( this . currentImageInputPath ) {
2021-05-11 21:03:54 +02:00
var inputFile = this . currentImageInputPath ;
2022-05-17 15:16:53 +00:00
if ( this . currentImageInput && this . currentImageInput . scaled && this . currentImageInput . useScaled ) {
2021-05-11 21:03:54 +02:00
// Send scaled version of image instead!
inputFile = this . currentImageInput . scaled ;
}
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 ,
2021-05-11 21:03:54 +02:00
inputFile ,
2020-11-25 14:42:50 +01:00
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 ( ) {
2021-05-11 21:03:54 +02:00
this . $refs . attachment . value = null ;
2020-11-21 14:57:43 +01:00
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 ( ) {
2022-05-17 15:16:53 +00:00
this . $nextTick ( function ( ) {
2020-11-17 20:02:42 +01:00
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 ) ;
} ,
2021-05-11 21:03:54 +02:00
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 } ) ;
} ,
2022-02-11 09:58:36 +00:00
setReplyToImage ( event ) {
util
2022-05-17 15:16:53 +00:00
. getThumbnail ( this . $matrix . matrixClient , event )
. then ( ( url ) => {
this . replyToImg = url ;
} )
. catch ( ( err ) => {
console . log ( "Failed to fetch thumbnail: " , err ) ;
} ) ;
2022-02-11 09:58:36 +00:00
} ,
2020-12-15 17:06:26 +01:00
addReply ( event ) {
this . replyToEvent = event ;
this . $refs . messageInput . focus ( ) ;
2022-05-17 15:16:53 +00:00
this . replyToContentType = event . getContent ( ) . msgtype ;
2022-02-11 09:58:36 +00:00
this . setReplyToImage ( event ) ;
2020-12-15 17:06:26 +01:00
} ,
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" ;
2022-02-13 11:31:41 +02:00
link . download = event . getContent ( ) . body || this . $t ( "fallbacks.download_name" ) ;
document . body . appendChild ( link ) ;
2021-01-12 09:25:39 +01:00
link . click ( ) ;
2022-02-13 11:31:41 +02:00
2022-05-17 15:16:53 +00:00
setTimeout ( function ( ) {
2022-02-13 11:31:41 +02:00
document . body . removeChild ( link ) ;
URL . revokeObjectURL ( url ) ;
2022-05-17 15:16:53 +00:00
} , 200 ) ;
2021-01-12 09:25:39 +01:00
} )
. 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 ;
2022-03-21 07:50:31 +00:00
this . $refs . messageOperationsSheet . close ( ) ;
2020-11-25 14:42:50 +01:00
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
2022-05-17 15:16:53 +00:00
. sendQuickReaction ( this . $matrix . matrixClient , this . roomId , e . reaction , e . event )
2020-12-04 10:44:46 +01:00
. 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
this . selectedEvent = event ;
2021-04-09 14:03:40 +02:00
this . updateRecentEmojis ( ) ;
2022-04-25 08:43:27 +00:00
this . showContextMenu = ! this . showContextMenu ;
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 ) {
2022-04-13 09:02:51 +00:00
this . loading = true ;
2021-05-11 21:03:54 +02:00
this . $matrix
. getOrCreatePrivateChat ( e . event . getSender ( ) )
. then ( ( room ) => {
2021-05-10 16:11:03 +02:00
this . $nextTick ( ( ) => {
this . $navigation . push (
{
name : "Chat" ,
2021-05-11 21:03:54 +02:00
params : {
2022-05-17 15:16:53 +00:00
roomId : util . sanitizeRoomId ( room . getCanonicalAlias ( ) || room . roomId ) ,
2021-05-11 21:03:54 +02:00
} ,
2021-05-10 16:11:03 +02:00
} ,
- 1
) ;
} ) ;
} )
2021-05-11 21:03:54 +02:00
. catch ( ( err ) => {
2021-05-10 16:11:03 +02:00
console . error ( err ) ;
2022-04-13 09:02:51 +00:00
} )
. finally ( ( ) => {
this . loading = false ;
2021-05-11 21:03:54 +02:00
} ) ;
2021-05-10 16:11:03 +02:00
} ,
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 ( ( ) => {
2022-05-17 15:16:53 +00:00
this . $matrix . matrixClient . setRoomReadMarkers ( this . room . roomId , event . getId ( ) ) ;
2021-03-04 12:48:32 +01:00
} )
. 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
} ,
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" ] ;
2021-06-29 14:25:57 +02:00
if ( this . recentEmojis . length < 20 ) {
let peoples = this . $refs . emojiPicker . mapEmojis [ "Peoples" ] ;
for ( var p of peoples ) {
this . recentEmojis . push ( p ) ;
}
2021-06-27 13:42:45 +05:30
}
2021-04-09 14:03:40 +02:00
return ;
}
this . recentEmojis = [ ] ;
2021-05-11 21:03:54 +02:00
} ,
2021-04-09 14:03:40 +02:00
2021-05-11 21:03:54 +02:00
formatBytes ( bytes ) {
return prettyBytes ( bytes ) ;
} ,
2021-07-18 12:17:15 +02:00
onHeaderClick ( ) {
const joinedRooms = this . $matrix . joinedRooms ;
2022-05-17 15:16:53 +00:00
if ( joinedRooms && joinedRooms . length == 1 && joinedRooms [ 0 ] . roomId == this . room . roomId ) {
2021-07-18 12:17:15 +02:00
// Only joined to this room, go directly to room details!
this . $navigation . push ( { name : "RoomInfo" } ) ;
return ;
}
this . $refs . roomInfoSheet . open ( ) ;
} ,
2022-04-13 09:02:51 +00:00
onInvitationsClick ( ) {
2022-05-17 15:16:53 +00:00
this . $navigation . push ( { name : "Home" } , - 1 ) ;
2022-05-03 09:40:02 +00:00
} ,
2020-11-09 10:26:56 +01:00
} ,
} ;
< / script >
< style lang = "scss" >
@ import "@/assets/css/chat.scss" ;
2022-05-17 15:16:53 +00:00
< / style >