Improve markdown formatting.
This commit is contained in:
@@ -259,7 +259,7 @@ Item {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
visible: notificationCount > 0 && highlightCount == 0
|
visible: notificationCount > 0 && highlightCount == 0
|
||||||
color: "white"
|
color: MPalette.background
|
||||||
text: notificationCount
|
text: notificationCount
|
||||||
leftPadding: 12
|
leftPadding: 12
|
||||||
rightPadding: 12
|
rightPadding: 12
|
||||||
|
|||||||
@@ -380,24 +380,9 @@ Control {
|
|||||||
var PREFIX_ME = '/me '
|
var PREFIX_ME = '/me '
|
||||||
var PREFIX_NOTICE = '/notice '
|
var PREFIX_NOTICE = '/notice '
|
||||||
var PREFIX_RAINBOW = '/rainbow '
|
var PREFIX_RAINBOW = '/rainbow '
|
||||||
var PREFIX_HTML = '/html '
|
|
||||||
var PREFIX_MARKDOWN = '/md '
|
|
||||||
|
|
||||||
if (isReply) {
|
var messageEventType = RoomMessageEvent.Text
|
||||||
currentRoom.sendReply(replyUser.id, replyEventID, replyContent, text)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (text.indexOf(PREFIX_RAINBOW) === 0) {
|
||||||
text = text.substr(PREFIX_RAINBOW.length)
|
text = text.substr(PREFIX_RAINBOW.length)
|
||||||
|
|
||||||
@@ -406,23 +391,38 @@ Control {
|
|||||||
for (var i = 0; i < text.length; i++) {
|
for (var i = 0; i < text.length; i++) {
|
||||||
parsedText = parsedText + "<font color='" + rainbowColor[i % rainbowColor.length] + "'>" + text.charAt(i) + "</font>"
|
parsedText = parsedText + "<font color='" + rainbowColor[i % rainbowColor.length] + "'>" + text.charAt(i) + "</font>"
|
||||||
}
|
}
|
||||||
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
|
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text, replyEventID)
|
||||||
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)
|
|
||||||
return
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ Settings {
|
|||||||
property bool darkTheme
|
property bool darkTheme
|
||||||
|
|
||||||
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
|
property string fontFamily: "Roboto,Noto Sans,Noto Color Emoji"
|
||||||
|
|
||||||
|
property bool markdownFormatting: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "csapi/rooms.h"
|
#include "csapi/rooms.h"
|
||||||
#include "csapi/typing.h"
|
#include "csapi/typing.h"
|
||||||
#include "events/accountdataevents.h"
|
#include "events/accountdataevents.h"
|
||||||
|
#include "events/roommessageevent.h"
|
||||||
#include "events/typingevent.h"
|
#include "events/typingevent.h"
|
||||||
#include "jobs/downloadfilejob.h"
|
#include "jobs/downloadfilejob.h"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
#include <QTextDocument>
|
||||||
|
|
||||||
#include "html.h"
|
#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",
|
|
||||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" + id() + "/" +
|
|
||||||
eventId + "\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
|
||||||
userId + "\">" + userId + "</a><br>" + replyContent +
|
|
||||||
"</blockquote></mx-reply>" + sendContent}};
|
|
||||||
postJson("m.room.message", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
QDateTime SpectralRoom::lastActiveTime() {
|
QDateTime SpectralRoom::lastActiveTime() {
|
||||||
if (timelineSize() == 0)
|
if (timelineSize() == 0)
|
||||||
return QDateTime();
|
return QDateTime();
|
||||||
@@ -234,29 +218,6 @@ QVariantList SpectralRoom::getUsers(const QString& prefix) {
|
|||||||
return matchedList;
|
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) {
|
QUrl SpectralRoom::urlToMxcUrl(QUrl mxcUrl) {
|
||||||
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
|
return DownloadFileJob::makeRequestUrl(connection()->homeserver(), mxcUrl);
|
||||||
}
|
}
|
||||||
@@ -336,3 +297,131 @@ void SpectralRoom::removeLocalAlias(const QString& alias) {
|
|||||||
|
|
||||||
setLocalAliases(aliases);
|
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",
|
||||||
|
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
|
||||||
|
id() + "/" + replyEventId +
|
||||||
|
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
||||||
|
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
||||||
|
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
||||||
|
"</blockquote></mx-reply>" + 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",
|
||||||
|
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
|
||||||
|
id() + "/" + replyEventId +
|
||||||
|
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
||||||
|
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
||||||
|
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
||||||
|
"</blockquote></mx-reply>" + html}};
|
||||||
|
postJson("m.room.message",
|
||||||
|
json); // TODO: Support other message event types?
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Room::postHtmlMessage(text, html, type);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include <events/encryptionevent.h>
|
||||||
#include <events/redactionevent.h>
|
#include <events/redactionevent.h>
|
||||||
#include <events/roomavatarevent.h>
|
#include <events/roomavatarevent.h>
|
||||||
#include <events/roomcreateevent.h>
|
#include <events/roomcreateevent.h>
|
||||||
#include <events/roommemberevent.h>
|
#include <events/roommemberevent.h>
|
||||||
#include <events/roommessageevent.h>
|
#include <events/roommessageevent.h>
|
||||||
#include <events/simplestateevents.h>
|
#include <events/simplestateevents.h>
|
||||||
#include <events/encryptionevent.h>
|
|
||||||
|
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
@@ -90,8 +90,6 @@ class SpectralRoom : public Room {
|
|||||||
|
|
||||||
Q_INVOKABLE QVariantList getUsers(const QString& prefix);
|
Q_INVOKABLE QVariantList getUsers(const QString& prefix);
|
||||||
|
|
||||||
Q_INVOKABLE QString postMarkdownText(const QString& markdown);
|
|
||||||
|
|
||||||
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
|
Q_INVOKABLE QUrl urlToMxcUrl(QUrl mxcUrl);
|
||||||
|
|
||||||
QUrl avatarUrl() const {
|
QUrl avatarUrl() const {
|
||||||
@@ -287,6 +285,8 @@ class SpectralRoom : public Room {
|
|||||||
void onAddNewTimelineEvents(timeline_iter_t from) override;
|
void onAddNewTimelineEvents(timeline_iter_t from) override;
|
||||||
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
void onAddHistoricalTimelineEvents(rev_iter_t from) override;
|
||||||
|
|
||||||
|
static QString markdownToHTML(const QString& plaintext);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void countChanged();
|
void countChanged();
|
||||||
|
|
||||||
@@ -302,10 +302,16 @@ class SpectralRoom : public Room {
|
|||||||
void acceptInvitation();
|
void acceptInvitation();
|
||||||
void forget();
|
void forget();
|
||||||
void sendTypingNotification(bool isTyping);
|
void sendTypingNotification(bool isTyping);
|
||||||
void sendReply(QString userId,
|
void postArbitaryMessage(const QString& text,
|
||||||
QString eventId,
|
MessageEventType type = MessageEventType::Text,
|
||||||
QString replyContent,
|
const QString& replyEventId = "");
|
||||||
QString sendContent);
|
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 changeAvatar(QUrl localFile);
|
||||||
void addLocalAlias(const QString& alias);
|
void addLocalAlias(const QString& alias);
|
||||||
void removeLocalAlias(const QString& alias);
|
void removeLocalAlias(const QString& alias);
|
||||||
|
|||||||
Reference in New Issue
Block a user