@@ -634,6 +634,9 @@ sealed class Message {
634
634
final String contentType;
635
635
636
636
// final List<MessageEditHistory> editHistory; // TODO handle
637
+ @JsonKey (readValue: MessageEditState .readFromMessage, fromJson: Message ._messageEditStateFromJson)
638
+ MessageEditState messageEditState;
639
+
637
640
final int id;
638
641
bool isMeMessage;
639
642
int ? lastEditTimestamp;
@@ -658,6 +661,12 @@ sealed class Message {
658
661
final String ? matchContent;
659
662
final String ? matchSubject;
660
663
664
+ static MessageEditState _messageEditStateFromJson (dynamic json) {
665
+ // The value passed here must be a MessageEditState already due to
666
+ // processing work done in [MessageEditState.readFromMessage].
667
+ return json as MessageEditState ;
668
+ }
669
+
661
670
static Reactions ? _reactionsFromJson (dynamic json) {
662
671
final list = (json as List <dynamic >);
663
672
return list.isNotEmpty ? Reactions .fromJson (list) : null ;
@@ -676,6 +685,7 @@ sealed class Message {
676
685
required this .client,
677
686
required this .content,
678
687
required this .contentType,
688
+ required this .messageEditState,
679
689
required this .id,
680
690
required this .isMeMessage,
681
691
required this .lastEditTimestamp,
@@ -741,6 +751,7 @@ class StreamMessage extends Message {
741
751
required super .client,
742
752
required super .content,
743
753
required super .contentType,
754
+ required super .messageEditState,
744
755
required super .id,
745
756
required super .isMeMessage,
746
757
required super .lastEditTimestamp,
@@ -843,6 +854,7 @@ class DmMessage extends Message {
843
854
required super .client,
844
855
required super .content,
845
856
required super .contentType,
857
+ required super .messageEditState,
846
858
required super .id,
847
859
required super .isMeMessage,
848
860
required super .lastEditTimestamp,
@@ -866,3 +878,80 @@ class DmMessage extends Message {
866
878
@override
867
879
Map <String , dynamic > toJson () => _$DmMessageToJson (this );
868
880
}
881
+
882
+ @visibleForTesting
883
+ enum MessageEditState {
884
+ none,
885
+ edited,
886
+ moved;
887
+
888
+ static bool isTopicMoved (String topic, String prevTopic) {
889
+ // TODO(#744) Extract this to its own home to fully support mark as resolve
890
+
891
+ // Code adapted from the shared code: web/shared/src/resolve_topic.ts
892
+
893
+ /**
894
+ * Pattern for an arbitrary resolved-topic prefix.
895
+ *
896
+ * These always begin with the canonical prefix, but can go on longer.
897
+ */
898
+ // The class has the same characters as RESOLVED_TOPIC_PREFIX.
899
+ // It's designed to remove a weird "✔ ✔✔ " prefix, if present.
900
+ final RegExp resolvedTopicPrefixRe = RegExp ('^✔ [ ✔]*' );
901
+
902
+ // Normalize both topics so the resolved-topic prefix do not interfere.
903
+ topic = topic.replaceFirst (resolvedTopicPrefixRe, '' );
904
+ prevTopic = prevTopic.replaceFirst (resolvedTopicPrefixRe, '' );
905
+
906
+ return topic != prevTopic;
907
+ }
908
+
909
+ static MessageEditState readFromMessage (Map <dynamic , dynamic > json, String key) {
910
+ // TODO refactor this into a helper that computes this from the serialized
911
+ // MessageEditHistory Otherwise, editHistory has to be a list
912
+ final editHistory = (json['edit_history' ] as List <dynamic >? );
913
+
914
+ if (editHistory == null ) {
915
+ return (json['last_edit_timestamp' ] != null )
916
+ ? MessageEditState .edited : MessageEditState .none;
917
+ }
918
+
919
+ // Edit history should never be empty whenever it is present
920
+ assert (editHistory.isNotEmpty);
921
+
922
+ bool hasEditedContent = false ;
923
+ bool hasMoved = false ;
924
+ for (final entry in editHistory) {
925
+ if (entry['prev_content' ] != null ) {
926
+ hasEditedContent = true ;
927
+ }
928
+
929
+ if (entry['prev_stream' ] != null ) {
930
+ hasMoved = true ;
931
+ }
932
+
933
+ // TODO(server-5) prev_subject was the old name of prev_topic on pre-5.0 servers
934
+ if (entry['prev_topic' ] != null || entry['prev_subject' ] != null ) {
935
+ // TODO(server-5) pre-5.0 servers do not have the 'topic' field
936
+ if (entry['topic' ] == null ) {
937
+ hasMoved = true ;
938
+ }
939
+ else {
940
+ hasMoved = isTopicMoved (
941
+ entry['topic' ] as String ,
942
+ // TODO(server-5) prev_subject was the old name of prev_topic on pre-5.0 servers
943
+ (entry['prev_topic' ] ?? entry['prev_subject' ]) as String
944
+ );
945
+ }
946
+ }
947
+ }
948
+
949
+ // Prioritize the 'edited' state over 'moved' when they both apply
950
+ if (hasEditedContent) return MessageEditState .edited;
951
+
952
+ if (hasMoved) return MessageEditState .moved;
953
+
954
+ // This can happen when a topic is resolved but nothing else has been edited
955
+ return MessageEditState .none;
956
+ }
957
+ }
0 commit comments