Skip to content

Commit

Permalink
Merge pull request #7284 from vector-im/alfogrillo/reply_to_ended_polls
Browse files Browse the repository at this point in the history
Render replies to ended polls (PSG-1131)
  • Loading branch information
Alfonso Grillo authored Jan 19, 2023
2 parents f410ad1 + 0df3c31 commit fdf1839
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 17 deletions.
2 changes: 2 additions & 0 deletions Riot/Assets/en.lproj/Vector.strings
Original file line number Diff line number Diff line change
Expand Up @@ -2369,6 +2369,8 @@ Tap the + to start adding people.";

"poll_timeline_ended_text" = "Ended the poll";

"poll_timeline_reply_ended_poll" = "Ended poll";

// MARK: - Location sharing

"location_sharing_title" = "Location";
Expand Down
4 changes: 4 additions & 0 deletions Riot/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4883,6 +4883,10 @@ public class VectorL10n: NSObject {
public static var pollTimelineOneVote: String {
return VectorL10n.tr("Vector", "poll_timeline_one_vote")
}
/// Ended poll
public static var pollTimelineReplyEndedPoll: String {
return VectorL10n.tr("Vector", "poll_timeline_reply_ended_poll")
}
/// Final results based on %lu votes
public static func pollTimelineTotalFinalResults(_ p1: Int) -> String {
return VectorL10n.tr("Vector", "poll_timeline_total_final_results", p1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,38 @@ import Foundation

class MXKSendReplyEventStringLocalizer: NSObject, MXSendReplyEventStringLocalizerProtocol {
func senderSentAnImage() -> String {
return VectorL10n.messageReplyToSenderSentAnImage
VectorL10n.messageReplyToSenderSentAnImage
}

func senderSentAVideo() -> String {
return VectorL10n.messageReplyToSenderSentAVideo
VectorL10n.messageReplyToSenderSentAVideo
}

func senderSentAnAudioFile() -> String {
return VectorL10n.messageReplyToSenderSentAnAudioFile
VectorL10n.messageReplyToSenderSentAnAudioFile
}

func senderSentAVoiceMessage() -> String {
return VectorL10n.messageReplyToSenderSentAVoiceMessage
VectorL10n.messageReplyToSenderSentAVoiceMessage
}

func senderSentAFile() -> String {
return VectorL10n.messageReplyToSenderSentAFile
VectorL10n.messageReplyToSenderSentAFile
}

func senderSentTheirLocation() -> String {
return VectorL10n.messageReplyToSenderSentTheirLocation
VectorL10n.messageReplyToSenderSentTheirLocation
}

func senderSentTheirLiveLocation() -> String {
return VectorL10n.messageReplyToSenderSentTheirLiveLocation
VectorL10n.messageReplyToSenderSentTheirLiveLocation
}

func messageToReplyToPrefix() -> String {
return VectorL10n.messageReplyToMessageToReplyToPrefix
VectorL10n.messageReplyToMessageToReplyToPrefix
}

func replyToEndedPoll() -> String {
VectorL10n.pollTimelineReplyEndedPoll
}
}
46 changes: 46 additions & 0 deletions Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#import "GeneratedInterface-Swift.h"

static NSString *const kHTMLATagRegexPattern = @"<a href=(?:'|\")(.*?)(?:'|\")>([^<]*)</a>";
static NSString *const kRepliedTextPattern = @"<mx-reply>.*<blockquote>.*<br>(.*)</blockquote></mx-reply>";

@interface MXKEventFormatter ()
{
Expand Down Expand Up @@ -1808,6 +1809,7 @@ - (NSAttributedString*)renderHTMLString:(NSString*)htmlString
}

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

// Apply the css style that corresponds to the event state
Expand Down Expand Up @@ -1880,6 +1882,12 @@ - (NSString*)buildHTMLStringForEvent:(MXEvent*)event inReplyToEvent:(MXEvent*)re
{
MXJSONModelSetString(repliedEventContent, repliedEvent.content[kMXMessageBodyKey]);
}
if (!repliedEventContent && repliedEvent.eventType == MXEventTypePollStart) {
repliedEventContent = [MXEventContentPollStart modelFromJSON:repliedEvent.content].question;
}
if (!repliedEventContent && repliedEvent.eventType == MXEventTypePollEnd) {
repliedEventContent = MXSendReplyEventDefaultStringLocalizer.new.replyToEndedPoll;
}
}

// No message content in a non-redacted event. Formatter should use fallback.
Expand Down Expand Up @@ -2014,6 +2022,44 @@ - (NSString*)renderReplyTo:(NSString*)htmlString withRoomState:(MXRoomState*)roo
return html;
}

- (NSString*)renderPollEndedReplyTo:(NSString*)htmlString repliedEvent:(MXEvent*)repliedEvent {
static NSRegularExpression *endedPollRegex;
static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{
endedPollRegex = [NSRegularExpression regularExpressionWithPattern:kRepliedTextPattern options:NSRegularExpressionCaseInsensitive error:nil];
});

NSString* finalString = htmlString;

if (repliedEvent.eventType != MXEventTypePollEnd) {
return finalString;
}

NSTextCheckingResult* match = [endedPollRegex firstMatchInString:htmlString options:0 range:NSMakeRange(0, htmlString.length)];

if (!(match && match.numberOfRanges > 1)) {
// no useful match found
return finalString;
}

NSRange groupRange = [match rangeAtIndex:1];
NSString* replacementText;

if (repliedEvent) {
MXEvent* pollStartedEvent = [mxSession.store eventWithEventId:repliedEvent.relatesTo.eventId inRoom:repliedEvent.roomId];
replacementText = [MXEventContentPollStart modelFromJSON:pollStartedEvent.content].question;
}

if (replacementText == nil) {
replacementText = VectorL10n.pollTimelineReplyEndedPoll;
}

finalString = [htmlString stringByReplacingCharactersInRange:groupRange withString:replacementText];

return finalString;
}

- (void)postFormatMutableAttributedString:(NSMutableAttributedString*)mutableAttributedString
forEvent:(MXEvent*)event
andRepliedEvent:(MXEvent*)repliedEvent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,16 @@ private enum Constants {
static let expectedEditedHTML = "<mx-reply><blockquote><a href=\"https://matrix.to/#/someRoomId/repliedEventId\">In reply to</a> <a href=\"https://matrix.to/#/alice\">alice</a><br>Edited message</blockquote></mx-reply>Reply"
static let expectedEditedHTMLWithNewContent = "<mx-reply><blockquote><a href=\"https://matrix.to/#/someRoomId/repliedEventId\">In reply to</a> <a href=\"https://matrix.to/#/alice\">alice</a><br>New content</blockquote></mx-reply>Reply"
static let expectedEditedHTMLWithParsedItalic = "<mx-reply><blockquote><a href=\"https://matrix.to/#/someRoomId/repliedEventId\">In reply to</a> <a href=\"https://matrix.to/#/alice\">alice</a><br>New content</blockquote></mx-reply><em>Reply</em>"
static let expectedReplyToPollEndedEvent = "<mx-reply><blockquote><a href=\"https://matrix.to/#/someRoomId/repliedEventId\">In reply to</a> <a href=\"https://matrix.to/#/alice\">alice</a><br>Ended poll</blockquote></mx-reply>Reply"
}

class MXKEventFormatterTests: XCTestCase {
class MXKEventFormatterSwiftTests: XCTestCase {
func testBuildHTMLString() {
let formatter = MXKEventFormatter()
let repliedEvent = MXEvent()
let repliedEvent: MXEvent = .mockEvent(eventType: kMXEventTypeStringRoomMessage)
let event = MXEvent()
func buildHTML() -> String? { return formatter.buildHTMLString(for: event, inReplyTo: repliedEvent) }

// Initial setup.
repliedEvent.sender = "alice"
repliedEvent.roomId = Constants.roomId
repliedEvent.eventId = Constants.repliedEventId
repliedEvent.wireType = kMXEventTypeStringRoomMessage
repliedEvent.wireContent = [kMXMessageTypeKey: kMXMessageTypeText,
kMXMessageBodyKey: Constants.repliedEventBody]
event.sender = "bob"
event.wireType = kMXEventTypeStringRoomMessage
event.wireContent = [
Expand Down Expand Up @@ -73,4 +67,39 @@ class MXKEventFormatterTests: XCTestCase {
repliedEvent.wireContent[kMXMessageContentKeyNewContent] = nil
XCTAssertNil(buildHTML())
}

func testBuildHTMLStringWithPollEndedReply() {
let formatter = MXKEventFormatter()
let repliedEvent: MXEvent = .mockEvent(eventType: kMXEventTypeStringPollEnd, body: nil)

let event = MXEvent()
event.sender = "bob"
event.wireType = kMXEventTypeStringRoomMessage
event.wireContent = [
kMXMessageTypeKey: kMXMessageTypeText,
kMXMessageBodyKey: Constants.replyBody,
kMXEventRelationRelatesToKey: [kMXEventContentRelatesToKeyInReplyTo: ["event_id": Constants.repliedEventId]]
]

let formattedText = formatter.buildHTMLString(for: event, inReplyTo: repliedEvent)

XCTAssertEqual(formattedText, Constants.expectedReplyToPollEndedEvent)
}
}

private extension MXEvent {
static func mockEvent(eventType: String, body: String? = Constants.repliedEventBody) -> MXEvent {
let repliedEvent = MXEvent()
repliedEvent.sender = "alice"
repliedEvent.roomId = Constants.roomId
repliedEvent.eventId = Constants.repliedEventId
repliedEvent.wireType = eventType
repliedEvent.wireContent = [kMXMessageTypeKey: kMXMessageTypeText]

if let body = body {
repliedEvent.wireContent[kMXMessageBodyKey] = body
}

return repliedEvent
}
}
1 change: 1 addition & 0 deletions changelog.d/pr-7284.change
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Polls: render replies to poll events better.

0 comments on commit fdf1839

Please sign in to comment.