Improve Text Handling
Improve the handling of text both when sending and receiving. The main feature is to fix the linked bug (and a host of others that are unreported but similar) which is caused by the fact that we don't properly clean html. This mr does that as per the matrix spec https://spec.matrix.org/v1.5/client-server-api/#mroommessage-msgtypes. So any disallowed tags or attributes are removed and it does the special handling for certain attributes. Additionally the functions are also designed to cover any other text formatting required, particularly fro received strings. The receive side is covered by 2 functions `handleRecieveRichText` and `handleRecievePlainText`. The rich/plain in the function name refers to the output type not the input type (both can take plain and rich input), so `handleRecieveRichText` is called to get a string suitable to go in a rich text control and `handleRecievePlainText` for a plain control. The functions also handle the following some of which was previously handled by `eventToString` in `NeoChatRoom`: - Strip and reply from the string - Format any user mentions - Linkify links in plain strings - Handle mxc urls in rich text (uses the new `room->makeMediaUrl` functionality from libQuotient) - `handleRecievePlainText` also deals with markup making `NeoChatRoom->subtitle` redundant There is also an extensive test suite which defines the behaviour and the best way to review this is probably to look at the tests and decide whether you agree with the expected output given the inputs and/or if there is any missing behaviour. The final aim especially with the test suite is to give us a framework to make further updates in the future easier and hopefully prevent a new feature breaking old behaviour with the tests. BUG: 463932 \ BUG: 466330 \ BUG: 466930
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
#endif
|
||||
#include "filetransferpseudojob.h"
|
||||
#include "stickerevent.h"
|
||||
#include "utils.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KIO/Job>
|
||||
@@ -257,10 +257,11 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
||||
return false;
|
||||
}
|
||||
|
||||
QString NeoChatRoom::lastEventToString() const
|
||||
QString NeoChatRoom::lastEventToString(Qt::TextFormat format, bool stripNewlines) const
|
||||
{
|
||||
if (auto event = lastEvent()) {
|
||||
return roomMembername(event->senderId()) + (event->isStateEvent() ? " " : ": ") + eventToString(*event);
|
||||
return roomMembername(event->senderId()) + (event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": "))
|
||||
+ eventToString(*event, format, stripNewlines);
|
||||
}
|
||||
return QLatin1String("");
|
||||
}
|
||||
@@ -329,45 +330,6 @@ QDateTime NeoChatRoom::lastActiveTime()
|
||||
return messageEvents().rbegin()->get()->originTimestamp();
|
||||
}
|
||||
|
||||
QString NeoChatRoom::subtitleText()
|
||||
{
|
||||
static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> ");
|
||||
static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} ");
|
||||
static const QRegularExpression newlines("(\r\n\t|\n|\r\t|\r\n)");
|
||||
static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1");
|
||||
static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
|
||||
static const QRegularExpression strike1("~~(.*)~~");
|
||||
static const QRegularExpression strike2("~(.*)~");
|
||||
static const QRegularExpression del("<del>(.*)</del>");
|
||||
static const QRegularExpression multileLineCode("```([^```]+)```");
|
||||
static const QRegularExpression singleLinecode("`([^`]+)`");
|
||||
QString subtitle = lastEventToString().size() == 0 ? topic() : lastEventToString();
|
||||
|
||||
subtitle
|
||||
// replace blockquote, i.e. '> text'
|
||||
.replace(blockquote, " ")
|
||||
// replace headings, i.e. "# text"
|
||||
.replace(heading, " ")
|
||||
// replace newlines
|
||||
.replace(newlines, " ")
|
||||
// replace '**text**' and '__text__'
|
||||
.replace(bold1, "\\2")
|
||||
// replace '*text*' and '_text_'
|
||||
.replace(bold2, "\\2")
|
||||
// replace '~~text~~'
|
||||
.replace(strike1, "\\1")
|
||||
// replace '~text~'
|
||||
.replace(strike2, "\\1")
|
||||
// replace '<del>text</del>'
|
||||
.replace(del, "\\1")
|
||||
// replace '```code```'
|
||||
.replace(multileLineCode, "\\1")
|
||||
// replace '`code`'
|
||||
.replace(singleLinecode, "\\1");
|
||||
|
||||
return subtitle.size() > 0 ? subtitle : QStringLiteral(" ");
|
||||
}
|
||||
|
||||
int NeoChatRoom::savedTopVisibleIndex() const
|
||||
{
|
||||
return firstDisplayedMarker() == historyEdge() ? 0 : int(firstDisplayedMarker() - messageEvents().rbegin());
|
||||
@@ -451,7 +413,7 @@ QString NeoChatRoom::avatarMediaId() const
|
||||
return {};
|
||||
}
|
||||
|
||||
QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format, bool removeReply) const
|
||||
QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format, bool stripNewlines) const
|
||||
{
|
||||
const bool prettyPrint = (format == Qt::RichText);
|
||||
|
||||
@@ -462,53 +424,43 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
return visit(
|
||||
#endif
|
||||
evt,
|
||||
[this, prettyPrint, removeReply](const RoomMessageEvent &e) {
|
||||
[this, format, stripNewlines](const RoomMessageEvent &e) {
|
||||
using namespace MessageEventContent;
|
||||
|
||||
// 1. prettyPrint/HTML
|
||||
if (prettyPrint && e.hasTextContent() && e.mimeType().name() != "text/plain") {
|
||||
auto htmlBody = static_cast<const TextContent *>(e.content())->body;
|
||||
if (removeReply) {
|
||||
htmlBody.remove(utils::removeRichReplyRegex);
|
||||
}
|
||||
htmlBody.replace(utils::userPillRegExp, R"(<b class="user-pill">\1</b>)");
|
||||
htmlBody.replace(utils::strikethroughRegExp, "<s>\\1</s>");
|
||||
|
||||
auto url = connection()->homeserver();
|
||||
auto base = url.scheme() + QStringLiteral("://") + url.host() + (url.port() != -1 ? ':' + QString::number(url.port()) : QString());
|
||||
htmlBody.replace(utils::mxcImageRegExp, QStringLiteral(R"(<img \1 src="%1/_matrix/media/r0/download/\2/\3" \4 > )").arg(base));
|
||||
|
||||
return htmlBody;
|
||||
}
|
||||
TextHandler textHandler;
|
||||
|
||||
if (e.hasFileContent()) {
|
||||
auto fileCaption = e.content()->fileInfo()->originalName.toHtmlEscaped();
|
||||
auto fileCaption = e.content()->fileInfo()->originalName;
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = prettyPrint ? Quotient::prettyPrint(e.plainBody()) : e.plainBody();
|
||||
fileCaption = e.plainBody();
|
||||
} else if (e.content()->fileInfo()->originalName != e.plainBody()) {
|
||||
fileCaption = e.plainBody() + " | " + fileCaption;
|
||||
}
|
||||
return !fileCaption.isEmpty() ? fileCaption : i18n("a file");
|
||||
textHandler.setData(fileCaption);
|
||||
return !fileCaption.isEmpty() ? textHandler.handleRecievePlainText() : i18n("a file");
|
||||
}
|
||||
|
||||
// 2. prettyPrint/text 3. plainText/HTML 4. plainText/text
|
||||
QString plainBody;
|
||||
if (e.hasTextContent() && e.content() && e.mimeType().name() == "text/plain") { // 2/4
|
||||
plainBody = static_cast<const TextContent *>(e.content())->body;
|
||||
} else { // 3
|
||||
plainBody = e.plainBody();
|
||||
QString body;
|
||||
if (e.hasTextContent() && e.content()) {
|
||||
body = static_cast<const TextContent *>(e.content())->body;
|
||||
} else {
|
||||
body = e.plainBody();
|
||||
}
|
||||
|
||||
if (prettyPrint) {
|
||||
if (removeReply) {
|
||||
plainBody.remove(utils::removeReplyRegex);
|
||||
}
|
||||
return Quotient::prettyPrint(plainBody);
|
||||
textHandler.setData(body);
|
||||
|
||||
Qt::TextFormat inputFormat;
|
||||
if (e.mimeType().name() == "text/plain") {
|
||||
inputFormat = Qt::PlainText;
|
||||
} else {
|
||||
inputFormat = Qt::RichText;
|
||||
}
|
||||
if (removeReply) {
|
||||
return plainBody.remove(utils::removeReplyRegex);
|
||||
|
||||
if (format == Qt::RichText) {
|
||||
return textHandler.handleRecieveRichText(inputFormat, this, &e, stripNewlines);
|
||||
} else {
|
||||
return textHandler.handleRecievePlainText(inputFormat, stripNewlines);
|
||||
}
|
||||
return plainBody;
|
||||
},
|
||||
[](const StickerEvent &e) {
|
||||
return e.body();
|
||||
|
||||
Reference in New Issue
Block a user