On Samstag, 6. Oktober 2012 15:00:28 CEST Jan Kundrát wrote:
> - *bold*, /italic/ and _underline_ formatting is lost when 
> replying (they appear as "bold, italic and underline" in the 
> response, without any extra formatting)
Fixed, was related to the 2nd "issue" (and i didn't care much about preserving 
markup in quoted text)

> - TB renders them as "<b>*bold*</b>" (ie. including the star, 
> underscore or a slash) while Trojita shows them as 
> "<b>bold</b>". I like TB's way more.
Frankly: I don't like that at all (actually "accidentally" happened on first 
try and I fixed that as fast as possible =)
The current implementation allows for both via css.
-> should the user be allowed to provide a custom stylesheet 
("~/.config/flaska.net/trojita_mail.css") or should we generate it from some 
settings?

> - the "On Freitag, 5. Oktober 2012 00:39:26 CEST Jan Kundrát 
> wrote:" is not quoted in the response
"Fixed." (first line doesn't follow '\n' but the new code is much different 
anyway)

> - there's an extra newline between the added "on ..., ... 
> wrote" and the quoted message
I removed it, but it actually was intentionally there.
Not sure whether i like the editor w/o.

> I haven't tested replying to HTML e-mails.
I've clicked "reply" on a mail from reviewboard, but it seems the quotes are in 
this case lost.
I assume in the end you'd like interpreting the blockquote tags as quotemarks?

> the old-school plaintext stuff, supports some fancy features 
> like interactive collapsing of big quotations by mouse clicks
That would require JS, would it? In that case i could rather resist.
(JS in a push medium sounds like a /very/ bad idea)


> transforming the text/plain input data into HTML (including the 
> <blockquote> stuff with appropriate styling) is the way to go. 
Well, see the patch =)

> It's going to be tricky as it will likely require 
> QWebView::selectedHtml()
This direction? No.

> One thing which is badly missing are unit tests for this
Like a test mail?
A automatic unittest would require to move the markup stuff into an extra 
function, returning a string, then pass it a testcontent, remove all expected 
tagged items and check that the result is empty.
Where do you keep unit tests and where do you want the function to reside?

> Harmattan version of Trojita, which means that the code should 
> be moved into some common utility library. I'm not sure where.
Aha. Feel free to locate a position and move it there :-P

> I'm not particularly happy for the loadFinished -> 
> slotMarkupPlainText connection,
Me neither, but we'd have to add the entire protocol stuff using 
QNetworkRequest replacing QWebView::load()
I don't mind that, but felt the change to be out of scope (to massive impact) 
in this regard.

There's an outstanding issue that worries me, being the editor.
One can either wrap on a static width (tried, sucks), not at all (sucks more) 
or wordwrap on the widget size (default, not ideal either?)
-> should we wrap the text on sending it or seek to "fix" wrapping in 
QTextEdit? (to support older mail clients)

Cheers,
Thomas
From 1d3b9ce54a0002377a855fd3ceb12c1e18c2a8b6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20L=C3=BCbking?= <[email protected]>
Date: Sun, 7 Oct 2012 21:08:46 +0200
Subject: [PATCH 1/3] more complex markup

---
 src/Gui/SimplePartWidget.cpp | 106 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 94 insertions(+), 12 deletions(-)

diff --git a/src/Gui/SimplePartWidget.cpp b/src/Gui/SimplePartWidget.cpp
index 79dae96..9cab042 100644
--- a/src/Gui/SimplePartWidget.cpp
+++ b/src/Gui/SimplePartWidget.cpp
@@ -59,22 +59,104 @@ void SimplePartWidget::slotMarkupPlainText() {
 
     static const QRegExp link("(https*://[;/?:@=&$\\-_.+!'(),0-9a-zA-Z%#]*)");
     static const QRegExp mail("([a-zA-Z0-9\\.\\-_]*@[a-zA-Z0-9\\.\\-_]*)");
-    static QString intro("([\\s\\(\\[\\{])");
+    static QString intro("(^|[\\s\\(\\[\\{])");
     static QString extro("([\\s\\),;.\\]\\}])");
     static const QRegExp bold(intro + "\\*(\\S*)\\*" + extro);
     static const QRegExp italic(intro + "/(\\S*)/" + extro);
     static const QRegExp underline(intro + "_(\\S*)_" + extro);
-    QString content = page()->mainFrame()->toHtml();
-    content.replace("&gt;", "§gt;");
-    content.replace("&lt;", "§lt;");
-    content.replace(link, "<a href=\"\\1\">\\1</a>");
-    content.replace(mail, "<a href=\"mailto:\\1\">\\1</a>");
-    content.replace("§gt;", "&gt;");
-    content.replace("§lt;", "&lt;");
-    content.replace(bold, "\\1<b>\\2</b>\\3");
-    content.replace(italic, "\\1<i>\\2</i>\\3");
-    content.replace(underline, "\\1<u>\\2</u>\\3");
-    page()->mainFrame()->setHtml(content);
+    // TODO: include some extern css here?
+    QString stylesheet(
+        "pre{word-wrap: break-word; white-space: pre-wrap;}"
+        ".markup{color:transparent;font-size:0px;}"
+        ".quotemarks{color:transparent;font-size:0px;}"
+        "blockquote{font-size:90%;padding:0;margin:4pt;margin-left:2em;}"
+    );
+    static QString htmlHeader("<html><head><style type=\"text/css\"><!--" + stylesheet + "--></style></head><body><pre>");
+    static QString htmlFooter("\n</pre></body></html>");
+
+    // Processing:
+    // the plain text is split into lines
+    // leading quotemarks are counted and stripped
+    // next, the line is marked up (*bold*, /italic/, _underline_ and active link support)
+    // if the last line ended with a space, the result is appended, otherwise canonical quotemarkes are
+    // prepended and the line appended to the markup list (see http://tools.ietf.org/html/rfc3676)
+    // whenever the quote level grows, a <blockquote> is opened and closed when it shrinks
+
+    int quoteLevel = 0;
+    QStringList plain(page()->mainFrame()->toPlainText().split('\n'));
+    QStringList markup(htmlHeader);
+    for (int i = 0; i < plain.count(); ++i) {
+        QString *line = const_cast<QString*>(&plain.at(i));
+
+        // ignore empty lines
+        if (line->isEmpty()) {
+            markup << *line;
+            continue;
+        }
+        // determine quotelevel
+        int cQuoteLevel = 0;
+        if (line->at(0) == '>') {
+            int j = 1;
+            cQuoteLevel = 1;
+            while (j < line->length() && (line->at(j) == '>' || line->at(j) == ' '))
+                cQuoteLevel += line->at(j++) == '>';
+        }
+        // strip quotemarks
+        if (cQuoteLevel) {
+            static QRegExp quotemarks("^[>\\s]*");
+            line->remove(quotemarks);
+        }
+        // markup *bold*, /italic/, _underline_ and active links
+        line->replace(">", "§gt;"); // we cannot escape them after we added actual tags
+        line->replace("<", "§lt;"); // and we cannot use the amps "&" since we'll have to escape it to &amp; later on as well
+        line->replace(link, "<a href=\"\\1\">\\1</a>");
+        line->replace(mail, "<a href=\"mailto:\\1\">\\1</a>");
+#define MARKUP(_item_) "<span class=\"markup\">"#_item_"</span>"
+        if (line->contains("italic"))
+            qDebug() << *line;
+        line->replace(bold, "\\1<b>" MARKUP(*) "\\2" MARKUP(*) "</b>\\3");
+        line->replace(italic, "\\1<i>" MARKUP(/) "\\2" MARKUP(/) "</i>\\3");
+        line->replace(underline, "\\1<u>" MARKUP(_) "\\2" MARKUP(_) "</u>\\3");
+#undef MARKUP
+        line->replace("&", "&amp;");
+        line->replace("§gt;", "&gt;");
+        line->replace("§lt;", "&lt;");
+
+        // if this is a non floating new line, prepend canonical quotemarks
+        if (cQuoteLevel && !(cQuoteLevel == quoteLevel && markup.last().endsWith(' '))) {
+            QString quotemarks("<span class=\"quotemarks\">");
+            for (int i = 0; i < cQuoteLevel; ++i)
+                quotemarks += "&gt;";
+            quotemarks += " </span>";
+            line->prepend(quotemarks);
+        }
+
+        // handle quotelevel depending blockquotes
+        cQuoteLevel -= quoteLevel;
+        quoteLevel += cQuoteLevel; // quoteLevel is now what cQuoteLevel was two lines before
+        while (cQuoteLevel > 0) {
+            line->prepend("<blockquote>");
+            --cQuoteLevel;
+        }
+        while (cQuoteLevel < 0) {
+            line->prepend("</blockquote>");
+            ++cQuoteLevel;
+        }
+        // appaned or join the line
+        if (markup.last().endsWith(' '))
+            markup.last().append(*line);
+        else
+            markup << *line;
+    }
+    // close open blockquotes
+    // (bottom quoters, we're unfortunately -yet- not permittet to shoot them, so we need to deal with them ;-)
+    QString quoteCloser("\n");
+    while (quoteLevel > 0) {
+        quoteCloser.append("</blockquote>");
+        --quoteLevel;
+    }
+    // and finally set the marked up page.
+    page()->mainFrame()->setHtml(markup.join("\n") + quoteCloser + htmlFooter);
 }
 
 void SimplePartWidget::slotFileNameRequested(QString *fileName)
-- 
1.7.12.2

From 60a3345f0512c9edbc361092271d774c4e37b6b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20L=C3=BCbking?= <[email protected]>
Date: Sun, 7 Oct 2012 21:09:11 +0200
Subject: [PATCH 2/3] format=float wrapping

---
 src/Gui/MessageView.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 51 insertions(+), 4 deletions(-)

diff --git a/src/Gui/MessageView.cpp b/src/Gui/MessageView.cpp
index 22a8c7e..8120a71 100644
--- a/src/Gui/MessageView.cpp
+++ b/src/Gui/MessageView.cpp
@@ -306,16 +306,63 @@ QString MessageView::headerText()
 QString MessageView::quoteText() const
 {
     if (const AbstractPartWidget *w = dynamic_cast<const AbstractPartWidget *>(viewer)) {
-        QString quote = w->quoteMe();
-        quote.replace('\n', "\n> ");
+        QStringList quote;
+        QStringList lines = w->quoteMe().split('\n');
+        for (int i = 0; i < lines.count(); ++i) {
+            // rewrap - we need to keep the quotes at < 79 chars, yet the grow with every level
+            QString *line = const_cast<QString*>(&lines.at(i));
+            if (line->length() < 79-2) {
+                // this line is short enough, prepend quote mark and continue
+                if (line->isEmpty() || line->at(0) == '>')
+                    line->prepend(">");
+                else
+                    line->prepend("> ");
+                quote << *line;
+                continue;
+            }
+            // long line -> needs to be wrapped
+            // 1st, detect the quote depth and eventually stript the quotes from the line
+            int quoteLevel(0);
+            int contentStart(0);
+            if (line->at(0) == '>') {
+                int j = 1;
+                quoteLevel = 1;
+                while (j < line->length() && line->at(j) == '>')
+                    ++quoteLevel;
+                contentStart = quoteLevel;
+                if (j < line->length() && line->at(j) == ' ')
+                    ++contentStart;
+            }
+
+            // 2nd, build a qute string
+            QString quotemarks;
+            for (int i = 0; i < quoteLevel; ++i)
+                quotemarks += ">";
+            quotemarks += "> ";
+
+            // 3rd, wrap the line, prepend the quotemarks to each line and add it to the quote text
+            int space(contentStart), lastSpace(contentStart), pos(contentStart), length(0);
+            while (pos < line->length()) {
+                if (line->at(pos) == ' ')
+                    space = pos+1;
+                ++length;
+                if (length > 65-quotemarks.length() && space != lastSpace) {
+                    // wrap
+                    quote << quotemarks + line->mid(lastSpace, space - lastSpace);
+                    lastSpace = space;
+                    length = pos - space;
+                }
+                ++pos;
+            }
+            quote << quotemarks + line->mid(lastSpace);
+        }
         const Imap::Message::Envelope &e = envelope();
         QString sender;
         if (!e.from.isEmpty())
             sender = e.from.at(0).name;
         if (e.from.isEmpty())
             sender = tr("you");
-        quote.prepend(tr("On %1 %2 wrote:\n\n").arg(e.date.toLocalTime().toString(Qt::SystemLocaleLongDate)).arg(sender));
-        return quote;
+        return tr("On %1 %2 wrote:\n").arg(e.date.toLocalTime().toString(Qt::SystemLocaleLongDate)).arg(sender) + quote.join("\n");
     }
     return QString();
 }
-- 
1.7.12.2

Reply via email to