8686 :isGroupCreator ='isGroupCreator'
8787 :class ='{removed: message.delete}'
8888 @retry ='retryMessage(index)'
89- @reply ='replyMessage (message)'
89+ @reply ='replyToMessage (message)'
9090 @scroll-to-replying-message ='scrollToMessage(message.replyingMessage.hash)'
9191 @edit-message ='(newMessage) => editMessage(message, newMessage)'
9292 @pin-to-channel ='pinToChannel(message)'
@@ -143,13 +143,19 @@ import ViewArea from './ViewArea.vue'
143143import Emoticons from ' ./Emoticons.vue'
144144import TouchLinkHelper from ' ./TouchLinkHelper.vue'
145145import DragActiveOverlay from ' ./file-attachment/DragActiveOverlay.vue'
146- import { MESSAGE_TYPES , MESSAGE_VARIANTS , CHATROOM_ACTIONS_PER_PAGE , CHATROOM_MEMBER_MENTION_SPECIAL_CHAR } from ' @model/contracts/shared/constants.js'
146+ import {
147+ MESSAGE_TYPES , MESSAGE_VARIANTS ,
148+ CHATROOM_ACTIONS_PER_PAGE ,
149+ CHATROOM_MEMBER_MENTION_SPECIAL_CHAR ,
150+ CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS
151+ } from ' @model/contracts/shared/constants.js'
147152import { CHATROOM_EVENTS , NEW_CHATROOM_UNREAD_POSITION , DELETE_ATTACHMENT_FEEDBACK } from ' @utils/events.js'
148153import { findMessageIdx } from ' @model/contracts/shared/functions.js'
149154import { proximityDate , MINS_MILLIS } from ' @model/contracts/shared/time.js'
150155import { cloneDeep , debounce , throttle , delay } from ' turtledash'
151156import { EVENT_HANDLED } from ' ~/shared/domains/chelonia/events.js'
152157import { compressImage } from ' @utils/image.js'
158+ import { swapMentionIDForDisplayname } from ' @model/chatroom/utils.js'
153159
154160const ignorableScrollDistanceInPixel = 500
155161
@@ -253,14 +259,19 @@ export default ({
253259 // NOTE: messagesInitiated describes if the messages are fully re-rendered
254260 // according to this, we could display loading/skeleton component
255261 messagesInitiated: undefined ,
256- scrollHashOnInitialLoad: null , // Message hash to scroll to on chatroom's initial load
257262 replyingMessage: null ,
258263 replyingTo: null ,
259264 unprocessedEvents: [],
260265
261266 // Related to switching chatrooms
262267 chatroomIdToSwitchTo: null ,
263- renderingChatRoomId: null
268+ renderingChatRoomId: null ,
269+
270+ // Related to auto-scrolling to initial position
271+ initialScroll: {
272+ hash: null ,
273+ timeoutId: null
274+ }
264275 },
265276 messageState: {
266277 contract: {}
@@ -281,7 +292,7 @@ export default ({
281292 },
282293 mounted () {
283294 // setup various event listeners.
284- this .ephemeral .onChatScroll = debounce (onChatScroll .bind (this ), 500 )
295+ this .ephemeral .onChatScroll = debounce (onChatScroll .bind (this ), 300 )
285296 sbp (' okTurtles.events/on' , EVENT_HANDLED , this .listenChatRoomActions )
286297 window .addEventListener (' resize' , this .resizeEventHandler )
287298
@@ -365,7 +376,9 @@ export default ({
365376 }
366377 },
367378 replyingMessageText (message ) {
368- return message .replyingMessage ? .text || ' '
379+ return message .replyingMessage ? .text
380+ ? message .replyingMessage .text .slice (0 , CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS )
381+ : ' '
369382 },
370383 time (strTime ) {
371384 return new Date (strTime)
@@ -395,15 +408,7 @@ export default ({
395408
396409 const data = { type: MESSAGE_TYPES .TEXT , text }
397410 if (replyingMessage) {
398- // NOTE: If not replying to a message, use original data; otherwise, append replyingMessage to data.
399411 data .replyingMessage = replyingMessage
400- // NOTE: for the messages with only images, the text should be updated with file name
401- if (! replyingMessage .text ) {
402- const msg = this .messages .find (m => (m .hash === replyingMessage .hash ))
403- if (msg) {
404- data .replyingMessage .text = msg .attachments [0 ].name
405- }
406- }
407412 }
408413
409414 const sendMessage = (beforePrePublish ) => {
@@ -590,7 +595,7 @@ export default ({
590595 }
591596 }
592597 },
593- updateScroll (scrollTargetMessage = null , effect = false ) {
598+ updateScroll (scrollTargetMessage = null , effect = false , delay = 100 ) {
594599 const contractID = this .ephemeral .renderingChatRoomId
595600 if (contractID) {
596601 return new Promise ((resolve ) => {
@@ -604,7 +609,7 @@ export default ({
604609 }
605610
606611 resolve ()
607- }, 100 )
612+ }, delay )
608613 })
609614 }
610615 },
@@ -624,21 +629,30 @@ export default ({
624629 this .messages .splice (index, 1 )
625630 this .handleSendMessage (message .text , message .attachments , message .replyingMessage )
626631 },
627- replyMessage (message ) {
628- const { text , hash , type } = message
632+ replyToMessage (message ) {
633+ const { text , hash , type , attachments } = message
634+ const isTypeInteractive = type === MESSAGE_TYPES .INTERACTIVE
629635
630- if (type === MESSAGE_TYPES . INTERACTIVE ) {
636+ if (isTypeInteractive ) {
631637 const proposal = message .proposal
632638
633639 this .ephemeral .replyingMessage = {
634- text: interactiveMessage (proposal, { from: ` ${ CHATROOM_MEMBER_MENTION_SPECIAL_CHAR }${ proposal .creatorID } ` }),
635- hash
640+ hash, text: interactiveMessage (proposal, { from: ` ${ CHATROOM_MEMBER_MENTION_SPECIAL_CHAR }${ proposal .creatorID } ` })
636641 }
637- this .ephemeral .replyingTo = L (' Proposal notification' )
642+ } else if (! text && attachments? .length ) {
643+ this .ephemeral .replyingMessage = { hash, text: attachments[0 ].name }
638644 } else {
639- this .ephemeral .replyingMessage = { text, hash }
640- this .ephemeral .replyingTo = this .who (message)
645+ const sanitizeAndTruncate = text => {
646+ return swapMentionIDForDisplayname (text)
647+ .replace (/ \s + / g , ' ' ) // Normalize spaces
648+ .trim ()
649+ .slice (0 , CHATROOM_REPLYING_MESSAGE_LIMITS_IN_CHARS )
650+ }
651+
652+ this .ephemeral .replyingMessage = { hash, text: sanitizeAndTruncate (text) }
641653 }
654+
655+ this .ephemeral .replyingTo = isTypeInteractive ? L (' Proposal notification' ) : this .who (message)
642656 },
643657 editMessage (message , newMessage ) {
644658 message .text = newMessage
@@ -863,7 +877,7 @@ export default ({
863877
864878 if (! this .ephemeral .messagesInitiated ) {
865879 this .setStartNewMessageIndex ()
866- this .ephemeral .scrollHashOnInitialLoad = messageHashToScroll
880+ this .ephemeral .initialScroll . hash = messageHashToScroll
867881 }
868882
869883 return events .length > 0 && SPMessage .deserializeHEAD (events[0 ]).head .height === 0
@@ -889,6 +903,30 @@ export default ({
889903 Vue .set (this .messageState , ' contract' , state)
890904 }
891905 },
906+ scrollToIntialPosition () {
907+ const hashTo = this .ephemeral .initialScroll .hash
908+ if (hashTo) {
909+ const scrollingToSpecificMessage = this .$route .query ? .mhash === hashTo
910+ this .$nextTick (() => {
911+ this .updateScroll (
912+ hashTo,
913+ // NOTE: we do want the 'c-focused' animation if there is a scroll-to-message query.
914+ scrollingToSpecificMessage,
915+ 0 // don't need the delay here
916+ )
917+ // NOTE: delete mhash in the query after scroll and highlight the message with mhash
918+ if (scrollingToSpecificMessage) {
919+ const newQuery = { ... this .$route .query }
920+ delete newQuery .mhash
921+ this .$router .replace ({ query: newQuery })
922+ }
923+
924+ // Once scrolling is complete, reset the hash to null.
925+ // This ensures that 'auto-scroll to initial position' happens only once.
926+ this .ephemeral .initialScroll .hash = null
927+ })
928+ }
929+ },
892930 setStartNewMessageIndex () {
893931 this .ephemeral .startedUnreadMessageHash = null
894932 if (this .currentChatRoomReadUntil ) {
@@ -1055,6 +1093,10 @@ export default ({
10551093
10561094 if (this .ephemeral .messagesInitiated === undefined ) return
10571095
1096+ if (this .ephemeral .initialScroll .hash ) {
1097+ clearTimeout (this .ephemeral .initialScroll .timeoutId )
1098+ }
1099+
10581100 const targetChatroomID = this .ephemeral .renderingChatRoomId
10591101 sbp (' okTurtles.eventQueue/queueEvent' , CHATROOM_EVENTS , async () => {
10601102 if (this .chatroomHasSwitchedFrom (targetChatroomID)) return
@@ -1087,29 +1129,20 @@ export default ({
10871129 }
10881130
10891131 if (completed !== undefined && ! this .ephemeral .messagesInitiated ) {
1090- // NOTE: 'this.ephemeral.messagesInitiated' can be set true only when renderMoreMessages are successfully proceeded
1132+ // NOTE: 'this.ephemeral.messagesInitiated' can be set true only when renderMoreMessages are successfully proceeded
10911133 this .ephemeral .messagesInitiated = true
1092-
1093- if (this .ephemeral .scrollHashOnInitialLoad ) {
1094- const scrollingToSpecificMessage = this .$route .query ? .mhash === this .ephemeral .scrollHashOnInitialLoad
1095- this .$nextTick (() => {
1096- this .updateScroll (
1097- this .ephemeral .scrollHashOnInitialLoad ,
1098- scrollingToSpecificMessage // NOTE: we do want the 'c-focused' animation if there is a scroll-to-message query.
1099- )
1100- // NOTE: delete mhash in the query after scroll and highlight the message with mhash
1101- if (scrollingToSpecificMessage) {
1102- const newQuery = { ... this .$route .query }
1103- delete newQuery .mhash
1104- this .$router .replace ({ query: newQuery })
1105- }
1106- this .ephemeral .scrollHashOnInitialLoad = null
1107- })
1108- }
11091134 }
11101135 } catch (e) {
11111136 console .error (' ChatMain infiniteHandler() error:' , e)
11121137 }
1138+
1139+ if (this .ephemeral .messagesInitiated ) {
1140+ // Sometimes even after 'messagesInitiated' is set to 'true', infiniteHandler() is called again and loads more messages.
1141+ // In that case, we should defer 'auto-scrolling to the initial-position' until those additional messages are rendered.
1142+ // This can be achieved by calling 'scrollToInitialPosition' here with setTimeout(),
1143+ // and calling clearTimeout() at the start of infiniteHandler().
1144+ this .ephemeral .initialScroll .timeoutId = setTimeout (this .scrollToIntialPosition , 150 )
1145+ }
11131146 })
11141147 },
11151148 onChatScroll () {
0 commit comments