summary refs log tree commit diff
diff options
context:
space:
mode:
authorNicolas Werner <nicolas.werner@hotmail.de>2023-01-31 18:22:12 +0100
committerNicolas Werner <nicolas.werner@hotmail.de>2023-01-31 18:22:12 +0100
commit0c3d46795b589d9dfb2e6caa7f94db77fd5371bd (patch)
tree4d3bdc9cee84be37f51465e49ec38a9055452dd0
parentUncoditionally request keyframes (diff)
downloadnheko-0c3d46795b589d9dfb2e6caa7f94db77fd5371bd.tar.xz
Make single newlines cause a <br> by default
This should match what people expect from a chat application much
better. The biggest reason not to do this, is because some people might
paste markdown documents. For those people there is now a /cmark
command, which disables most of our extensions to cmark, including the
newline behaviour. There is a long discussion on the Fediverse and on
Github linked below.

Mastodon https://fosstodon.org/@deepbluev7/109771668066978726
fixes #757
-rw-r--r--src/CommandCompleter.cpp7
-rw-r--r--src/CommandCompleter.h1
-rw-r--r--src/UserDirectoryModel.cpp11
-rw-r--r--src/UserDirectoryModel.h8
-rw-r--r--src/Utils.cpp23
-rw-r--r--src/Utils.h2
-rw-r--r--src/timeline/InputBar.cpp10
-rw-r--r--src/timeline/InputBar.h1
8 files changed, 48 insertions, 15 deletions
diff --git a/src/CommandCompleter.cpp b/src/CommandCompleter.cpp
index 4cc61291..51eef4f5 100644
--- a/src/CommandCompleter.cpp
+++ b/src/CommandCompleter.cpp
@@ -73,6 +73,8 @@ CommandCompleter::data(const QModelIndex &index, int role) const
                 return QString("/rotate-megolm-session");
             case Md:
                 return QString("/md ");
+            case Cmark:
+                return QString("/cmark ");
             case Plain:
                 return QString("/plain ");
             case Rainbow:
@@ -140,6 +142,8 @@ CommandCompleter::data(const QModelIndex &index, int role) const
                 return tr("/rotate-megolm-session");
             case Md:
                 return tr("/md [message]");
+            case Cmark:
+                return tr("/cmark [message]");
             case Plain:
                 return tr("/plain [message]");
             case Rainbow:
@@ -206,6 +210,9 @@ CommandCompleter::data(const QModelIndex &index, int role) const
                 return tr("Rotate the current symmetric encryption key.");
             case Md:
                 return tr("Send a markdown formatted message (ignoring the global setting).");
+            case Cmark:
+                return tr(
+                  "Send a commonmark formatted message disabling most extensions compared to /md.");
             case Plain:
                 return tr("Send an unformatted message (ignoring the global setting).");
             case Rainbow:
diff --git a/src/CommandCompleter.h b/src/CommandCompleter.h
index 24242209..a3339c7a 100644
--- a/src/CommandCompleter.h
+++ b/src/CommandCompleter.h
@@ -40,6 +40,7 @@ public:
         ResetState,
         RotateMegolmSession,
         Md,
+        Cmark,
         Plain,
         Rainbow,
         RainbowMe,
diff --git a/src/UserDirectoryModel.cpp b/src/UserDirectoryModel.cpp
index 2c44df40..37753ad8 100644
--- a/src/UserDirectoryModel.cpp
+++ b/src/UserDirectoryModel.cpp
@@ -6,11 +6,13 @@
 
 #include "UserDirectoryModel.h"
 
+#include <QSharedPointer>
+
+#include <mtx/responses/users.hpp>
+
 #include "Cache.h"
 #include "Logging.h"
-#include <QSharedPointer>
 #include "MatrixClient.h"
-#include "mtx/responses/users.hpp"
 
 UserDirectoryModel::UserDirectoryModel(QObject *parent)
   : QAbstractListModel{parent}
@@ -49,7 +51,7 @@ UserDirectoryModel::fetchMore(const QModelIndex &)
 
     nhlog::net()->debug("Fetching users from mtxclient...");
     std::string searchTerm = userSearchString_;
-    searchingUsers_ = true;
+    searchingUsers_        = true;
     emit searchingUsersChanged();
     auto job = QSharedPointer<FetchUsersFromDirectoryJob>::create();
     connect(job.data(),
@@ -88,7 +90,8 @@ UserDirectoryModel::data(const QModelIndex &index, int role) const
 }
 
 void
-UserDirectoryModel::displaySearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm)
+UserDirectoryModel::displaySearchResults(std::vector<mtx::responses::User> results,
+                                         const std::string &searchTerm)
 {
     if (searchTerm != this->userSearchString_)
         return;
diff --git a/src/UserDirectoryModel.h b/src/UserDirectoryModel.h
index 87f8163c..cdceed46 100644
--- a/src/UserDirectoryModel.h
+++ b/src/UserDirectoryModel.h
@@ -18,11 +18,12 @@ class FetchUsersFromDirectoryJob final : public QObject
     Q_OBJECT
 public:
     explicit FetchUsersFromDirectoryJob(QObject *p = nullptr)
-    : QObject(p)
+      : QObject(p)
     {
     }
 signals:
-    void fetchedSearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
+    void
+    fetchedSearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
 };
 class UserDirectoryModel : public QAbstractListModel
 {
@@ -65,5 +66,6 @@ public slots:
     bool searchingUsers() const { return searchingUsers_; }
 
 private slots:
-    void displaySearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
+    void
+    displaySearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
 };
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 218edb62..649e9124 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -901,19 +901,24 @@ process_strikethrough(cmark_node *node)
     cmark_iter_free(iter);
 }
 QString
-utils::markdownToHtml(const QString &text, bool rainbowify_)
+utils::markdownToHtml(const QString &text, bool rainbowify_, bool noExtensions)
 {
     const auto str         = text.toUtf8();
     cmark_node *const node = cmark_parse_document(str.constData(), str.size(), CMARK_OPT_UNSAFE);
 
-    process_strikethrough(node);
-    process_spoilers(node);
+    if (!noExtensions) {
+        process_strikethrough(node);
+        process_spoilers(node);
 
-    if (rainbowify_) {
-        rainbowify(node);
+        if (rainbowify_) {
+            rainbowify(node);
+        }
     }
 
-    const char *tmp_buf = cmark_render_html(node, CMARK_OPT_UNSAFE);
+    const char *tmp_buf = cmark_render_html(
+      node,
+      // by default make single linebreaks <br> tags
+      noExtensions ? CMARK_OPT_UNSAFE : (CMARK_OPT_UNSAFE | CMARK_OPT_HARDBREAKS));
     // Copy the null terminated output buffer.
     std::string html(tmp_buf);
 
@@ -921,7 +926,11 @@ utils::markdownToHtml(const QString &text, bool rainbowify_)
     free((char *)tmp_buf);
     cmark_node_free(node);
 
-    auto result = linkifyMessage(escapeBlacklistedHtml(QString::fromStdString(html))).trimmed();
+    auto result = escapeBlacklistedHtml(QString::fromStdString(html)).trimmed();
+
+    if (!noExtensions) {
+        result = linkifyMessage(std::move(result)).trimmed();
+    }
 
     if (result.count(QStringLiteral("<p>")) == 1 && result.startsWith(QLatin1String("<p>")) &&
         result.endsWith(QLatin1String("</p>"))) {
diff --git a/src/Utils.h b/src/Utils.h
index e6d66f47..18a1cb78 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -276,7 +276,7 @@ linkifyMessage(const QString &body);
 
 //! Convert the input markdown text to html.
 QString
-markdownToHtml(const QString &text, bool rainbowify = false);
+markdownToHtml(const QString &text, bool rainbowify = false, bool noExtensions = false);
 
 //! Escape every html tag, that was not whitelisted
 QString
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b6355418..94944337 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -465,6 +465,14 @@ InputBar::message(const QString &msg, MarkdownOverride useMarkdown, bool rainbow
             text.formatted_body = "";
         else
             text.format = "org.matrix.custom.html";
+    } else if (useMarkdown == MarkdownOverride::CMARK) {
+        // disable all markdown extensions
+        text.formatted_body = utils::markdownToHtml(msg, rainbowify, true).toStdString();
+        // keep everything as it was
+        text.body = msg.trimmed().toStdString();
+
+        // always send formatted
+        text.format = "org.matrix.custom.html";
     }
 
     text.relations = generateRelations();
@@ -802,6 +810,8 @@ InputBar::command(const QString &command, QString args)
         cache::dropOutboundMegolmSession(room->roomId().toStdString());
     } else if (command == QLatin1String("md")) {
         message(args, MarkdownOverride::ON);
+    } else if (command == QLatin1String("cmark")) {
+        message(args, MarkdownOverride::CMARK);
     } else if (command == QLatin1String("plain")) {
         message(args, MarkdownOverride::OFF);
     } else if (command == QLatin1String("rainbow")) {
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index eb261b02..32e3deee 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -40,6 +40,7 @@ enum class MarkdownOverride
     NOT_SPECIFIED, // no override set
     ON,
     OFF,
+    CMARK,
 };
 
 class InputVideoSurface final : public QAbstractVideoSurface