@@ -124,6 +124,23 @@ pub(crate) enum TimelineItemHandle<'a> {
124124 Local ( & ' a SendHandle ) ,
125125}
126126
127+ /// A single revision in the edit history of a message.
128+ #[ derive( Clone , Debug ) ]
129+ pub struct EditRevision {
130+ /// The body text of the message after this revision.
131+ pub body : String ,
132+ /// The timestamp of the event that created this revision.
133+ pub timestamp : MilliSecondsSinceUnixEpoch ,
134+ /// The raw JSON of the edit event, if available.
135+ /// This is `None` for the original revision and for local echoes
136+ /// that haven't been echoed by the server yet.
137+ ///
138+ /// Only the latest revision retains its `edit_json`; all earlier
139+ /// revisions have this field cleared to `None` to avoid storing
140+ /// copies of raw event JSON for every edit.
141+ pub edit_json : Option < Raw < AnySyncTimelineEvent > > ,
142+ }
143+
127144/// A container for temporarily holding onto data that is going to be erased by
128145/// a redaction once the server plays it back.
129146#[ derive( Clone , Debug ) ]
@@ -134,8 +151,8 @@ pub(super) struct UnredactedEventTimelineItem {
134151 /// JSON of the original event.
135152 pub ( crate ) original_json : Option < Raw < AnySyncTimelineEvent > > ,
136153
137- /// JSON of the latest edit to this item.
138- pub ( crate ) latest_edit_json : Option < Raw < AnySyncTimelineEvent > > ,
154+ /// All edits to this item, in chronological order .
155+ pub ( crate ) edit_revisions : Vec < EditRevision > ,
139156}
140157
141158impl EventTimelineItem {
@@ -453,10 +470,20 @@ impl EventTimelineItem {
453470 }
454471
455472 /// Get the raw JSON representation of the latest edit, if any.
473+ /// Returns the raw JSON of the most recent edit revision, if available.
474+ /// Use `edit_revisions()` for structured access to the full edit history.
456475 pub fn latest_edit_json ( & self ) -> Option < & Raw < AnySyncTimelineEvent > > {
476+ self . edit_revisions ( ) . last ( ) ?. edit_json . as_ref ( )
477+ }
478+
479+ /// Get all revisions of this item, in chronological order.
480+ ///
481+ /// The first entry is the original revision, followed by each edit
482+ /// in the order they were applied.
483+ pub fn edit_revisions ( & self ) -> & [ EditRevision ] {
457484 match & self . kind {
458- EventTimelineItemKind :: Local ( _) => None ,
459- EventTimelineItemKind :: Remote ( remote_event) => remote_event. latest_edit_json . as_ref ( ) ,
485+ EventTimelineItemKind :: Local ( _) => & [ ] ,
486+ EventTimelineItemKind :: Remote ( remote_event) => & remote_event. edit_revisions ,
460487 }
461488 }
462489
@@ -497,20 +524,42 @@ impl EventTimelineItem {
497524 new
498525 }
499526
500- /// Clone the current event item, and update its content.
527+ /// Clone the current event item, and update its content with an edit .
501528 ///
502- /// Optionally update `latest_edit_json` if the update is an edit received
503- /// from the server.
529+ /// If this is the first edit, the original revision is captured
530+ /// automatically from the item's current state. The `edit_revision`
531+ /// contains the edit's data (timestamp and the new body).
532+ /// If `edit_revision` is `None`, no edit revision is recorded
533+ /// (e.g. for non-message edits like polls).
504534 pub ( super ) fn with_content_and_latest_edit (
505535 & self ,
506536 new_content : TimelineItemContent ,
507- edit_json : Option < Raw < AnySyncTimelineEvent > > ,
537+ edit_revision : Option < EditRevision > ,
508538 ) -> Self {
509539 let mut new = self . clone ( ) ;
510- new. content = new_content;
511540 if let EventTimelineItemKind :: Remote ( r) = & mut new. kind {
512- r. latest_edit_json = edit_json;
541+ // If this is the first edit, capture the original as revision 0
542+ if edit_revision. is_some ( ) && r. edit_revisions . is_empty ( ) {
543+ if let Some ( body) = extract_body_from_content ( & new. content ) {
544+ r. edit_revisions . push ( EditRevision {
545+ body,
546+ timestamp : new. timestamp ,
547+ edit_json : None ,
548+ } ) ;
549+ }
550+ }
551+ if let Some ( revision) = edit_revision {
552+ r. edit_revisions . push ( revision) ;
553+ // Keep `edit_json` only on the latest revision to avoid
554+ // storing copies of raw edit event JSON for every revision.
555+ if r. edit_revisions . len ( ) > 1 {
556+ for rev in r. edit_revisions . iter_mut ( ) . rev ( ) . skip ( 1 ) {
557+ rev. edit_json = None ;
558+ }
559+ }
560+ }
513561 }
562+ new. content = new_content;
514563 new
515564 }
516565
@@ -534,10 +583,16 @@ impl EventTimelineItem {
534583
535584 /// Create a clone of the current item, with content that's been redacted.
536585 pub ( super ) fn redact ( & self , rules : & RedactionRules , is_local : bool ) -> Self {
537- let unredacted_item = is_local. then ( || UnredactedEventTimelineItem {
538- content : self . content . clone ( ) ,
539- original_json : self . original_json ( ) . cloned ( ) ,
540- latest_edit_json : self . latest_edit_json ( ) . cloned ( ) ,
586+ let unredacted_item = is_local. then ( || {
587+ let edit_revisions = match & self . kind {
588+ EventTimelineItemKind :: Remote ( r) => r. edit_revisions . clone ( ) ,
589+ EventTimelineItemKind :: Local ( _) => vec ! [ ] ,
590+ } ;
591+ UnredactedEventTimelineItem {
592+ content : self . content . clone ( ) ,
593+ original_json : self . original_json ( ) . cloned ( ) ,
594+ edit_revisions,
595+ }
541596 } ) ;
542597 let content = self . content . redact ( rules) ;
543598 let kind = match & self . kind {
@@ -567,7 +622,7 @@ impl EventTimelineItem {
567622 EventTimelineItemKind :: Remote ( r) => {
568623 EventTimelineItemKind :: Remote ( RemoteEventTimelineItem {
569624 original_json : unredacted_item. original_json . clone ( ) ,
570- latest_edit_json : unredacted_item. latest_edit_json . clone ( ) ,
625+ edit_revisions : unredacted_item. edit_revisions . clone ( ) ,
571626 ..r. clone ( )
572627 } )
573628 }
@@ -914,6 +969,17 @@ impl From<ShieldStateCode> for TimelineEventShieldStateCode {
914969 }
915970}
916971
972+ /// Extract the body text from a `TimelineItemContent`, if it's a message.
973+ pub ( crate ) fn extract_body_from_content ( content : & TimelineItemContent ) -> Option < String > {
974+ match content {
975+ TimelineItemContent :: MsgLike ( msglike) => match & msglike. kind {
976+ MsgLikeKind :: Message ( message) => Some ( message. body ( ) . to_owned ( ) ) ,
977+ _ => None ,
978+ } ,
979+ _ => None ,
980+ }
981+ }
982+
917983#[ cfg( test) ]
918984mod tests {
919985 use std:: time:: Duration ;
@@ -985,7 +1051,7 @@ mod tests {
9851051 is_highlighted : false ,
9861052 encryption_info : None ,
9871053 original_json,
988- latest_edit_json : None ,
1054+ edit_revisions : vec ! [ ] ,
9891055 origin : RemoteEventOrigin :: Sync ,
9901056 } ) ,
9911057 false ,
0 commit comments