diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml
index 646229c1d..9284f3c07 100644
--- a/imports/Spectral/Panel/RoomListPanel.qml
+++ b/imports/Spectral/Panel/RoomListPanel.qml
@@ -259,7 +259,7 @@ Item {
Label {
visible: notificationCount > 0 && highlightCount == 0
- color: "white"
+ color: MPalette.background
text: notificationCount
leftPadding: 12
rightPadding: 12
diff --git a/imports/Spectral/Panel/RoomPanelInput.qml b/imports/Spectral/Panel/RoomPanelInput.qml
index cae34ff9e..65139d88c 100644
--- a/imports/Spectral/Panel/RoomPanelInput.qml
+++ b/imports/Spectral/Panel/RoomPanelInput.qml
@@ -380,24 +380,9 @@ Control {
var PREFIX_ME = '/me '
var PREFIX_NOTICE = '/notice '
var PREFIX_RAINBOW = '/rainbow '
- var PREFIX_HTML = '/html '
- var PREFIX_MARKDOWN = '/md '
- if (isReply) {
- currentRoom.sendReply(replyUser.id, replyEventID, replyContent, text)
- return
- }
+ var messageEventType = RoomMessageEvent.Text
- if (text.indexOf(PREFIX_ME) === 0) {
- text = text.substr(PREFIX_ME.length)
- currentRoom.postMessage(text, RoomMessageEvent.Emote)
- return
- }
- if (text.indexOf(PREFIX_NOTICE) === 0) {
- text = text.substr(PREFIX_NOTICE.length)
- currentRoom.postMessage(text, RoomMessageEvent.Notice)
- return
- }
if (text.indexOf(PREFIX_RAINBOW) === 0) {
text = text.substr(PREFIX_RAINBOW.length)
@@ -406,23 +391,38 @@ Control {
for (var i = 0; i < text.length; i++) {
parsedText = parsedText + "" + text.charAt(i) + ""
}
- currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
- return
- }
- if (text.indexOf(PREFIX_HTML) === 0) {
- text = text.substr(PREFIX_HTML.length)
- var re = new RegExp("<.*?>")
- var plainText = text.replace(re, "")
- currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text)
- return
- }
- if (text.indexOf(PREFIX_MARKDOWN) === 0) {
- text = text.substr(PREFIX_MARKDOWN.length)
- currentRoom.postMarkdownText(text)
+ currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text, replyEventID)
return
}
- currentRoom.postPlainText(text)
+ if (text.indexOf(PREFIX_ME) === 0) {
+ text = text.substr(PREFIX_ME.length)
+ messageEventType = RoomMessageEvent.Emote
+ } else if (text.indexOf(PREFIX_NOTICE) === 0) {
+ text = text.substr(PREFIX_NOTICE.length)
+ messageEventType = RoomMessageEvent.Notice
+ }
+
+ if (MSettings.markdownFormatting) {
+ currentRoom.postArbitaryMessage(text, messageEventType, replyEventID)
+ } else {
+ currentRoom.postPlainMessage(text, messageEventType, replyEventID)
+ }
+ }
+ }
+
+ MaterialIcon {
+ Layout.alignment: Qt.AlignVCenter
+
+ icon: "\ue165"
+ font.pixelSize: 16
+ color: MPalette.foreground
+ opacity: MSettings.markdownFormatting ? 1 : 0.3
+
+ MouseArea {
+ anchors.fill: parent
+
+ onClicked: MSettings.markdownFormatting = !MSettings.markdownFormatting
}
}
diff --git a/imports/Spectral/Setting/Setting.qml b/imports/Spectral/Setting/Setting.qml
index 3dd86287b..6aee66ad3 100644
--- a/imports/Spectral/Setting/Setting.qml
+++ b/imports/Spectral/Setting/Setting.qml
@@ -10,4 +10,6 @@ Settings {
property bool darkTheme
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
+
+ property bool markdownFormatting: true
}
diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp
index 5aa154c09..7e4b0efa0 100644
--- a/src/spectralroom.cpp
+++ b/src/spectralroom.cpp
@@ -10,6 +10,7 @@
#include "csapi/rooms.h"
#include "csapi/typing.h"
#include "events/accountdataevents.h"
+#include "events/roommessageevent.h"
#include "events/typingevent.h"
#include "jobs/downloadfilejob.h"
@@ -18,6 +19,7 @@
#include
#include
#include
+#include
#include "html.h"
@@ -169,24 +171,6 @@ void SpectralRoom::countChanged() {
}
}
-void SpectralRoom::sendReply(QString userId,
- QString eventId,
- QString replyContent,
- QString sendContent) {
- QJsonObject json{
- {"msgtype", "m.text"},
- {"body", "> <" + userId + "> " + replyContent + "\n\n" + sendContent},
- {"format", "org.matrix.custom.html"},
- {"m.relates_to",
- QJsonObject{{"m.in_reply_to", QJsonObject{{"event_id", eventId}}}}},
- {"formatted_body",
- "In reply to " + userId + "
" + replyContent +
- "
" + sendContent}};
- postJson("m.room.message", json);
-}
-
QDateTime SpectralRoom::lastActiveTime() {
if (timelineSize() == 0)
return QDateTime();
@@ -234,29 +218,6 @@ QVariantList SpectralRoom::getUsers(const QString& prefix) {
return matchedList;
}
-QString SpectralRoom::postMarkdownText(const QString& markdown) {
- unsigned char* sequence =
- (unsigned char*)qstrdup(markdown.toUtf8().constData());
- qint64 length = strlen((char*)sequence);
-
- hoedown_renderer* renderer =
- hoedown_html_renderer_new(HOEDOWN_HTML_USE_XHTML, 32);
- hoedown_extensions extensions = (hoedown_extensions)(
- (HOEDOWN_EXT_BLOCK | HOEDOWN_EXT_SPAN | HOEDOWN_EXT_MATH_EXPLICIT) &
- ~HOEDOWN_EXT_QUOTE);
- hoedown_document* document = hoedown_document_new(renderer, extensions, 32);
- hoedown_buffer* html = hoedown_buffer_new(length);
- hoedown_document_render(document, html, sequence, length);
- QString result = QString::fromUtf8((char*)html->data, html->size);
-
- free(sequence);
- hoedown_buffer_free(html);
- hoedown_document_free(document);
- hoedown_html_renderer_free(renderer);
-
- return postHtmlText(markdown, result);
-}
-
QUrl SpectralRoom::urlToMxcUrl(QUrl mxcUrl) {
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
}
@@ -336,3 +297,131 @@ void SpectralRoom::removeLocalAlias(const QString& alias) {
setLocalAliases(aliases);
}
+
+QString SpectralRoom::markdownToHTML(const QString& markdown) {
+ unsigned char* sequence =
+ (unsigned char*)qstrdup(markdown.toUtf8().constData());
+ qint64 length = strlen((char*)sequence);
+
+ hoedown_renderer* renderer =
+ hoedown_html_renderer_new(HOEDOWN_HTML_USE_XHTML, 32);
+ hoedown_extensions extensions = (hoedown_extensions)(
+ (HOEDOWN_EXT_BLOCK | HOEDOWN_EXT_SPAN | HOEDOWN_EXT_MATH_EXPLICIT) &
+ ~HOEDOWN_EXT_QUOTE);
+ hoedown_document* document = hoedown_document_new(renderer, extensions, 32);
+ hoedown_buffer* html = hoedown_buffer_new(length);
+ hoedown_document_render(document, html, sequence, length);
+ QString result = QString::fromUtf8((char*)html->data, html->size);
+
+ free(sequence);
+ hoedown_buffer_free(html);
+ hoedown_document_free(document);
+ hoedown_html_renderer_free(renderer);
+
+ return result;
+}
+
+void SpectralRoom::postArbitaryMessage(const QString& text,
+ MessageEventType type,
+ const QString& replyEventId) {
+ auto parsedHTML = markdownToHTML(text);
+ bool isRichText = Qt::mightBeRichText(parsedHTML);
+
+ if (isRichText) { // Markdown
+ postHtmlMessage(text, parsedHTML, type, replyEventId);
+ } else { // Plain text
+ postPlainMessage(text, type, replyEventId);
+ }
+}
+
+QString msgTypeToString(MessageEventType msgType) {
+ switch (msgType) {
+ case MessageEventType::Text:
+ return "m.text";
+ case MessageEventType::File:
+ return "m.file";
+ case MessageEventType::Audio:
+ return "m.audio";
+ case MessageEventType::Emote:
+ return "m.emote";
+ case MessageEventType::Image:
+ return "m.image";
+ case MessageEventType::Video:
+ return "m.video";
+ case MessageEventType::Notice:
+ return "m.notice";
+ case MessageEventType::Location:
+ return "m.location";
+ default:
+ return "m.text";
+ }
+}
+
+void SpectralRoom::postPlainMessage(const QString& text,
+ MessageEventType type,
+ const QString& replyEventId) {
+ bool isReply = !replyEventId.isEmpty();
+ const auto replyIt = findInTimeline(replyEventId);
+ if (replyIt == timelineEdge())
+ isReply = false;
+
+ if (isReply) {
+ const auto& replyEvt = **replyIt;
+
+ QJsonObject json{{"msgtype", msgTypeToString(type)},
+ {"body", "> <" + replyEvt.senderId() + "> " +
+ eventToString(replyEvt) + "\n\n" + text},
+ {"format", "org.matrix.custom.html"},
+ {"m.relates_to",
+ QJsonObject{{"m.in_reply_to",
+ QJsonObject{{"event_id", replyEventId}}}}},
+ {"formatted_body",
+ "In reply to " + replyEvt.senderId() +
+ "
" + eventToString(replyEvt, Qt::RichText) +
+ "
" + text.toHtmlEscaped()}};
+ postJson("m.room.message",
+ json); // TODO: Support other message event types?
+
+ return;
+ }
+
+ Room::postMessage(text, type);
+}
+
+void SpectralRoom::postHtmlMessage(const QString& text,
+ const QString& html,
+ MessageEventType type,
+ const QString& replyEventId) {
+ bool isReply = !replyEventId.isEmpty();
+ const auto replyIt = findInTimeline(replyEventId);
+ if (replyIt == timelineEdge())
+ isReply = false;
+
+ if (isReply) {
+ const auto& replyEvt = **replyIt;
+
+ QJsonObject json{{"msgtype", msgTypeToString(type)},
+ {"body", "> <" + replyEvt.senderId() + "> " +
+ eventToString(replyEvt) + "\n\n" + text},
+ {"format", "org.matrix.custom.html"},
+ {"m.relates_to",
+ QJsonObject{{"m.in_reply_to",
+ QJsonObject{{"event_id", replyEventId}}}}},
+ {"formatted_body",
+ "In reply to " + replyEvt.senderId() +
+ "
" + eventToString(replyEvt, Qt::RichText) +
+ "
" + html}};
+ postJson("m.room.message",
+ json); // TODO: Support other message event types?
+
+ return;
+ }
+
+ Room::postHtmlMessage(text, html, type);
+}
diff --git a/src/spectralroom.h b/src/spectralroom.h
index 729b7c1d6..e4885b8f5 100644
--- a/src/spectralroom.h
+++ b/src/spectralroom.h
@@ -8,13 +8,13 @@
#include
#include
+#include
#include
#include
#include
#include
#include
#include
-#include
using namespace QMatrixClient;
@@ -90,8 +90,6 @@ class SpectralRoom : public Room {
Q_INVOKABLE QVariantList getUsers(const QString& prefix);
- Q_INVOKABLE QString postMarkdownText(const QString& markdown);
-
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
QUrl avatarUrl() const {
@@ -287,6 +285,8 @@ class SpectralRoom : public Room {
void onAddNewTimelineEvents(timeline_iter_t from) override;
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
+ static QString markdownToHTML(const QString& plaintext);
+
private slots:
void countChanged();
@@ -302,10 +302,16 @@ class SpectralRoom : public Room {
void acceptInvitation();
void forget();
void sendTypingNotification(bool isTyping);
- void sendReply(QString userId,
- QString eventId,
- QString replyContent,
- QString sendContent);
+ void postArbitaryMessage(const QString& text,
+ MessageEventType type = MessageEventType::Text,
+ const QString& replyEventId = "");
+ void postPlainMessage(const QString& text,
+ MessageEventType type = MessageEventType::Text,
+ const QString& replyEventId = "");
+ void postHtmlMessage(const QString& text,
+ const QString& html,
+ MessageEventType type = MessageEventType::Text,
+ const QString& replyEventId = "");
void changeAvatar(QUrl localFile);
void addLocalAlias(const QString& alias);
void removeLocalAlias(const QString& alias);