168
168
v-show =" Object.keys(room).length && showFooter"
169
169
>
170
170
<transition name =" vac-slide-up" >
171
- <div v-if =" messageReply" class =" vac-reply-container" >
171
+ <div
172
+ v-if =" messageReply"
173
+ class =" vac-reply-container"
174
+ :style =" { bottom: `${roomFooterHeight}px` }"
175
+ >
172
176
<div class =" vac-reply-box" >
173
177
<img
174
178
v-if =" isImageCheck(messageReply.file)"
191
195
</div >
192
196
</transition >
193
197
194
- <div class =" vac-box-footer" >
198
+ <transition name =" vac-slide-up" >
199
+ <div
200
+ v-if =" filteredUsersTag.length"
201
+ class =" vac-tags-container vac-app-box-shadow"
202
+ :style =" { bottom: `${roomFooterHeight}px` }"
203
+ >
204
+ <div
205
+ class =" vac-tags-box"
206
+ v-for =" user in filteredUsersTag"
207
+ :key =" user._id"
208
+ @click =" selectUserTag(user)"
209
+ >
210
+ <div class =" vac-tags-info" >
211
+ <div
212
+ v-if =" user.avatar"
213
+ class =" vac-room-avatar vac-tags-avatar"
214
+ :style =" { 'background-image': `url('${user.avatar}')` }"
215
+ ></div >
216
+ <div class =" vac-tags-username" >
217
+ {{ user.username }}
218
+ </div >
219
+ </div >
220
+ </div >
221
+ </div >
222
+ </transition >
223
+
224
+ <div
225
+ class =" vac-box-footer"
226
+ :class =" { 'vac-app-box-shadow': filteredUsersTag.length }"
227
+ >
195
228
<div class =" vac-icon-textarea-left" v-if =" showAudio && !imageFile" >
196
229
<div class =" vac-svg-button" @click =" recordAudio" >
197
230
<slot
255
288
}"
256
289
v-model =" message"
257
290
@input =" onChangeInput"
258
- @keydown.esc =" resetMessage "
291
+ @keydown.esc =" escapeTextarea "
259
292
@keydown.enter.exact.prevent =" "
260
293
></textarea >
261
294
@@ -339,6 +372,7 @@ import EmojiPicker from './EmojiPicker'
339
372
340
373
const { messagesValid } = require (' ../utils/roomValidation' )
341
374
const { detectMobile , iOSDevice } = require (' ../utils/mobileDetection' )
375
+ import filteredUsers from ' ../utils/filterItems'
342
376
import typingText from ' ../utils/typingText'
343
377
344
378
export default {
@@ -402,33 +436,40 @@ export default {
402
436
recorderStream: {},
403
437
recorder: {},
404
438
recordedChunks: [],
405
- keepKeyboardOpen: false
439
+ keepKeyboardOpen: false ,
440
+ filteredUsersTag: [],
441
+ textareaCursorPosition: null ,
442
+ roomFooterHeight: 0
406
443
}
407
444
},
408
445
409
446
mounted () {
410
447
this .newMessages = []
448
+ const isMobile = detectMobile ()
411
449
412
450
window .addEventListener (' keyup' , e => {
413
451
if (e .keyCode === 13 && ! e .shiftKey ) {
414
- if (detectMobile () ) {
452
+ if (isMobile ) {
415
453
this .message = this .message + ' \n '
416
454
setTimeout (() => this .onChangeInput (), 0 )
417
455
} else {
418
456
this .sendMessage ()
419
457
}
420
458
}
459
+
460
+ this .updateShowUsersTag ()
421
461
})
422
462
423
- if (detectMobile ()) {
424
- this .$refs [' roomTextarea' ].addEventListener (' blur' , () =>
425
- setTimeout (() => (this .keepKeyboardOpen = false ), 0 )
426
- )
427
- this .$refs [' roomTextarea' ].addEventListener (
428
- ' click' ,
429
- () => (this .keepKeyboardOpen = true )
430
- )
431
- }
463
+ this .$refs [' roomTextarea' ].addEventListener (' click' , () => {
464
+ if (isMobile) this .keepKeyboardOpen = true
465
+ this .updateShowUsersTag ()
466
+ })
467
+
468
+ this .$refs [' roomTextarea' ].addEventListener (' blur' , () => {
469
+ this .filteredUsersTag = []
470
+ this .textareaCursorPosition = null
471
+ if (isMobile) setTimeout (() => (this .keepKeyboardOpen = false ), 0 )
472
+ })
432
473
433
474
this .$refs .scrollContainer .addEventListener (' scroll' , e => {
434
475
this .hideOptions = true
@@ -545,6 +586,55 @@ export default {
545
586
},
546
587
547
588
methods: {
589
+ updateShowUsersTag () {
590
+ if (this .$refs [' roomTextarea' ]) {
591
+ if (
592
+ this .textareaCursorPosition ===
593
+ this .$refs [' roomTextarea' ].selectionStart
594
+ ) {
595
+ return
596
+ }
597
+
598
+ this .textareaCursorPosition = this .$refs [' roomTextarea' ].selectionStart
599
+
600
+ let n = this .textareaCursorPosition
601
+
602
+ while (
603
+ n > 0 &&
604
+ this .message .charAt (n - 1 ) !== ' @' &&
605
+ this .message .charAt (n - 1 ) !== ' '
606
+ ) {
607
+ n--
608
+ }
609
+
610
+ const beforeTag = this .message .charAt (n - 2 )
611
+ const notLetterNumber = ! beforeTag .match (/ ^ [0-9a-zA-Z ] + $ / )
612
+
613
+ if (
614
+ this .message .charAt (n - 1 ) === ' @' &&
615
+ (! beforeTag || beforeTag === ' ' || notLetterNumber)
616
+ ) {
617
+ const query = this .message .substring (n, this .textareaCursorPosition )
618
+
619
+ this .filteredUsersTag = filteredUsers (
620
+ this .room .users ,
621
+ ' username' ,
622
+ query,
623
+ true
624
+ )
625
+ } else {
626
+ this .filteredUsersTag = []
627
+ this .textareaCursorPosition = null
628
+ }
629
+ }
630
+ },
631
+ selectUserTag (user ) {
632
+ const cursorPosition = this .$refs [' roomTextarea' ].selectionStart - 1
633
+ this .message =
634
+ this .message .substr (0 , cursorPosition + 1 ) +
635
+ user .username +
636
+ this .message .substr (cursorPosition + 1 )
637
+ },
548
638
onImgLoad () {
549
639
let height = this .$refs .imageFile .height
550
640
if (height < 30 ) height = 30
@@ -624,6 +714,10 @@ export default {
624
714
addNewMessage (message ) {
625
715
this .newMessages .push (message)
626
716
},
717
+ escapeTextarea () {
718
+ if (this .filteredUsersTag .length ) this .filteredUsersTag = []
719
+ else this .resetMessage ()
720
+ },
627
721
resetMessage (disableMobileFocus = null , editFile = null ) {
628
722
this .$emit (' typing-message' , null )
629
723
@@ -755,6 +849,10 @@ export default {
755
849
756
850
el .style .height = 0
757
851
el .style .height = el .scrollHeight - padding * 2 + ' px'
852
+
853
+ setTimeout (() => {
854
+ this .roomFooterHeight = this .$refs [' roomFooter' ].clientHeight
855
+ }, 10 )
758
856
},
759
857
addEmoji (emoji ) {
760
858
this .message += emoji .icon
@@ -942,7 +1040,7 @@ export default {
942
1040
}
943
1041
944
1042
.vac-room-footer {
945
- width : calc ( 100% - 1 px ) ;
1043
+ width : 100% ;
946
1044
border-bottom-right-radius : 4px ;
947
1045
z-index : 10 ;
948
1046
}
@@ -954,12 +1052,56 @@ export default {
954
1052
padding : 10px 8px 10px ;
955
1053
}
956
1054
1055
+ .vac-tags-container {
1056
+ position : absolute ;
1057
+ display : flex ;
1058
+ flex-direction : column ;
1059
+ align-items : center ;
1060
+ width : 100% ;
1061
+
1062
+ .vac-tags-box {
1063
+ display : flex ;
1064
+ width : 100% ;
1065
+ overflow : hidden ;
1066
+ cursor : pointer ;
1067
+ background : var (--chat-footer-bg-color );
1068
+
1069
+ & :hover {
1070
+ background : var (--chat-footer-bg-color-tag-active );
1071
+ transition : background-color 0.3s cubic-bezier (0.25 , 0.8 , 0.5 , 1 );
1072
+ }
1073
+
1074
+ & :not (:hover ) {
1075
+ transition : background-color 0.3s cubic-bezier (0.25 , 0.8 , 0.5 , 1 );
1076
+ }
1077
+ }
1078
+
1079
+ .vac-tags-info {
1080
+ display : flex ;
1081
+ overflow : hidden ;
1082
+ padding : 10px 20px ;
1083
+ align-items : center ;
1084
+ }
1085
+
1086
+ .vac-tags-avatar {
1087
+ height : 34px ;
1088
+ width : 34px ;
1089
+ min-height : 34px ;
1090
+ min-width : 34px ;
1091
+ }
1092
+
1093
+ .vac-tags-username {
1094
+ font-size : 14px ;
1095
+ }
1096
+ }
1097
+
957
1098
.vac-reply-container {
1099
+ position : absolute ;
958
1100
display : flex ;
959
1101
padding : 10px 10px 0 10px ;
960
- background : var (--chat-content -bg-color );
1102
+ background : var (--chat-footer -bg-color );
961
1103
align-items : center ;
962
- max- width : 100% ;
1104
+ width : calc ( 100% - 20 px ) ;
963
1105
964
1106
.vac-reply-box {
965
1107
width : 100% ;
@@ -1252,6 +1394,11 @@ export default {
1252
1394
1253
1395
.vac-reply-container {
1254
1396
padding : 5px 8px ;
1397
+ width : calc (100% - 16px );
1398
+ }
1399
+
1400
+ .vac-tags-container .vac-tags-info {
1401
+ padding : 8px 12px ;
1255
1402
}
1256
1403
1257
1404
.vac-icon-scroll {
0 commit comments