summaryrefslogtreecommitdiffstats
path: root/framework
diff options
context:
space:
mode:
Diffstat (limited to 'framework')
-rw-r--r--framework/qml/Icons.qml1
-rw-r--r--framework/qml/MailViewer.qml36
-rw-r--r--framework/qml/Messages.qml1
-rw-r--r--framework/src/domain/composercontroller.cpp66
-rw-r--r--framework/src/domain/composercontroller.h13
-rw-r--r--framework/src/domain/mime/mailtemplates.cpp36
-rw-r--r--framework/src/domain/mime/mailtemplates.h1
-rw-r--r--framework/src/domain/mime/tests/mailtemplatetest.cpp24
8 files changed, 137 insertions, 41 deletions
diff --git a/framework/qml/Icons.qml b/framework/qml/Icons.qml
index 3636a192..2afe840e 100644
--- a/framework/qml/Icons.qml
+++ b/framework/qml/Icons.qml
@@ -41,6 +41,7 @@ Item {
41 property string edit: "document-edit" 41 property string edit: "document-edit"
42 property string edit_inverted: "document-edit-inverted" 42 property string edit_inverted: "document-edit-inverted"
43 property string replyToSender: "mail-reply-sender" 43 property string replyToSender: "mail-reply-sender"
44 property string forward: "mail-forward"
44 property string outbox: "mail-folder-outbox" 45 property string outbox: "mail-folder-outbox"
45 property string outbox_inverted: "mail-folder-outbox-inverted" 46 property string outbox_inverted: "mail-folder-outbox-inverted"
46 property string copy: "edit-copy" 47 property string copy: "edit-copy"
diff --git a/framework/qml/MailViewer.qml b/framework/qml/MailViewer.qml
index 1a832470..f52e694d 100644
--- a/framework/qml/MailViewer.qml
+++ b/framework/qml/MailViewer.qml
@@ -370,24 +370,40 @@ Rectangle {
370 } 370 }
371 } 371 }
372 372
373 Kube.IconButton { 373 Grid {
374 visible: !model.trash 374 anchors {
375 anchors{
376 verticalCenter: parent.verticalCenter 375 verticalCenter: parent.verticalCenter
377 right: parent.right 376 right: parent.right
378 rightMargin: Kube.Units.largeSpacing 377 rightMargin: Kube.Units.largeSpacing
379 } 378 }
380 activeFocusOnTab: false 379 columns: 2
381 380
382 iconName: model.draft ? Kube.Icons.edit : Kube.Icons.replyToSender 381 Kube.IconButton {
383 onClicked: { 382 visible: !model.trash
384 if (model.draft) { 383 activeFocusOnTab: false
385 Kube.Fabric.postMessage(Kube.Messages.edit, {"mail": model.mail, "isDraft": model.draft}) 384
386 } else { 385 iconName: model.draft ? Kube.Icons.edit : Kube.Icons.replyToSender
387 Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail, "isDraft": model.draft}) 386 onClicked: {
387 if (model.draft) {
388 Kube.Fabric.postMessage(Kube.Messages.edit, {"mail": model.mail})
389 } else {
390 Kube.Fabric.postMessage(Kube.Messages.reply, {"mail": model.mail})
391 }
388 } 392 }
389 } 393 }
394
395 Kube.IconButton {
396 visible: !model.trash && !model.draft
397 activeFocusOnTab: false
398
399 iconName: Kube.Icons.forward
400 onClicked: {
401 Kube.Fabric.postMessage(Kube.Messages.forward, {"mail": model.mail})
402 }
403 }
404
390 } 405 }
406
391 } 407 }
392 Rectangle { 408 Rectangle {
393 anchors.fill: parent 409 anchors.fill: parent
diff --git a/framework/qml/Messages.qml b/framework/qml/Messages.qml
index 18db51ff..9df83863 100644
--- a/framework/qml/Messages.qml
+++ b/framework/qml/Messages.qml
@@ -44,6 +44,7 @@ Item {
44 property string search: "search" 44 property string search: "search"
45 property string synchronize: "synchronize" 45 property string synchronize: "synchronize"
46 property string reply: "reply" 46 property string reply: "reply"
47 property string forward: "forward"
47 property string edit: "edit" 48 property string edit: "edit"
48 property string compose: "compose" 49 property string compose: "compose"
49 property string sendOutbox: "sendOutbox" 50 property string sendOutbox: "sendOutbox"
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp
index 60e955ea..4bfc34ab 100644
--- a/framework/src/domain/composercontroller.cpp
+++ b/framework/src/domain/composercontroller.cpp
@@ -272,14 +272,8 @@ static QStringList getStringListFromAddresses(const KMime::Types::Mailbox::List
272void ComposerController::addAttachmentPart(KMime::Content *partToAttach) 272void ComposerController::addAttachmentPart(KMime::Content *partToAttach)
273{ 273{
274 QVariantMap map; 274 QVariantMap map;
275 if (partToAttach->contentType()->mimeType() == "multipart/digest" || 275 // May need special care for the multipart/digest MIME type
276 partToAttach->contentType()->mimeType() == "message/rfc822") { 276 map.insert("content", partToAttach->decodedContent());
277 // if it is a digest or a full message, use the encodedContent() of the attachment,
278 // which already has the proper headers
279 map.insert("content", partToAttach->encodedContent());
280 } else {
281 map.insert("content", partToAttach->decodedContent());
282 }
283 map.insert("mimetype", partToAttach->contentType()->mimeType()); 277 map.insert("mimetype", partToAttach->contentType()->mimeType());
284 278
285 QMimeDatabase db; 279 QMimeDatabase db;
@@ -337,7 +331,40 @@ void ComposerController::setMessage(const KMime::Message::Ptr &msg)
337 setExistingMessage(msg); 331 setExistingMessage(msg);
338} 332}
339 333
340void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) 334void ComposerController::loadDraft(const QVariant &message) {
335 loadMessage(message, [this] (const KMime::Message::Ptr &mail) {
336 mRemoveDraft = true;
337 setMessage(mail);
338 });
339}
340
341void ComposerController::loadReply(const QVariant &message) {
342 loadMessage(message, [this] (const KMime::Message::Ptr &mail) {
343 //Find all personal email addresses to exclude from reply
344 KMime::Types::AddrSpecList me;
345 auto list = static_cast<IdentitySelector*>(mIdentitySelector.data())->getAllAddresses();
346 for (const auto &a : list) {
347 KMime::Types::Mailbox mb;
348 mb.setAddress(a);
349 me << mb.addrSpec();
350 }
351
352 MailTemplates::reply(mail, [this] (const KMime::Message::Ptr &reply) {
353 //We assume reply
354 setMessage(reply);
355 }, me);
356 });
357}
358
359void ComposerController::loadForward(const QVariant &message) {
360 loadMessage(message, [this] (const KMime::Message::Ptr &mail) {
361 MailTemplates::forward(mail, [this] (const KMime::Message::Ptr &fwdMessage) {
362 setMessage(fwdMessage);
363 });
364 });
365}
366
367void ComposerController::loadMessage(const QVariant &message, std::function<void(const KMime::Message::Ptr&)> callback)
341{ 368{
342 using namespace Sink; 369 using namespace Sink;
343 using namespace Sink::ApplicationDomain; 370 using namespace Sink::ApplicationDomain;
@@ -346,8 +373,7 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
346 Q_ASSERT(msg); 373 Q_ASSERT(msg);
347 Query query(*msg); 374 Query query(*msg);
348 query.request<Mail::MimeMessage>(); 375 query.request<Mail::MimeMessage>();
349 Store::fetchOne<Mail>(query).then([this, loadAsDraft](const Mail &mail) { 376 Store::fetchOne<Mail>(query).then([this, callback](const Mail &mail) {
350 mRemoveDraft = loadAsDraft;
351 setExistingMail(mail); 377 setExistingMail(mail);
352 378
353 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); 379 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage());
@@ -355,23 +381,7 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
355 KMime::Message::Ptr mail(new KMime::Message); 381 KMime::Message::Ptr mail(new KMime::Message);
356 mail->setContent(mailData); 382 mail->setContent(mailData);
357 mail->parse(); 383 mail->parse();
358 if (loadAsDraft) { 384 callback(mail);
359 setMessage(mail);
360 } else {
361 //Find all personal email addresses to exclude from reply
362 KMime::Types::AddrSpecList me;
363 auto list = static_cast<IdentitySelector*>(mIdentitySelector.data())->getAllAddresses();
364 for (const auto &a : list) {
365 KMime::Types::Mailbox mb;
366 mb.setAddress(a);
367 me << mb.addrSpec();
368 }
369
370 MailTemplates::reply(mail, [this] (const KMime::Message::Ptr &reply) {
371 //We assume reply
372 setMessage(reply);
373 }, me);
374 }
375 } else { 385 } else {
376 qWarning() << "Retrieved empty message"; 386 qWarning() << "Retrieved empty message";
377 } 387 }
diff --git a/framework/src/domain/composercontroller.h b/framework/src/domain/composercontroller.h
index 0ace365b..ac83dfd3 100644
--- a/framework/src/domain/composercontroller.h
+++ b/framework/src/domain/composercontroller.h
@@ -81,12 +81,21 @@ class ComposerController : public Kube::Controller
81 KUBE_CONTROLLER_ACTION(saveAsDraft) 81 KUBE_CONTROLLER_ACTION(saveAsDraft)
82 82
83public: 83public:
84 enum LoadType {
85 Draft,
86 Reply,
87 Forward,
88 };
89 Q_ENUMS(LoadType);
90
84 explicit ComposerController(); 91 explicit ComposerController();
85 92
86 Completer *recipientCompleter() const; 93 Completer *recipientCompleter() const;
87 Selector *identitySelector() const; 94 Selector *identitySelector() const;
88 95
89 Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft); 96 Q_INVOKABLE void loadDraft(const QVariant &message);
97 Q_INVOKABLE void loadReply(const QVariant &message);
98 Q_INVOKABLE void loadForward(const QVariant &message);
90 99
91public slots: 100public slots:
92 virtual void clear() Q_DECL_OVERRIDE; 101 virtual void clear() Q_DECL_OVERRIDE;
@@ -95,6 +104,8 @@ private slots:
95 void findPersonalKey(); 104 void findPersonalKey();
96 105
97private: 106private:
107 void loadMessage(const QVariant &message, std::function<void(const KMime::Message::Ptr&)> callback);
108
98 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); 109 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName);
99 void setMessage(const QSharedPointer<KMime::Message> &msg); 110 void setMessage(const QSharedPointer<KMime::Message> &msg);
100 void addAttachmentPart(KMime::Content *partToAttach); 111 void addAttachmentPart(KMime::Content *partToAttach);
diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp
index 9af15e91..8e644b34 100644
--- a/framework/src/domain/mime/mailtemplates.cpp
+++ b/framework/src/domain/mime/mailtemplates.cpp
@@ -864,6 +864,34 @@ void MailTemplates::reply(const KMime::Message::Ptr &origMsg, const std::functio
864 }); 864 });
865} 865}
866 866
867void MailTemplates::forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback)
868{
869 KMime::Message::Ptr wrapperMsg(new KMime::Message);
870
871 wrapperMsg->to()->clear();
872 wrapperMsg->cc()->clear();
873
874 wrapperMsg->subject()->fromUnicodeString(forwardSubject(origMsg->subject()->asUnicodeString()), "utf-8");
875
876 const QByteArray refStr = getRefStr(origMsg);
877 if (!refStr.isEmpty()) {
878 wrapperMsg->references()->fromUnicodeString(QString::fromLocal8Bit(refStr), "utf-8");
879 }
880
881 KMime::Content* fwdAttachment = new KMime::Content;
882
883 fwdAttachment->contentDisposition()->setDisposition(KMime::Headers::CDinline);
884 fwdAttachment->contentType()->setMimeType("message/rfc822");
885 fwdAttachment->contentDisposition()->setFilename(origMsg->subject()->asUnicodeString() + ".eml");
886 // The mail was parsed in loadMessage before, so no need to assemble it
887 fwdAttachment->setBody(origMsg->encodedContent());
888
889 wrapperMsg->addContent(fwdAttachment);
890 wrapperMsg->assemble();
891
892 callback(wrapperMsg);
893}
894
867QString MailTemplates::plaintextContent(const KMime::Message::Ptr &msg) 895QString MailTemplates::plaintextContent(const KMime::Message::Ptr &msg)
868{ 896{
869 MimeTreeParser::ObjectTreeParser otp; 897 MimeTreeParser::ObjectTreeParser otp;
@@ -899,10 +927,14 @@ static KMime::Content *createAttachmentPart(const QByteArray &content, const QSt
899 } else { 927 } else {
900 part->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment); 928 part->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
901 } 929 }
930
902 part->contentType(true)->setMimeType(mimeType); 931 part->contentType(true)->setMimeType(mimeType);
903 part->contentType(true)->setName(name, "utf-8"); 932 part->contentType(true)->setName(name, "utf-8");
904 //Just always encode attachments base64 so it's safe for binary data 933 // Just always encode attachments base64 so it's safe for binary data,
905 part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64); 934 // except when it's another message
935 if(mimeType != "message/rfc822") {
936 part->contentTransferEncoding(true)->setEncoding(KMime::Headers::CEbase64);
937 }
906 part->setBody(content); 938 part->setBody(content);
907 return part; 939 return part;
908} 940}
diff --git a/framework/src/domain/mime/mailtemplates.h b/framework/src/domain/mime/mailtemplates.h
index 21efb5a0..9447e169 100644
--- a/framework/src/domain/mime/mailtemplates.h
+++ b/framework/src/domain/mime/mailtemplates.h
@@ -35,6 +35,7 @@ struct Attachment {
35namespace MailTemplates 35namespace MailTemplates
36{ 36{
37 void reply(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback, const KMime::Types::AddrSpecList &me = {}); 37 void reply(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback, const KMime::Types::AddrSpecList &me = {});
38 void forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback);
38 QString plaintextContent(const KMime::Message::Ptr &origMsg); 39 QString plaintextContent(const KMime::Message::Ptr &origMsg);
39 QString body(const KMime::Message::Ptr &msg, bool &isHtml); 40 QString body(const KMime::Message::Ptr &msg, bool &isHtml);
40 KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {}); 41 KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {});
diff --git a/framework/src/domain/mime/tests/mailtemplatetest.cpp b/framework/src/domain/mime/tests/mailtemplatetest.cpp
index 302fae95..8d044608 100644
--- a/framework/src/domain/mime/tests/mailtemplatetest.cpp
+++ b/framework/src/domain/mime/tests/mailtemplatetest.cpp
@@ -224,6 +224,30 @@ private slots:
224 QCOMPARE(result->cc()->addresses(), l); 224 QCOMPARE(result->cc()->addresses(), l);
225 } 225 }
226 226
227 void testForwardAsAttachment()
228 {
229 auto msg = readMail("plaintext.mbox");
230 KMime::Message::Ptr result;
231 MailTemplates::forward(msg, [&] (const KMime::Message::Ptr &r) {
232 result = r;
233 });
234 QTRY_VERIFY(result);
235 QCOMPARE(result->subject(false)->asUnicodeString(), {"FW: A random subject with alternative contenttype"});
236 QCOMPARE(result->to()->addresses(), {});
237 QCOMPARE(result->cc()->addresses(), {});
238
239 auto attachments = result->attachments();
240 QCOMPARE(attachments.size(), 1);
241 auto attachment = attachments[0];
242 QCOMPARE(attachment->contentDisposition(false)->disposition(), KMime::Headers::CDinline);
243 QCOMPARE(attachment->contentDisposition(false)->filename(), {"A random subject with alternative contenttype.eml"});
244 QVERIFY(attachment->bodyIsMessage());
245
246 attachment->parse();
247 auto origMsg = attachment->bodyAsMessage();
248 QCOMPARE(origMsg->subject(false)->asUnicodeString(), {"A random subject with alternative contenttype"});
249 }
250
227 void testCreatePlainMail() 251 void testCreatePlainMail()
228 { 252 {
229 QStringList to = {{"to@example.org"}}; 253 QStringList to = {{"to@example.org"}};