@@ -168,13 +168,18 @@ export class ChatActions extends Actions<ChatState> {
168168 tag,
169169 noNotification,
170170 submitMentionsRef,
171+ extraInput,
172+ name,
171173 } : {
172174 input ?: string ;
173175 sender_id ?: string ;
174176 reply_to ?: Date ;
175177 tag ?: string ;
176178 noNotification ?: boolean ;
177179 submitMentionsRef ?;
180+ extraInput ?: string ;
181+ // if name is given, rename thread to have that name
182+ name ?: string ;
178183 } ) : string => {
179184 if ( this . syncdb == null || this . store == null ) {
180185 console . warn ( "attempt to sendChat before chat actions initialized" ) ;
@@ -186,11 +191,15 @@ export class ChatActions extends Actions<ChatState> {
186191 if ( submitMentionsRef ?. current != null ) {
187192 input = submitMentionsRef . current ?.( { chat : `${ time_stamp . valueOf ( ) } ` } ) ;
188193 }
194+ if ( extraInput ) {
195+ input = ( input ?? "" ) + extraInput ;
196+ }
189197 input = input ?. trim ( ) ;
190198 if ( ! input ) {
191199 // do not send when there is nothing to send.
192200 return "" ;
193201 }
202+ const trimmedName = name ?. trim ( ) ;
194203 const message : ChatMessage = {
195204 sender_id,
196205 event : "chat" ,
@@ -205,7 +214,12 @@ export class ChatActions extends Actions<ChatState> {
205214 reply_to : reply_to ?. toISOString ( ) ,
206215 editing : { } ,
207216 } ;
217+ if ( trimmedName && ! reply_to ) {
218+ ( message as any ) . name = trimmedName ;
219+ }
208220 this . syncdb . set ( message ) ;
221+ const messagesState = this . store . get ( "messages" ) ;
222+ let selectedThreadKey : string ;
209223 if ( ! reply_to ) {
210224 this . deleteDraft ( 0 ) ;
211225 // NOTE: we also clear search, since it's confusing to send a message and not
@@ -214,17 +228,29 @@ export class ChatActions extends Actions<ChatState> {
214228 // Also, only do this clearing when not replying.
215229 // For replies search find full threads not individual messages.
216230 this . clearAllFilters ( ) ;
231+ selectedThreadKey = `${ time_stamp . valueOf ( ) } ` ;
217232 } else {
218233 // when replying we make sure that the thread is expanded, since otherwise
219234 // our reply won't be visible
220- const messages = this . store . get ( "messages" ) ;
221235 if (
222- messages
236+ messagesState
223237 ?. getIn ( [ `${ reply_to . valueOf ( ) } ` , "folding" ] )
224238 ?. includes ( sender_id )
225239 ) {
226240 this . toggleFoldThread ( reply_to ) ;
227241 }
242+ const root =
243+ getThreadRootDate ( {
244+ date : reply_to . valueOf ( ) ,
245+ messages : messagesState ,
246+ } ) ?? reply_to . valueOf ( ) ;
247+ selectedThreadKey = `${ root } ` ;
248+ }
249+ if ( selectedThreadKey != "0" ) {
250+ this . setSelectedThread ( selectedThreadKey ) ;
251+ }
252+ if ( trimmedName && reply_to ) {
253+ this . renameThread ( selectedThreadKey , trimmedName ) ;
228254 }
229255
230256 const project_id = this . store ?. get ( "project_id" ) ;
@@ -481,6 +507,127 @@ export class ChatActions extends Actions<ChatState> {
481507 } ) ;
482508 } ;
483509
510+ // returns number of deleted messages
511+ // threadKey = iso timestamp root of thread.
512+ deleteThread = ( threadKey : string ) : number => {
513+ if ( this . syncdb == null || this . store == null ) {
514+ return 0 ;
515+ }
516+ const messages = this . store . get ( "messages" ) ;
517+ if ( messages == null ) {
518+ return 0 ;
519+ }
520+ const rootTarget = parseInt ( `${ threadKey } ` ) ;
521+ if ( ! isFinite ( rootTarget ) ) {
522+ return 0 ;
523+ }
524+ let deleted = 0 ;
525+ for ( const [ _ , message ] of messages ) {
526+ if ( message == null ) continue ;
527+ const dateField = message . get ( "date" ) ;
528+ let dateValue : number | undefined ;
529+ let dateIso : string | undefined ;
530+ if ( dateField instanceof Date ) {
531+ dateValue = dateField . valueOf ( ) ;
532+ dateIso = dateField . toISOString ( ) ;
533+ } else if ( typeof dateField === "number" ) {
534+ dateValue = dateField ;
535+ dateIso = new Date ( dateField ) . toISOString ( ) ;
536+ } else if ( typeof dateField === "string" ) {
537+ const t = Date . parse ( dateField ) ;
538+ dateValue = isNaN ( t ) ? undefined : t ;
539+ dateIso = dateField ;
540+ }
541+ if ( dateValue == null || dateIso == null ) {
542+ continue ;
543+ }
544+ const rootDate =
545+ getThreadRootDate ( { date : dateValue , messages } ) || dateValue ;
546+ if ( rootDate !== rootTarget ) {
547+ continue ;
548+ }
549+ this . syncdb . delete ( { event : "chat" , date : dateIso } ) ;
550+ deleted ++ ;
551+ }
552+ if ( deleted > 0 ) {
553+ this . syncdb . commit ( ) ;
554+ }
555+ return deleted ;
556+ } ;
557+
558+ renameThread = ( threadKey : string , name : string ) : boolean => {
559+ if ( this . syncdb == null ) {
560+ return false ;
561+ }
562+ const entry = this . getThreadRootDoc ( threadKey ) ;
563+ if ( entry == null ) {
564+ return false ;
565+ }
566+ const trimmed = name . trim ( ) ;
567+ if ( trimmed ) {
568+ entry . doc . name = trimmed ;
569+ } else {
570+ delete entry . doc . name ;
571+ }
572+ this . syncdb . set ( entry . doc ) ;
573+ this . syncdb . commit ( ) ;
574+ return true ;
575+ } ;
576+
577+ markThreadRead = ( threadKey : string , count : number ) : boolean => {
578+ if ( this . syncdb == null ) {
579+ return false ;
580+ }
581+ const account_id = this . redux . getStore ( "account" ) . get_account_id ( ) ;
582+ if ( ! account_id || ! Number . isFinite ( count ) ) {
583+ return false ;
584+ }
585+ const entry = this . getThreadRootDoc ( threadKey ) ;
586+ if ( entry == null ) {
587+ return false ;
588+ }
589+ entry . doc [ `read-${ account_id } ` ] = count ;
590+ this . syncdb . set ( entry . doc ) ;
591+ this . syncdb . commit ( ) ;
592+ return true ;
593+ } ;
594+
595+ private getThreadRootDoc = (
596+ threadKey : string ,
597+ ) : { doc : any ; message : ChatMessageTyped } | null => {
598+ if ( this . store == null ) {
599+ return null ;
600+ }
601+ const messages = this . store . get ( "messages" ) ;
602+ if ( messages == null ) {
603+ return null ;
604+ }
605+ const normalizedKey = toMsString ( threadKey ) ;
606+ const fallbackKey = `${ parseInt ( threadKey , 10 ) } ` ;
607+ const candidates = [ normalizedKey , threadKey , fallbackKey ] ;
608+ let message : ChatMessageTyped | undefined ;
609+ for ( const key of candidates ) {
610+ if ( ! key ) continue ;
611+ message = messages . get ( key ) ;
612+ if ( message != null ) break ;
613+ }
614+ if ( message == null ) {
615+ return null ;
616+ }
617+ const dateField = message . get ( "date" ) ;
618+ const dateIso =
619+ dateField instanceof Date
620+ ? dateField . toISOString ( )
621+ : typeof dateField === "string"
622+ ? dateField
623+ : new Date ( dateField ) . toISOString ( ) ;
624+ if ( ! dateIso ) {
625+ return null ;
626+ }
627+ const doc = { ...message . toJS ( ) , date : dateIso } ;
628+ return { doc, message } ;
629+ } ;
630+
484631 save_scroll_state = ( position , height , offset ) : void => {
485632 if ( height == 0 ) {
486633 // height == 0 means chat room is not rendered
@@ -1104,13 +1251,15 @@ export class ChatActions extends Actions<ChatState> {
11041251 } ;
11051252
11061253 setFragment = ( date ?) => {
1254+ let fragmentId ;
11071255 if ( ! date ) {
11081256 Fragment . clear ( ) ;
1257+ fragmentId = "" ;
11091258 } else {
1110- const fragmentId = toMsString ( date ) ;
1259+ fragmentId = toMsString ( date ) ;
11111260 Fragment . set ( { chat : fragmentId } ) ;
1112- this . frameTreeActions ?. set_frame_data ( { id : this . frameId , fragmentId } ) ;
11131261 }
1262+ this . frameTreeActions ?. set_frame_data ( { id : this . frameId , fragmentId } ) ;
11141263 } ;
11151264
11161265 setShowPreview = ( showPreview ) => {
@@ -1119,6 +1268,14 @@ export class ChatActions extends Actions<ChatState> {
11191268 showPreview,
11201269 } ) ;
11211270 } ;
1271+
1272+ setSelectedThread = ( threadKey : string | null ) => {
1273+ console . log ( "setSelectedThread" , { threadKey } ) ;
1274+ this . frameTreeActions ?. set_frame_data ( {
1275+ id : this . frameId ,
1276+ selectedThreadKey : threadKey ,
1277+ } ) ;
1278+ } ;
11221279}
11231280
11241281// We strip out any cased version of the string @chatgpt and also all mentions.
0 commit comments