From c639a939f485b10ff5b5c40a8b7e62024467dafa Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sat, 13 Apr 2019 20:05:35 +0200 Subject: [PATCH 01/16] mu4e (ical): Fix indentation & typos --- mu4e/mu4e-draft.el | 18 +++++++++--------- mu4e/mu4e-vars.el | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/mu4e/mu4e-draft.el b/mu4e/mu4e-draft.el index d340d6fa..3b7679c1 100644 --- a/mu4e/mu4e-draft.el +++ b/mu4e/mu4e-draft.el @@ -413,15 +413,15 @@ You can append flags." Replying-to-self is special; in that case, the To and Cc fields will be the same as in the original." (let* ((reply-to-self (mu4e-message-contact-field-matches-me origmsg :from)) - (recipnum - (+ (length (mu4e~draft-create-to-lst origmsg)) - (length (mu4e~draft-create-cc-lst origmsg t)))) - ;; reply-to-self implies reply-all - (reply-all (or reply-to-self (mu4e~draft-reply-all-p origmsg))) - (old-msgid (plist-get origmsg :message-id)) - (subject - (concat mu4e~draft-reply-prefix - (message-strip-subject-re (or (plist-get origmsg :subject) ""))))) + (recipnum + (+ (length (mu4e~draft-create-to-lst origmsg)) + (length (mu4e~draft-create-cc-lst origmsg t)))) + ;; reply-to-self implies reply-all + (reply-all (or reply-to-self (mu4e~draft-reply-all-p origmsg))) + (old-msgid (plist-get origmsg :message-id)) + (subject (concat mu4e~draft-reply-prefix + (message-strip-subject-re + (or (plist-get origmsg :subject) ""))))) (concat (mu4e~draft-header "From" (or (mu4e~draft-from-construct) "")) (mu4e~draft-header "Reply-To" mu4e-compose-reply-to-address) diff --git a/mu4e/mu4e-vars.el b/mu4e/mu4e-vars.el index 8704ae86..afae182d 100644 --- a/mu4e/mu4e-vars.el +++ b/mu4e/mu4e-vars.el @@ -941,9 +941,9 @@ This before new headers are displayed, to clear the current headers buffer. See `mu4e~proc-filter' for the format.") (defvar mu4e-compose-func 'mu4e~compose-handler - "Function called for each comoose message received. -Ie. the original message that is used as basis for composing a -new message (ie., either a reply or a forward); the function is + "Function called for each compose message received. +I.e., the original message that is used as basis for composing a +new message (i.e., either a reply or a forward); the function is passed msg and a symbol (either reply or forward). See `mu4e~proc-filter' for the format of .") From 1078fee2c5fc67bdb8c2aab2c8082b0d9b17ac78 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sat, 13 Apr 2019 00:53:36 +0200 Subject: [PATCH 02/16] mu4e (ical): Allow to reply to icalendar invitations Fixes https://github.com/djcb/mu/issues/994 --- mu4e/Makefile.am | 1 + mu4e/mu4e-draft.el | 19 +++++---- mu4e/mu4e-icalendar.el | 92 ++++++++++++++++++++++++++++++++++++++++++ mu4e/mu4e-vars.el | 8 ++++ mu4e/mu4e-view.el | 3 +- 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 mu4e/mu4e-icalendar.el diff --git a/mu4e/Makefile.am b/mu4e/Makefile.am index ebccf26c..b84bb487 100644 --- a/mu4e/Makefile.am +++ b/mu4e/Makefile.am @@ -28,6 +28,7 @@ dist_lisp_LISP= \ mu4e-contrib.el \ mu4e-draft.el \ mu4e-headers.el \ + mu4e-icalendar.el \ mu4e-lists.el \ mu4e-main.el \ mu4e-mark.el \ diff --git a/mu4e/mu4e-draft.el b/mu4e/mu4e-draft.el index 3b7679c1..2b221644 100644 --- a/mu4e/mu4e-draft.el +++ b/mu4e/mu4e-draft.el @@ -413,14 +413,17 @@ You can append flags." Replying-to-self is special; in that case, the To and Cc fields will be the same as in the original." (let* ((reply-to-self (mu4e-message-contact-field-matches-me origmsg :from)) - (recipnum - (+ (length (mu4e~draft-create-to-lst origmsg)) - (length (mu4e~draft-create-cc-lst origmsg t)))) - ;; reply-to-self implies reply-all - (reply-all (or reply-to-self (mu4e~draft-reply-all-p origmsg))) - (old-msgid (plist-get origmsg :message-id)) - (subject (concat mu4e~draft-reply-prefix - (message-strip-subject-re + (recipnum + (+ (length (mu4e~draft-create-to-lst origmsg)) + (length (mu4e~draft-create-cc-lst origmsg t)))) + ;; reply-to-self implies reply-all + (reply-all (or reply-to-self + (eq mu4e-compose-reply-recipients 'all) + (and (not (eq mu4e-compose-reply-recipients 'sender)) + (mu4e~draft-reply-all-p origmsg)))) + (old-msgid (plist-get origmsg :message-id)) + (subject (concat mu4e~draft-reply-prefix + (message-strip-subject-re (or (plist-get origmsg :subject) ""))))) (concat (mu4e~draft-header "From" (or (mu4e~draft-from-construct) "")) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el new file mode 100644 index 00000000..84f5d58a --- /dev/null +++ b/mu4e/mu4e-icalendar.el @@ -0,0 +1,92 @@ +;;; mu4e-icalendar.el --- reply to iCalendar meeting requests + +;;; Commentary: + +;; To install: +;; (require 'gnus-icalendar) +;; (gnus-icalendar-setup) + +;; to enable optional iCalendar->Org sync functionality +;; NOTE: both the capture file and the headline(s) inside must already exist +;; (setq gnus-icalendar-org-capture-file "~/org/notes.org") +;; (setq gnus-icalendar-org-capture-headline '("Calendar")) +;; (gnus-icalendar-org-setup) + +(require 'mu4e) +(require 'gnus-icalendar) + +(eval-when-compile (require 'cl)) + + +(defun mu4e-icalendar-setup () + (gnus-icalendar-setup) + (cl-defmethod gnus-icalendar-event:inline-reply-buttons :around + ((event gnus-icalendar-event) handle) + (if (and (boundp 'mu4e~view-rendering) + (gnus-icalendar-event:rsvp event)) + `(("Accept" mu4e-icalendar-reply (,handle accepted ,event)) + ("Tentative" mu4e-icalendar-reply (,handle tentative ,event)) + ("Decline" mu4e-icalendar-reply (,handle declined ,event))) + (cl-call-next-method event handle)))) + +(defun mu4e-icalendar-reply (data) + "Reply to a text/calendar event." + ;; Based on `gnus-icalendar-reply'. + (let* ((handle (car data)) + (status (cadr data)) + (event (caddr data)) + (reply (gnus-icalendar-with-decoded-handle handle + (gnus-icalendar-event-reply-from-buffer + (current-buffer) status (gnus-icalendar-identities)))) + (msg (mu4e-message-at-point 'noerror))) + + (when reply + (cl-labels + ((fold-icalendar-buffer + () + (goto-char (point-min)) + (while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t) + (replace-match "\\1\n \\2") + (goto-char (line-beginning-position))))) + (let ((subject (concat (capitalize (symbol-name status)) + ": " (gnus-icalendar-event:summary event)))) + + (with-current-buffer (get-buffer-create gnus-icalendar-reply-bufname) + (delete-region (point-min) (point-max)) + (insert reply) + (fold-icalendar-buffer) + (mu4e-icalendar-reply-with-buffer msg subject (buffer-name))) + + ;; Back in article buffer + (setq-local gnus-icalendar-reply-status status) + (when gnus-icalendar-org-enabled-p + (gnus-icalendar--update-org-event event status) + ;; refresh article buffer to update the reply status + (with-current-buffer mu4e~headers-buffer-name + (mu4e-headers-rerun-search)))))))) + +(defun mu4e~icalendar-delete-citation () + (delete-region (point-min) (point-max))) + +(defun mu4e-icalendar-reply-with-buffer (original-msg subject buffer-name) + (let ((message-signature nil)) + (let ((mu4e-compose-cite-function #'mu4e~icalendar-delete-citation) + (mu4e-sent-messages-behavior 'delete) + (mu4e-compose-reply-recipients 'sender)) + ;; FIXME: only reply to the original sender (do not ask) + (mu4e~compose-handler 'reply original-msg)) + (message-goto-body) + (insert "\n\n") + (mml-insert-multipart "alternative") + (mml-insert-empty-tag 'part 'type "text/plain") + (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=UTF-8") + (message-goto-subject) + (delete-region (line-beginning-position) (line-end-position)) + (insert "Subject: " subject) +; (message-send-and-exit) + )) + + + +(provide 'mu4e-icalendar) +;;; mu4e-icalendar.el ends here diff --git a/mu4e/mu4e-vars.el b/mu4e/mu4e-vars.el index afae182d..5a0dd0f8 100644 --- a/mu4e/mu4e-vars.el +++ b/mu4e/mu4e-vars.el @@ -405,6 +405,14 @@ predicate function. A value of nil keeps all the addresses." (repeat string)) :group 'mu4e-compose) +(defcustom mu4e-compose-reply-recipients 'ask + "Which recipients to use when replying to a message. +May be 'ask, 'all, 'sender." + :type '(choice ask + all + sender) + :group 'mu4e-compose) + (defcustom mu4e-compose-reply-to-address nil "The Reply-To address. Useful when this is not equal to the From: address." diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index 0da6e0ad..3f4bcc21 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -381,7 +381,8 @@ article-mode." gnus-summary-buffer (get-buffer-create " *appease-gnus*") gnus-original-article-buffer (current-buffer)) (run-hooks 'gnus-article-decode-hook) - (let ((max-specpdl-size mu4e-view-max-specpdl-size)) + (let ((mu4e~view-rendering t) ; customize gnus in mu4e + (max-specpdl-size mu4e-view-max-specpdl-size)) (gnus-article-prepare-display)) (mu4e-view-mode) (setq mu4e~view-message msg) From 2e676b6c11e23fe76ec9b8ca46c01b9a17501eb9 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sat, 13 Apr 2019 23:37:06 +0200 Subject: [PATCH 03/16] mu4e (ical): Insert a text summary of the reply .ics file This is important because the users of some email clients (such as Outlook Wep Application) cannot read .ics files. See e.g., https://answers.microsoft.com/en-us/office/forum/office_2010-outlook/how-do-i-open-ics-files-in-outlook-web-access/c765c07e-20ae-44b5-baa4-1d4f94fdfbeb --- mu4e/mu4e-icalendar.el | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 84f5d58a..c66f2255 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -48,14 +48,18 @@ (while (re-search-forward "^\\(.\\{72\\}\\)\\(.+\\)$" nil t) (replace-match "\\1\n \\2") (goto-char (line-beginning-position))))) - (let ((subject (concat (capitalize (symbol-name status)) - ": " (gnus-icalendar-event:summary event)))) - (with-current-buffer (get-buffer-create gnus-icalendar-reply-bufname) - (delete-region (point-min) (point-max)) - (insert reply) - (fold-icalendar-buffer) - (mu4e-icalendar-reply-with-buffer msg subject (buffer-name))) + (with-current-buffer (get-buffer-create gnus-icalendar-reply-bufname) + (delete-region (point-min) (point-max)) + (insert reply) + (fold-icalendar-buffer) + (let* ((subject (concat (capitalize (symbol-name status)) + ": " (gnus-icalendar-event:summary event))) + (reply-event (gnus-icalendar-event-from-buffer + (buffer-name) mu4e-user-mail-address-list)) + (body (gnus-icalendar-event->gnus-calendar reply-event + status))) + (mu4e-icalendar-reply-with-buffer msg subject body (buffer-name)))) ;; Back in article buffer (setq-local gnus-icalendar-reply-status status) @@ -63,12 +67,12 @@ (gnus-icalendar--update-org-event event status) ;; refresh article buffer to update the reply status (with-current-buffer mu4e~headers-buffer-name - (mu4e-headers-rerun-search)))))))) + (mu4e-headers-rerun-search))))))) (defun mu4e~icalendar-delete-citation () (delete-region (point-min) (point-max))) -(defun mu4e-icalendar-reply-with-buffer (original-msg subject buffer-name) +(defun mu4e-icalendar-reply-with-buffer (original-msg subject body buffer-name) (let ((message-signature nil)) (let ((mu4e-compose-cite-function #'mu4e~icalendar-delete-citation) (mu4e-sent-messages-behavior 'delete) @@ -77,8 +81,8 @@ (mu4e~compose-handler 'reply original-msg)) (message-goto-body) (insert "\n\n") + (insert body) (mml-insert-multipart "alternative") - (mml-insert-empty-tag 'part 'type "text/plain") (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=UTF-8") (message-goto-subject) (delete-region (line-beginning-position) (line-end-position)) From 121bb002789ac4be47de9258ac0cbce7eb8a6cee Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sun, 14 Apr 2019 01:23:50 +0200 Subject: [PATCH 04/16] mu4e (ical): Make sure all user emails known to mu4e are used for ical events --- mu4e/mu4e-view.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mu4e/mu4e-view.el b/mu4e/mu4e-view.el index 3f4bcc21..b695e068 100644 --- a/mu4e/mu4e-view.el +++ b/mu4e/mu4e-view.el @@ -382,7 +382,9 @@ article-mode." gnus-original-article-buffer (current-buffer)) (run-hooks 'gnus-article-decode-hook) (let ((mu4e~view-rendering t) ; customize gnus in mu4e - (max-specpdl-size mu4e-view-max-specpdl-size)) + (max-specpdl-size mu4e-view-max-specpdl-size) + (gnus-icalendar-additional-identities + mu4e-user-mail-address-list)) (gnus-article-prepare-display)) (mu4e-view-mode) (setq mu4e~view-message msg) From 14d5b9db6ec4e875d010e60aeb557088e72156a6 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Wed, 17 Apr 2019 23:48:58 +0200 Subject: [PATCH 05/16] mu4e (ical): Make sure the organizer is the person replied to --- mu4e/mu4e-icalendar.el | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index c66f2255..2acd0579 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -53,13 +53,7 @@ (delete-region (point-min) (point-max)) (insert reply) (fold-icalendar-buffer) - (let* ((subject (concat (capitalize (symbol-name status)) - ": " (gnus-icalendar-event:summary event))) - (reply-event (gnus-icalendar-event-from-buffer - (buffer-name) mu4e-user-mail-address-list)) - (body (gnus-icalendar-event->gnus-calendar reply-event - status))) - (mu4e-icalendar-reply-with-buffer msg subject body (buffer-name)))) + (mu4e-icalendar-reply-ical msg event status (buffer-name))) ;; Back in article buffer (setq-local gnus-icalendar-reply-status status) @@ -72,21 +66,29 @@ (defun mu4e~icalendar-delete-citation () (delete-region (point-min) (point-max))) -(defun mu4e-icalendar-reply-with-buffer (original-msg subject body buffer-name) +(defun mu4e-icalendar-reply-ical (original-msg event status buffer-name) (let ((message-signature nil)) (let ((mu4e-compose-cite-function #'mu4e~icalendar-delete-citation) (mu4e-sent-messages-behavior 'delete) (mu4e-compose-reply-recipients 'sender)) - ;; FIXME: only reply to the original sender (do not ask) (mu4e~compose-handler 'reply original-msg)) + ;; Make sure the recipient is the organizer + (let ((organizer (gnus-icalendar-event:organizer event))) + (unless (string= organizer "") + (message-goto-to) + (delete-region (line-beginning-position) (line-end-position)) + (insert "To: " organizer))) (message-goto-body) (insert "\n\n") - (insert body) + (let ((reply-event (gnus-icalendar-event-from-buffer + buffer-name mu4e-user-mail-address-list))) + (insert (gnus-icalendar-event->gnus-calendar reply-event status))) (mml-insert-multipart "alternative") (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=UTF-8") (message-goto-subject) (delete-region (line-beginning-position) (line-end-position)) - (insert "Subject: " subject) + (insert "Subject: " (capitalize (symbol-name status)) + ": " (gnus-icalendar-event:summary event)) ; (message-send-and-exit) )) From 5ccd58b40b99b598c5c0be0715274ea7ebf00e64 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 11:57:39 +0200 Subject: [PATCH 06/16] mu4e (ical): Update installation instructions --- mu4e/mu4e-icalendar.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 2acd0579..78ef29d7 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -3,10 +3,10 @@ ;;; Commentary: ;; To install: -;; (require 'gnus-icalendar) -;; (gnus-icalendar-setup) +;; (require 'mu4e-icalendar) +;; (mu4e-icalendar-setup) -;; to enable optional iCalendar->Org sync functionality +;; To enable optional iCalendar->Org sync functionality ;; NOTE: both the capture file and the headline(s) inside must already exist ;; (setq gnus-icalendar-org-capture-file "~/org/notes.org") ;; (setq gnus-icalendar-org-capture-headline '("Calendar")) From 5573e7bed0cfa2b2165ac7e7456e90c3d3454dff Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 16:05:08 +0200 Subject: [PATCH 07/16] mu4e (ical): Use a text/plain alternative when replying to ical events --- mu4e/mu4e-icalendar.el | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 78ef29d7..a5bef3e6 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -80,11 +80,13 @@ (insert "To: " organizer))) (message-goto-body) (insert "\n\n") + (mml-insert-multipart "alternative") + (mml-insert-part "text/plain") (let ((reply-event (gnus-icalendar-event-from-buffer buffer-name mu4e-user-mail-address-list))) (insert (gnus-icalendar-event->gnus-calendar reply-event status))) - (mml-insert-multipart "alternative") - (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=UTF-8") + (forward-line 1); move past closing tag + (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=utf-8") (message-goto-subject) (delete-region (line-beginning-position) (line-end-position)) (insert "Subject: " (capitalize (symbol-name status)) From 66e514c58480677c7e6268f59683fa7a2e81698f Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 16:42:34 +0200 Subject: [PATCH 08/16] mu4e (ical): Use mu4e-user-mail-address-list when replying to ical events --- mu4e/mu4e-icalendar.el | 1 + 1 file changed, 1 insertion(+) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index a5bef3e6..3bc09217 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -35,6 +35,7 @@ (let* ((handle (car data)) (status (cadr data)) (event (caddr data)) + (gnus-icalendar-additional-identities mu4e-user-mail-address-list) (reply (gnus-icalendar-with-decoded-handle handle (gnus-icalendar-event-reply-from-buffer (current-buffer) status (gnus-icalendar-identities)))) From 0f38dd4b1a525b2eca7657dc26b7d36d4844ff51 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 17:06:34 +0200 Subject: [PATCH 09/16] =?UTF-8?q?mu4e=20(ical):=20Display=20ical=20buttons?= =?UTF-8?q?=20only=20when=20method=E2=88=88{REQUEST,PUBLISH}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mu4e/mu4e-icalendar.el | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 3bc09217..fc7cb52b 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -24,9 +24,11 @@ ((event gnus-icalendar-event) handle) (if (and (boundp 'mu4e~view-rendering) (gnus-icalendar-event:rsvp event)) - `(("Accept" mu4e-icalendar-reply (,handle accepted ,event)) - ("Tentative" mu4e-icalendar-reply (,handle tentative ,event)) - ("Decline" mu4e-icalendar-reply (,handle declined ,event))) + (let ((method (gnus-icalendar-event:method event))) + (when (or (string= method "REQUEST") (string= method "PUBLISH")) + `(("Accept" mu4e-icalendar-reply (,handle accepted ,event)) + ("Tentative" mu4e-icalendar-reply (,handle tentative ,event)) + ("Decline" mu4e-icalendar-reply (,handle declined ,event))))) (cl-call-next-method event handle)))) (defun mu4e-icalendar-reply (data) From 834582b7bd7ca38b2710a1bf7831a0976f1a05d0 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 23:11:57 +0200 Subject: [PATCH 10/16] mu4e (ical): Optionally save ical events to a diary file upon reply --- mu4e/mu4e-icalendar.el | 39 ++++++++++++++++++++++++++++++++------- mu4e/mu4e-vars.el | 16 ++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index fc7cb52b..59ed9bcd 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -8,6 +8,7 @@ ;; To enable optional iCalendar->Org sync functionality ;; NOTE: both the capture file and the headline(s) inside must already exist +;; (require 'org-agenda) ;; (setq gnus-icalendar-org-capture-file "~/org/notes.org") ;; (setq gnus-icalendar-org-capture-headline '("Calendar")) ;; (gnus-icalendar-org-setup) @@ -58,13 +59,15 @@ (fold-icalendar-buffer) (mu4e-icalendar-reply-ical msg event status (buffer-name))) - ;; Back in article buffer - (setq-local gnus-icalendar-reply-status status) - (when gnus-icalendar-org-enabled-p - (gnus-icalendar--update-org-event event status) - ;; refresh article buffer to update the reply status - (with-current-buffer mu4e~headers-buffer-name - (mu4e-headers-rerun-search))))))) + ;; Back in article buffer + (setq-local gnus-icalendar-reply-status status) + (when gnus-icalendar-org-enabled-p + (gnus-icalendar--update-org-event event status)) + (when mu4e-icalendar-diary-file + (mu4e~icalendar-insert-diary event status mu4e-icalendar-diary-file)) + ;; refresh article buffer to update the reply status + (with-current-buffer mu4e~headers-buffer-name + (mu4e-headers-rerun-search)))))) (defun mu4e~icalendar-delete-citation () (delete-region (point-min) (point-max))) @@ -98,6 +101,28 @@ )) +(defun mu4e~icalendar-insert-diary (event reply-status filename) + "Insert a diary entry for the EVENT with reply STATUS in FILE." + ;; FIXME: handle recurring events + (let* ((beg (gnus-icalendar-event:start-time event)) + (beg-date (format-time-string "%d/%m/%Y" beg)) + (beg-time (format-time-string "%H:%M" beg)) + (end (gnus-icalendar-event:end-time event)) + (end-date (format-time-string "%d/%m/%Y" end)) + (end-time (format-time-string "%H:%M" end)) + (summary (gnus-icalendar-event:summary event)) + (location (gnus-icalendar-event:location event)) + (status (capitalize (symbol-name reply-status))) + (txt (if location + (format "%s (%s)\n %s " summary status location) + (format "%s (%s)" summary status)))) + (with-temp-buffer + (if (string= beg-date end-date) + (insert beg-date " " beg-time "-" end-time " " txt "\n") + (insert beg-date " " beg-time " Start of: " txt "\n") + (insert beg-date " " end-time " End of: " txt "\n")) + (write-region (point-min) (point-max) filename t)))) + (provide 'mu4e-icalendar) ;;; mu4e-icalendar.el ends here diff --git a/mu4e/mu4e-vars.el b/mu4e/mu4e-vars.el index 5a0dd0f8..51c23c4b 100644 --- a/mu4e/mu4e-vars.el +++ b/mu4e/mu4e-vars.el @@ -439,6 +439,22 @@ Useful when this is not equal to the From: address." This is the message being replied to, forwarded or edited; used in `mu4e-compose-pre-hook'. For new messages, it is nil.") +(defgroup mu4e-icalendar nil + "Icalendar related settings." + :group 'mu4e) + +(defcustom mu4e-icalendar-trash-after-reply nil + "If non-nil, trash the icalendar invitation after replying." + :type 'boolean + :group 'mu4e-icalendar) + +(defcustom mu4e-icalendar-diary-file nil + "If non-nil, the file in which to add events upon reply." + :type '(choice (const :tag "Do not insert a diary entry" nil) + (string :tag "Insert a diary entry in this file")) + :group 'mu4e-icalendar) + + ;; Folders (defgroup mu4e-folders nil "Special folders." From 18e12ed3cb78c24f2ee55d6edd41bc8eae228d51 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Thu, 18 Apr 2019 11:00:50 +0200 Subject: [PATCH 11/16] mu4e (ical): Make possible to automatically trash a replied ical event --- mu4e/mu4e-icalendar.el | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 59ed9bcd..9ca8952d 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -42,8 +42,8 @@ (reply (gnus-icalendar-with-decoded-handle handle (gnus-icalendar-event-reply-from-buffer (current-buffer) status (gnus-icalendar-identities)))) - (msg (mu4e-message-at-point 'noerror))) - + (msg (mu4e-message-at-point 'noerror)) + (charset (cdr (assoc 'charset (mm-handle-type handle))))) (when reply (cl-labels ((fold-icalendar-buffer @@ -57,6 +57,8 @@ (delete-region (point-min) (point-max)) (insert reply) (fold-icalendar-buffer) + (when (string= (downcase charset) "utf-8") + (decode-coding-region (point-min) (point-max) 'utf-8)) (mu4e-icalendar-reply-ical msg event status (buffer-name))) ;; Back in article buffer @@ -64,14 +66,22 @@ (when gnus-icalendar-org-enabled-p (gnus-icalendar--update-org-event event status)) (when mu4e-icalendar-diary-file - (mu4e~icalendar-insert-diary event status mu4e-icalendar-diary-file)) - ;; refresh article buffer to update the reply status - (with-current-buffer mu4e~headers-buffer-name - (mu4e-headers-rerun-search)))))) + (mu4e~icalendar-insert-diary event status + mu4e-icalendar-diary-file)))))) (defun mu4e~icalendar-delete-citation () (delete-region (point-min) (point-max))) +(defun mu4e~icalendar-trash-message (msg) + "Trash the message MSG." + (with-current-buffer mu4e~headers-buffer-name + (let* ((docid (mu4e-message-field msg :docid)) + (markdescr (assq 'trash mu4e-marks)) + (action (plist-get (cdr markdescr) :action)) + (target (mu4e-get-trash-folder msg))) + (run-hook-with-args 'mu4e-mark-execute-pre-hook 'trash msg) + (funcall action docid msg target)))) + (defun mu4e-icalendar-reply-ical (original-msg event status buffer-name) (let ((message-signature nil)) (let ((mu4e-compose-cite-function #'mu4e~icalendar-delete-citation) @@ -85,7 +95,6 @@ (delete-region (line-beginning-position) (line-end-position)) (insert "To: " organizer))) (message-goto-body) - (insert "\n\n") (mml-insert-multipart "alternative") (mml-insert-part "text/plain") (let ((reply-event (gnus-icalendar-event-from-buffer @@ -97,6 +106,18 @@ (delete-region (line-beginning-position) (line-end-position)) (insert "Subject: " (capitalize (symbol-name status)) ": " (gnus-icalendar-event:summary event)) + (when mu4e-icalendar-trash-after-reply + ;; `mu4e~switch-back-to-mu4e-buffer' was executed. + (push + (lexical-let ((msg original-msg)) + #'(lambda () + (when (and (not (eq mu4e-split-view 'single-window)) + (buffer-live-p (mu4e-get-view-buffer))) + (switch-to-buffer (mu4e-get-view-buffer)) + (or (mu4e-view-headers-next) + (kill-buffer-and-window)) + (mu4e~icalendar-trash-message msg)))) + message-send-actions)) ; (message-send-and-exit) )) From 676ccc6e551d99b00eec6e63772cf2679a76c87e Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sat, 20 Apr 2019 12:44:44 +0200 Subject: [PATCH 12/16] mu4e (ical): Set automatically composed reply ical message unmodified --- mu4e/mu4e-icalendar.el | 1 + 1 file changed, 1 insertion(+) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 9ca8952d..74b9b6f7 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -106,6 +106,7 @@ (delete-region (line-beginning-position) (line-end-position)) (insert "Subject: " (capitalize (symbol-name status)) ": " (gnus-icalendar-event:summary event)) + (set-buffer-modified-p nil); not yet modified by user (when mu4e-icalendar-trash-after-reply ;; `mu4e~switch-back-to-mu4e-buffer' was executed. (push From e48835fadf3a28c5770f67a6a5990a40d584efe3 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sun, 21 Apr 2019 22:26:10 +0200 Subject: [PATCH 13/16] mu4e (ical): Add header and documentation and make checkdoc happy --- mu4e/mu4e-icalendar.el | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 74b9b6f7..6a5dea3a 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -1,4 +1,26 @@ -;;; mu4e-icalendar.el --- reply to iCalendar meeting requests +;;; mu4e-icalendar.el --- reply to iCalendar meeting requests (part of mu4e) +;; +;; Copyright (C) 2019- Christophe Troestler + +;; Author: Christophe Troestler +;; Maintainer: Dirk-Jan C. Binnema +;; Keywords: email icalendar +;; Version: 0.0 + +;; This file is not part of GNU Emacs. +;; +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . ;;; Commentary: @@ -13,13 +35,16 @@ ;; (setq gnus-icalendar-org-capture-headline '("Calendar")) ;; (gnus-icalendar-org-setup) +;;; Code: + (require 'mu4e) (require 'gnus-icalendar) (eval-when-compile (require 'cl)) - +;;;###autoload (defun mu4e-icalendar-setup () + "Perform the necessary initialization to use mu4e-icalendar." (gnus-icalendar-setup) (cl-defmethod gnus-icalendar-event:inline-reply-buttons :around ((event gnus-icalendar-event) handle) @@ -33,7 +58,7 @@ (cl-call-next-method event handle)))) (defun mu4e-icalendar-reply (data) - "Reply to a text/calendar event." + "Reply to the text/calendar event present in DATA." ;; Based on `gnus-icalendar-reply'. (let* ((handle (car data)) (status (cadr data)) @@ -70,6 +95,7 @@ mu4e-icalendar-diary-file)))))) (defun mu4e~icalendar-delete-citation () + "Function passed to `mu4e-compose-cite-function' to remove the citation." (delete-region (point-min) (point-max))) (defun mu4e~icalendar-trash-message (msg) @@ -83,6 +109,10 @@ (funcall action docid msg target)))) (defun mu4e-icalendar-reply-ical (original-msg event status buffer-name) + "Reply to ORIGINAL-MSG containing invitation EVENT with STATUS. +See `gnus-icalendar-event-reply-from-buffer' for the possible +STATUS values. BUFFER-NAME is the name of the buffer holding the +response in icalendar format." (let ((message-signature nil)) (let ((mu4e-compose-cite-function #'mu4e~icalendar-delete-citation) (mu4e-sent-messages-behavior 'delete) @@ -124,7 +154,9 @@ (defun mu4e~icalendar-insert-diary (event reply-status filename) - "Insert a diary entry for the EVENT with reply STATUS in FILE." + "Insert a diary entry for the EVENT in file named FILENAME. +REPLY-STATUS is the status of the reply. The possible values are +given in the doc of `gnus-icalendar-event-reply-from-buffer'." ;; FIXME: handle recurring events (let* ((beg (gnus-icalendar-event:start-time event)) (beg-date (format-time-string "%d/%m/%Y" beg)) From 19f09e6e5922e0611cc49c2f2edf7d7866664984 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Sun, 21 Apr 2019 22:47:29 +0200 Subject: [PATCH 14/16] mu4e (ical): Use message-remove-header to update the reply headers Fixes https://github.com/djcb/mu/pull/1403#discussion_r277174623 --- mu4e/mu4e-icalendar.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 6a5dea3a..416ce0ee 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -121,9 +121,9 @@ response in icalendar format." ;; Make sure the recipient is the organizer (let ((organizer (gnus-icalendar-event:organizer event))) (unless (string= organizer "") + (message-remove-header "To") (message-goto-to) - (delete-region (line-beginning-position) (line-end-position)) - (insert "To: " organizer))) + (insert organizer))) (message-goto-body) (mml-insert-multipart "alternative") (mml-insert-part "text/plain") @@ -132,9 +132,9 @@ response in icalendar format." (insert (gnus-icalendar-event->gnus-calendar reply-event status))) (forward-line 1); move past closing tag (mml-attach-buffer buffer-name "text/calendar; method=REPLY; charset=utf-8") + (message-remove-header "Subject") (message-goto-subject) - (delete-region (line-beginning-position) (line-end-position)) - (insert "Subject: " (capitalize (symbol-name status)) + (insert (capitalize (symbol-name status)) ": " (gnus-icalendar-event:summary event)) (set-buffer-modified-p nil); not yet modified by user (when mu4e-icalendar-trash-after-reply From a6efefb622afa0ac0997675bb1a83a709afc0f24 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Mon, 29 Apr 2019 09:52:28 +0200 Subject: [PATCH 15/16] mu4e (ical): Mention mu4e-icalendar-trash-after-reply in the configuration --- mu4e/mu4e-icalendar.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 416ce0ee..01616d66 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -27,6 +27,8 @@ ;; To install: ;; (require 'mu4e-icalendar) ;; (mu4e-icalendar-setup) +;; Optional +;; (setq mu4e-icalendar-trash-after-reply t) ;; To enable optional iCalendar->Org sync functionality ;; NOTE: both the capture file and the headline(s) inside must already exist From 5ca3254ef973e70402a02ec81ab6803eb53d2947 Mon Sep 17 00:00:00 2001 From: Christophe Troestler Date: Mon, 29 Apr 2019 20:17:27 +0200 Subject: [PATCH 16/16] mu4e (ical): Run message deletion after mu4e-sent-handler --- mu4e/mu4e-icalendar.el | 44 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/mu4e/mu4e-icalendar.el b/mu4e/mu4e-icalendar.el index 01616d66..6ab7d7e2 100644 --- a/mu4e/mu4e-icalendar.el +++ b/mu4e/mu4e-icalendar.el @@ -1,4 +1,4 @@ -;;; mu4e-icalendar.el --- reply to iCalendar meeting requests (part of mu4e) +;;; mu4e-icalendar.el --- reply to iCalendar meeting requests (part of mu4e) -*- lexical-binding: t; -*- ;; ;; Copyright (C) 2019- Christophe Troestler @@ -100,15 +100,23 @@ "Function passed to `mu4e-compose-cite-function' to remove the citation." (delete-region (point-min) (point-max))) -(defun mu4e~icalendar-trash-message (msg) - "Trash the message MSG." - (with-current-buffer mu4e~headers-buffer-name - (let* ((docid (mu4e-message-field msg :docid)) +(defun mu4e~icalendar-trash-message (original-msg) + "Trash the message ORIGINAL-MSG and move to the next one." + (lambda (docid path) + "See `mu4e-sent-handler' for DOCID and PATH." + (mu4e-sent-handler docid path) + (let* ((docid (mu4e-message-field original-msg :docid)) (markdescr (assq 'trash mu4e-marks)) (action (plist-get (cdr markdescr) :action)) - (target (mu4e-get-trash-folder msg))) - (run-hook-with-args 'mu4e-mark-execute-pre-hook 'trash msg) - (funcall action docid msg target)))) + (target (mu4e-get-trash-folder original-msg))) + (with-current-buffer (mu4e-get-headers-buffer) + (run-hook-with-args 'mu4e-mark-execute-pre-hook 'trash original-msg) + (funcall action docid original-msg target)) + (when (and (mu4e~headers-view-this-message-p docid) + (buffer-live-p (mu4e-get-view-buffer))) + (switch-to-buffer (mu4e-get-view-buffer)) + (or (mu4e-view-headers-next) + (kill-buffer-and-window)))))) (defun mu4e-icalendar-reply-ical (original-msg event status buffer-name) "Reply to ORIGINAL-MSG containing invitation EVENT with STATUS. @@ -140,19 +148,13 @@ response in icalendar format." ": " (gnus-icalendar-event:summary event)) (set-buffer-modified-p nil); not yet modified by user (when mu4e-icalendar-trash-after-reply - ;; `mu4e~switch-back-to-mu4e-buffer' was executed. - (push - (lexical-let ((msg original-msg)) - #'(lambda () - (when (and (not (eq mu4e-split-view 'single-window)) - (buffer-live-p (mu4e-get-view-buffer))) - (switch-to-buffer (mu4e-get-view-buffer)) - (or (mu4e-view-headers-next) - (kill-buffer-and-window)) - (mu4e~icalendar-trash-message msg)))) - message-send-actions)) -; (message-send-and-exit) - )) + ;; Override `mu4e-sent-handler' set by `mu4e-compose-mode' to + ;; also trash the message (thus must be appended to hooks). + (add-hook + 'message-sent-hook + #'(lambda () (setq mu4e-sent-func + (mu4e~icalendar-trash-message original-msg))) + t t)))) (defun mu4e~icalendar-insert-diary (event reply-status filename)