diff --git a/src/Utils.cpp b/src/Utils.cpp
index 8a3b9e4c..32d435f0 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -17,6 +17,7 @@
#include <QTextDocument>
#include <QXmlStreamReader>
+#include <QTextBoundaryFinder>
#include <cmath>
#include <variant>
@@ -468,11 +469,92 @@ utils::escapeBlacklistedHtml(const QString &rawStr)
}
QString
-utils::markdownToHtml(const QString &text)
-{
- const auto str = text.toUtf8();
- const char *tmp_buf = cmark_markdown_to_html(str.constData(), str.size(), CMARK_OPT_UNSAFE);
+utils::markdownToHtml(const QString &text, bool rainbowify)
+{
+ const auto str = text.toUtf8();
+ cmark_node *const node =
+ cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE);
+
+ if (rainbowify) {
+ // create iterator over node
+ cmark_iter *iter = cmark_iter_new(node);
+
+ cmark_event_type ev_type;
+
+ // First loop to get total text length
+ int textLen = 0;
+ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+ cmark_node *cur = cmark_iter_get_node(iter);
+ // only text nodes (no code or semilar)
+ if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+ continue;
+ // count up by length of current node's text
+ QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
+ QString(cmark_node_get_literal(cur)));
+ while (tbf.toNextBoundary() != -1)
+ textLen++;
+ }
+
+ // create new iter to start over
+ cmark_iter_free(iter);
+ iter = cmark_iter_new(node);
+
+ // Second loop to rainbowify
+ int charIdx = 0;
+ while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
+ cmark_node *cur = cmark_iter_get_node(iter);
+ // only text nodes (no code or semilar)
+ if (cmark_node_get_type(cur) != CMARK_NODE_TEXT)
+ continue;
+
+ // get text in current node
+ QString nodeText(cmark_node_get_literal(cur));
+ // create buffer to append rainbow text to
+ QString buf;
+ int boundaryStart = 0;
+ int boundaryEnd = 0;
+ // use QTextBoundaryFinder to iterate ofer graphemes
+ QTextBoundaryFinder tbf(QTextBoundaryFinder::BoundaryType::Grapheme,
+ nodeText);
+ while ((boundaryEnd = tbf.toNextBoundary()) != -1) {
+ // Split text to get current char
+ auto curChar =
+ nodeText.midRef(boundaryStart, boundaryEnd - boundaryStart);
+ boundaryStart = boundaryEnd;
+ // Don't rainbowify whitespaces
+ if (curChar.trimmed().isEmpty()) {
+ buf.append(curChar.toString());
+ continue;
+ }
+
+ // get correct color for char index
+ auto color = QColor::fromHsvF(1.0 / textLen * charIdx, 1.0, 1.0);
+ // format color for HTML
+ auto colorString = color.name(QColor::NameFormat::HexRgb);
+ // create HTML element for current char
+ auto curCharColored = QString("<font color=\"%0\">%1</font>")
+ .arg(colorString)
+ .arg(curChar);
+ // append colored HTML element to buffer
+ buf.append(curCharColored);
+
+ charIdx++;
+ }
+
+ // create HTML_INLINE node to prevent HTML from being escaped
+ auto htmlNode = cmark_node_new(CMARK_NODE_HTML_INLINE);
+ // set content of HTML node to buffer contents
+ cmark_node_set_literal(htmlNode, buf.toUtf8().data());
+ // replace current node with HTML node
+ cmark_node_replace(cur, htmlNode);
+ // free memory of old node
+ cmark_node_free(cur);
+ }
+
+ cmark_iter_free(iter);
+ }
+ const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE);
// Copy the null terminated output buffer.
std::string html(tmp_buf);
diff --git a/src/Utils.h b/src/Utils.h
index f8ead68c..37c1baba 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -268,7 +268,7 @@ linkifyMessage(const QString &body);
//! Convert the input markdown text to html.
QString
-markdownToHtml(const QString &text);
+markdownToHtml(const QString &text, bool rainbowify = false);
//! Escape every html tag, that was not whitelisted
QString
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b45827f6..53296efd 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -13,6 +13,7 @@
#include <QStandardPaths>
#include <QUrl>
+#include <QRegularExpression>
#include <mtx/responses/common.hpp>
#include <mtx/responses/media.hpp>
@@ -203,7 +204,7 @@ InputBar::send()
auto wasEdit = !room->edit().isEmpty();
if (text().startsWith('/')) {
- int command_end = text().indexOf(' ');
+ int command_end = text().indexOf(QRegularExpression("\\s"));
if (command_end == -1)
command_end = text().size();
auto name = text().mid(1, command_end - 1);
@@ -255,7 +256,7 @@ InputBar::openFileSelection()
}
void
-InputBar::message(QString msg, MarkdownOverride useMarkdown)
+InputBar::message(QString msg, MarkdownOverride useMarkdown, bool rainbowify)
{
mtx::events::msg::Text text = {};
text.body = msg.trimmed().toStdString();
@@ -263,7 +264,7 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown)
if ((ChatPage::instance()->userSettings()->markdown() &&
useMarkdown == MarkdownOverride::NOT_SPECIFIED) ||
useMarkdown == MarkdownOverride::ON) {
- text.formatted_body = utils::markdownToHtml(msg).toStdString();
+ text.formatted_body = utils::markdownToHtml(msg, rainbowify).toStdString();
// Remove markdown links by completer
text.body =
msg.trimmed().replace(conf::strings::matrixToMarkdownLink, "\\1").toStdString();
@@ -526,6 +527,8 @@ InputBar::command(QString command, QString args)
message(args, MarkdownOverride::ON);
} else if (command == "plain") {
message(args, MarkdownOverride::OFF);
+ } else if (command == "rainbow") {
+ message(args, MarkdownOverride::ON, true);
}
}
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index acd9e22c..f7a60488 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -53,7 +53,9 @@ public slots:
void updateState(int selectionStart, int selectionEnd, int cursorPosition, QString text);
void openFileSelection();
bool uploading() const { return uploading_; }
- void message(QString body, MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED);
+ void message(QString body,
+ MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
+ bool rainbowify = false);
private slots:
void startTyping();
|