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