Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion Riot/Modules/MatrixKit/Models/Room/MXKRoomDataSource.m
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,8 @@ - (void)refreshEventListeners:(NSArray *)liveEventTypesFilterForMessages
// Update bubble data
NSUInteger remainingEvents = [bubbleData updateEvent:redactionEvent.redacts withEvent:redactedEvent];

[self refreshRepliesWithUpdatedEventId:redactedEvent.eventId];

hasChanged = YES;

// Remove the bubble if there is no more events
Expand Down Expand Up @@ -4242,10 +4244,34 @@ - (void)unregisterEventEditsListener
}
}

- (BOOL)refreshRepliesWithUpdatedEventId:(NSString*)updatedEventId
{
BOOL hasChanged = NO;

@synchronized (bubbles) {
for (id<MXKRoomBubbleCellDataStoring> bubbleCellData in bubbles)
{
for (MXEvent *event in bubbleCellData.events)
{
if ([event.relatesTo.inReplyTo.eventId isEqual:updatedEventId])
{
[bubbleCellData updateEvent:event.eventId withEvent:event];
[bubbleCellData invalidateTextLayout];
hasChanged = YES;
}
}
}
}

return hasChanged;
}

- (BOOL)updateCellData:(id<MXKRoomBubbleCellDataStoring>)bubbleCellData forEditionWithReplaceEvent:(MXEvent*)replaceEvent andEventId:(NSString*)eventId
{
BOOL hasChanged = NO;


hasChanged = [self refreshRepliesWithUpdatedEventId:eventId];

@synchronized (bubbleCellData)
{
// Retrieve the original event to edit it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,13 @@ typedef enum : NSUInteger {
*/
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState isEditMode:(BOOL)isEditMode;

/**
Defines the replacement attributed string for a redacted message.

@return attributed string describing redacted message.
*/
- (NSAttributedString*)redactedMessageReplacementAttributedString;

/**
Same as [self renderString:forEvent:] but add a prefix.
The prefix will be rendered with 'prefixTextFont' and 'prefixTextColor'.
Expand Down
98 changes: 98 additions & 0 deletions Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m
Original file line number Diff line number Diff line change
Expand Up @@ -1757,11 +1757,19 @@ - (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*
- (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*)event withRoomState:(MXRoomState*)roomState isEditMode:(BOOL)isEditMode
{
NSString *html = htmlString;
MXEvent *repliedEvent;

// Special treatment for "In reply to" message
// Note: `isEditMode` fixes an issue where editing a reply would display an "In reply to" span instead of a mention.
if (!isEditMode && (event.isReplyEvent || (!RiotSettings.shared.enableThreads && event.isInThread)))
{
repliedEvent = [self->mxSession.store eventWithEventId:event.relatesTo.inReplyTo.eventId inRoom:roomState.roomId];
if (repliedEvent)
{
// Try to construct rich reply.
html = [self buildHTMLStringForEvent:event inReplyToEvent:repliedEvent] ?: html;
}

html = [self renderReplyTo:html withRoomState:roomState];
}

Expand Down Expand Up @@ -1804,6 +1812,18 @@ - (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*
// Finalize HTML blockquote blocks marking
str = [MXKTools removeMarkedBlockquotesArtifacts:str];

if (repliedEvent && repliedEvent.isRedactedEvent)
{
// Replace the description of an empty replied event
NSMutableAttributedString *mutableStr = [[NSMutableAttributedString alloc] initWithAttributedString:str];
NSRange nullRange = [mutableStr.string rangeOfString:@"(null)"];
if (nullRange.location != NSNotFound)
{
[mutableStr replaceCharactersInRange:nullRange withAttributedString:[self redactedMessageReplacementAttributedString]];
str = mutableStr;
}
}

UIFont *fontForBody = [self fontForEvent:event string:nil];
if ([fontForWholeString isEqual:fontForBody])
{
Expand Down Expand Up @@ -1832,6 +1852,84 @@ - (NSAttributedString*)renderHTMLString:(NSString*)htmlString forEvent:(MXEvent*
return mutableStr;
}

- (NSAttributedString*)redactedMessageReplacementAttributedString
{
return [[NSAttributedString alloc] initWithString:VectorL10n.eventFormatterMessageDeleted];
}

/**
Build the HTML body of a reply from its related event (rich replies).

@param event the reply event.
@param repliedEvent the event it replies to.
@return an html string containing the updated content of both events.
*/
- (NSString*)buildHTMLStringForEvent:(MXEvent*)event inReplyToEvent:(MXEvent*)repliedEvent
{
NSString *repliedEventContent;
NSString *eventContent;
NSString *html;

if (repliedEvent.isRedactedEvent)
{
repliedEventContent = nil;
}
else
{
if (repliedEvent.content[kMXMessageContentKeyNewContent])
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageContentKeyNewContent][@"formatted_body"]);
if (!repliedEventContent)
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
}
}
else
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[@"formatted_body"]);
if (!repliedEventContent)
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageBodyKey]);
}
}
}

if (event.content[kMXMessageContentKeyNewContent])
{
MXJSONModelSetString(eventContent, event.content[kMXMessageContentKeyNewContent][@"formatted_body"]);
if (!eventContent)
{
MXJSONModelSetString(eventContent, event.content[kMXMessageContentKeyNewContent][kMXMessageBodyKey]);
}
}
else
{
MXReplyEventParser *parser = [[MXReplyEventParser alloc] init];
MXReplyEventParts *parts = [parser parse:event];
MXJSONModelSetString(eventContent, parts.formattedBodyParts.replyText)
if (!eventContent)
{
MXJSONModelSetString(eventContent, parts.bodyParts.replyText)
}
}

if (eventContent && repliedEvent.sender)
{
html = [NSString stringWithFormat:@"<mx-reply><blockquote><a href=\"%@\">In reply to</a> <a href=\"%@\">%@</a><br>%@</blockquote></mx-reply>%@",
[MXTools permalinkToEvent:repliedEvent.eventId inRoom:repliedEvent.roomId],
[MXTools permalinkToUserWithUserId:repliedEvent.sender],
repliedEvent.sender,
repliedEventContent,
eventContent];
}
else
{
MXLogDebug(@"[MXKEventFormatter] Unable to build reply event %@", event.description)
}

return html;
}

/**
Special treatment for "In reply to" message.

Expand Down
14 changes: 11 additions & 3 deletions Riot/Modules/Room/DataSources/RoomDataSource.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,18 @@ extension RoomDataSource {
let editableTextMessage: NSAttributedString?

if event.isReply() {
let parser = MXReplyEventParser()
let replyEventParts = parser.parse(event)
let body: String
if let newContent = event.content[kMXMessageContentKeyNewContent] as? [String:Any] {
// Use new content if available.
body = newContent["formatted_body"] as? String ?? newContent[kMXMessageBodyKey] as? String ?? ""
} else {
// Otherwise parse MXReply.
let parser = MXReplyEventParser()
let replyEventParts = parser.parse(event)

body = replyEventParts?.formattedBodyParts?.replyText ?? replyEventParts?.bodyParts.replyText ?? ""
}

let body: String = replyEventParts?.formattedBodyParts?.replyText ?? replyEventParts?.bodyParts.replyText ?? ""
let attributed = eventFormatter.renderHTMLString(body, for: event, with: self.roomState, isEditMode: true)
if let attributed = attributed, #available(iOS 15.0, *) {
editableTextMessage = PillsFormatter.insertPills(in: attributed,
Expand Down
41 changes: 24 additions & 17 deletions Riot/Utils/EventFormatter.m
Original file line number Diff line number Diff line change
Expand Up @@ -143,23 +143,7 @@ - (NSAttributedString *)unsafeAttributedStringFromEvent:(MXEvent *)event withRoo
if ((RiotSettings.shared.enableThreads && [mxSession.threadingService isEventThreadRoot:event])
|| self.settings.showRedactionsInRoomHistory)
{
UIFont *font = self.defaultTextFont;
UIColor *color = ThemeService.shared.theme.colors.secondaryContent;
NSString *string = [NSString stringWithFormat:@" %@", VectorL10n.eventFormatterMessageDeleted];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string
attributes:@{
NSFontAttributeName: font,
NSForegroundColorAttributeName: color
}];

CGSize imageSize = CGSizeMake(20, 20);
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [[AssetImages.roomContextMenuDelete.image vc_resizedWith:imageSize] vc_tintedImageUsingColor:color];
attachment.bounds = CGRectMake(0, font.descender, imageSize.width, imageSize.height);
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment];

NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithAttributedString:imageString];
[result appendAttributedString:attrString];
NSAttributedString *result = [self redactedMessageReplacementAttributedString];

if (error)
{
Expand Down Expand Up @@ -539,6 +523,29 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary
return updated;
}

- (NSAttributedString *)redactedMessageReplacementAttributedString
{
UIFont *font = self.defaultTextFont;
UIColor *color = ThemeService.shared.theme.colors.secondaryContent;
NSString *string = [NSString stringWithFormat:@" %@", VectorL10n.eventFormatterMessageDeleted];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string
attributes:@{
NSFontAttributeName: font,
NSForegroundColorAttributeName: color
}];

CGSize imageSize = CGSizeMake(20, 20);
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [[AssetImages.roomContextMenuDelete.image vc_resizedWith:imageSize] vc_tintedImageUsingColor:color];
attachment.bounds = CGRectMake(0, font.descender, imageSize.width, imageSize.height);
NSAttributedString *imageString = [NSAttributedString attributedStringWithAttachment:attachment];

NSMutableAttributedString *result = [[NSMutableAttributedString alloc] initWithAttributedString:imageString];
[result appendAttributedString:attrString];

return result;
}

- (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary withServerRoomSummary:(MXRoomSyncSummary *)serverRoomSummary roomState:(MXRoomState *)roomState
{
BOOL updated = [super session:session updateRoomSummary:summary withServerRoomSummary:serverRoomSummary roomState:roomState];
Expand Down
1 change: 1 addition & 0 deletions changelog.d/pr-6155.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Partial implementation of rich replies