* enable writing messages using org-mode, on-fly conversion to html, document it
This commit is contained in:
@ -159,13 +159,11 @@ e-mail addresses. If LST is nil, returns nil."
|
|||||||
the original message ORIGMSG. If the Reply-To address is set, use
|
the original message ORIGMSG. If the Reply-To address is set, use
|
||||||
that, otherwise use the From address. Note, whatever was in the To:
|
that, otherwise use the From address. Note, whatever was in the To:
|
||||||
field before, goes to the Cc:-list (if we're doing a reply-to-all)."
|
field before, goes to the Cc:-list (if we're doing a reply-to-all)."
|
||||||
(let* ((reply-to (plist-get origmsg :reply-to))
|
(message "REPLY TO %S" (plist-get origmsg :reply-to))
|
||||||
(to-lst
|
(let ((reply-to
|
||||||
(or reply-to
|
(or (plist-get origmsg :reply-to) (plist-get origmsg :from))))
|
||||||
(delete-duplicates
|
(delete-duplicates reply-to :test #'mu4e~compose-address-cell-equal)))
|
||||||
(plist-get origmsg :from)
|
|
||||||
:test #'mu4e~compose-address-cell-equal))))
|
|
||||||
to-lst))
|
|
||||||
|
|
||||||
(defun mu4e~compose-create-cc-lst (origmsg reply-all)
|
(defun mu4e~compose-create-cc-lst (origmsg reply-all)
|
||||||
"Create a list of address for the Cc: in a new message, based on
|
"Create a list of address for the Cc: in a new message, based on
|
||||||
@ -435,8 +433,7 @@ needed, set the Fcc header, and register the handler function."
|
|||||||
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
||||||
(use-local-map mu4e-compose-mode-map)
|
(use-local-map mu4e-compose-mode-map)
|
||||||
(make-local-variable 'message-default-charset)
|
(make-local-variable 'message-default-charset)
|
||||||
(message-hide-headers)
|
|
||||||
|
|
||||||
;; if the default charset is not set, use UTF-8
|
;; if the default charset is not set, use UTF-8
|
||||||
(unless message-default-charset
|
(unless message-default-charset
|
||||||
(setq message-default-charset 'utf-8))
|
(setq message-default-charset 'utf-8))
|
||||||
@ -541,15 +538,17 @@ Gnus' `message-mode'."
|
|||||||
|
|
||||||
;; set compose mode -- so now hooks can run
|
;; set compose mode -- so now hooks can run
|
||||||
(mu4e-compose-mode)
|
(mu4e-compose-mode)
|
||||||
|
(message-hide-headers)
|
||||||
|
|
||||||
;; buffer is not user-modified yet
|
|
||||||
(mu4e~compose-set-friendly-buffer-name compose-type)
|
|
||||||
(set-buffer-modified-p nil)
|
|
||||||
|
|
||||||
;; now jump to some use positions, and start writing that mail!
|
;; buffer is not user-modified yet
|
||||||
(if (member compose-type '(new forward))
|
(mu4e~compose-set-friendly-buffer-name compose-type)
|
||||||
(message-goto-to)
|
(set-buffer-modified-p nil)
|
||||||
(message-goto-body))))
|
|
||||||
|
;; now jump to some use positions, and start writing that mail!
|
||||||
|
(if (member compose-type '(new forward))
|
||||||
|
(message-goto-to)
|
||||||
|
(message-goto-body))))
|
||||||
|
|
||||||
(defun mu4e-sent-handler (docid path)
|
(defun mu4e-sent-handler (docid path)
|
||||||
"Handler function, called with DOCID and PATH for the just-sent
|
"Handler function, called with DOCID and PATH for the just-sent
|
||||||
@ -702,4 +701,4 @@ message."
|
|||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(provide 'mu4e-compose)
|
(provide 'mu4e-compose)
|
||||||
|
|||||||
@ -1327,6 +1327,7 @@ with other tools.
|
|||||||
@menu
|
@menu
|
||||||
* Setting the default emacs mail program::
|
* Setting the default emacs mail program::
|
||||||
* Creating org-mode links::
|
* Creating org-mode links::
|
||||||
|
* Rich-text messages with org-mode::
|
||||||
* Maintaining an address-book with org-contacts::
|
* Maintaining an address-book with org-contacts::
|
||||||
* Getting new mail notifications with Sauron::
|
* Getting new mail notifications with Sauron::
|
||||||
* Speedbar support::
|
* Speedbar support::
|
||||||
@ -1369,6 +1370,50 @@ to the query or message the link points to with either @t{M-x
|
|||||||
org-agenda-open-link} in agenda buffers, or @t{M-x org-open-at-point}
|
org-agenda-open-link} in agenda buffers, or @t{M-x org-open-at-point}
|
||||||
elsewhere - both are typically bound to @kbd{C-c C-o}.
|
elsewhere - both are typically bound to @kbd{C-c C-o}.
|
||||||
|
|
||||||
|
@node Rich-text messages with org-mode
|
||||||
|
@section Rich-text messages with org-mode
|
||||||
|
|
||||||
|
@t{org-mode} has some nice facilities for editing texts -- creating lists,
|
||||||
|
tables, mathematical formulae etc. In addition, it can convert them to
|
||||||
|
@abbr{HTML}.
|
||||||
|
|
||||||
|
An @emph{experimental} @t{mu4e} feature lets you edit your messages with
|
||||||
|
@t{org-mode}, and (optionally) convert them on the fly (when sending them) to
|
||||||
|
messages with an HTML-part containing the rich-text version of your messages.
|
||||||
|
|
||||||
|
To enable all this, make sure you have
|
||||||
|
@lisp
|
||||||
|
(require 'org-mu4e)
|
||||||
|
@end lisp
|
||||||
|
somewhere in your setup, and also make sure that the @t{dvipng} program is
|
||||||
|
available in your path.
|
||||||
|
|
||||||
|
Then, when composing a message, you can use @code{M-x
|
||||||
|
org-mu4e-compose-org-mode} to enable this mode, or, alternatively, put in the
|
||||||
|
mode-hook for @t{mu4e-compose-mode}.
|
||||||
|
|
||||||
|
@t{org-mu4e-compose-org-mode} behaves more or less like a minor-mode. When it
|
||||||
|
is active, editing the message body takes place in @t{org-mode}, while editing
|
||||||
|
the headers uses the normal message editing mode, @t{mu4e-compose-mode}.
|
||||||
|
|
||||||
|
Now, if you want to automatically convert the @t{org-mode} markup to rich-text
|
||||||
|
when sending messages, you need to set the variable
|
||||||
|
@code{org-mu4e-convert-to-html} to non-nil:
|
||||||
|
|
||||||
|
@lisp
|
||||||
|
(setq org-mu4e-convert-to-html t)
|
||||||
|
@end lisp
|
||||||
|
|
||||||
|
To send the message or execute other @t{mu4e-compose-mode}/@t{message-mode}
|
||||||
|
commands on the message, first press @key{M-m}. Thus, for example, to send the
|
||||||
|
message, you'd press @key{M-m C-c}.
|
||||||
|
|
||||||
|
The code for doing the conversion is based on Eric Schultze's
|
||||||
|
@t{org-mime}@footnote{@url{http://orgmode.org/worg/org-contrib/org-mime.php}},
|
||||||
|
but has been customized for use with @t{mu4e}. In particular, the
|
||||||
|
mode-switching between @t{org-mode} and @t{mu4e-compose-mode} is
|
||||||
|
@t{mu4e-specific}.
|
||||||
|
|
||||||
@node Maintaining an address-book with org-contacts
|
@node Maintaining an address-book with org-contacts
|
||||||
@section Maintaining an address-book with org-contacts
|
@section Maintaining an address-book with org-contacts
|
||||||
|
|
||||||
|
|||||||
@ -86,7 +86,7 @@ the query (for paths starting with 'query:')."
|
|||||||
(format (concat "<#part type=\"%s\" filename=\"%s\" "
|
(format (concat "<#part type=\"%s\" filename=\"%s\" "
|
||||||
"disposition=inline id=\"<%s>\">\n<#/part>\n")
|
"disposition=inline id=\"<%s>\">\n<#/part>\n")
|
||||||
ext path id))
|
ext path id))
|
||||||
|
|
||||||
(defun org~mu4e-mime-multipart (plain html &optional images)
|
(defun org~mu4e-mime-multipart (plain html &optional images)
|
||||||
"Create a multipart/alternative with text/plain and text/html alternatives.
|
"Create a multipart/alternative with text/plain and text/html alternatives.
|
||||||
If the html portion of the message includes images, wrap the html
|
If the html portion of the message includes images, wrap the html
|
||||||
@ -99,7 +99,7 @@ and images in a multipart/related part."
|
|||||||
images
|
images
|
||||||
(when images "<#/multipart>\n")
|
(when images "<#/multipart>\n")
|
||||||
"<#/multipart>\n"))
|
"<#/multipart>\n"))
|
||||||
|
|
||||||
(defun org~mu4e-mime-replace-images (str current-file)
|
(defun org~mu4e-mime-replace-images (str current-file)
|
||||||
"Replace images in html files with cid links."
|
"Replace images in html files with cid links."
|
||||||
(let (html-images)
|
(let (html-images)
|
||||||
@ -147,88 +147,96 @@ and images in a multipart/related part."
|
|||||||
(org-export-string raw-body 'html (file-name-directory tmp-file))
|
(org-export-string raw-body 'html (file-name-directory tmp-file))
|
||||||
tmp-file))
|
tmp-file))
|
||||||
(html-images (cdr html-and-images))
|
(html-images (cdr html-and-images))
|
||||||
(html (car html-and-images)))
|
(html (car html-and-images)))
|
||||||
(delete-region begin end)
|
(delete-region begin end)
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char begin)
|
(goto-char begin)
|
||||||
(newline)
|
(newline)
|
||||||
(insert (org~mu4e-mime-multipart
|
(insert (org~mu4e-mime-multipart
|
||||||
body html (mapconcat 'identity html-images "\n"))))))
|
body html (mapconcat 'identity html-images "\n"))))))
|
||||||
|
|
||||||
;; next some functions to make the org/mu4e-compose-mode switch as smooth as
|
;; next some functions to make the org/mu4e-compose-mode switch as smooth as
|
||||||
;; possible.
|
;; possible.
|
||||||
|
(defun org~mu4e-mime-decorate-headers ()
|
||||||
(defun org~mu4e-mime-decorate-headers (where)
|
"Make the headers visually distinctive (org-mode)."
|
||||||
"If WHERE is the symbol 'body, prefix headers with '#' block;
|
|
||||||
when it is 'headers, remove this."
|
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(message-narrow-to-headers)
|
(goto-char (point-min))
|
||||||
(goto-char (point-min))
|
(let* ((eoh (when (search-forward mail-header-separator)
|
||||||
(case where
|
(match-end 0)))
|
||||||
(headers
|
(olay (make-overlay (point-min) eoh)))
|
||||||
(when (looking-at "#")
|
(when olay
|
||||||
(goto-char (point-min))
|
(overlay-put olay 'face 'font-lock-comment-face)))))
|
||||||
(while (re-search-forward "^#" nil t)
|
|
||||||
(replace-match ""))))
|
(defun org~mu4e-mime-undecorate-headers ()
|
||||||
(body
|
"Don't make the headers visually distinctive (well,
|
||||||
(unless (looking-at "#")
|
mu4e-compose-mode will take care of that)."
|
||||||
(goto-char (point-min))
|
(save-excursion
|
||||||
(while (re-search-forward "^" nil t)
|
(goto-char (point-min))
|
||||||
(replace-match "#")))))
|
(let* ((eoh (when (search-forward mail-header-separator)
|
||||||
(widen)))
|
(match-end 0))))
|
||||||
|
(remove-overlays (point-min) eoh))))
|
||||||
|
|
||||||
|
(defvar org-mu4e-convert-to-html nil
|
||||||
|
"Wether to an org-mode => html conversion when sending messages.")
|
||||||
|
|
||||||
(defun org~mu4e-mime-convert-to-html-maybe ()
|
(defun org~mu4e-mime-convert-to-html-maybe ()
|
||||||
"Convert to html if `org-mu4e-convert-to-html' is non-nil. This
|
"Convert to html if `org-mu4e-convert-to-html' is non-nil. This
|
||||||
function is called when sending a message (from
|
function is called when sending a message (from
|
||||||
`message-send-hook') and, if non-nil, will send the message as the
|
`message-send-hook') and, if non-nil, will send the message as the
|
||||||
rich-text version of the what is assumed to be an org-mode body."
|
rich-text version of the what is assumed to be an org-mode body."
|
||||||
(message "Converting to html")
|
|
||||||
(when org-mu4e-convert-to-html
|
(when org-mu4e-convert-to-html
|
||||||
|
(message "Converting to html")
|
||||||
(org~mu4e-mime-convert-to-html)))
|
(org~mu4e-mime-convert-to-html)))
|
||||||
|
|
||||||
|
(defun org~mu4e-execute-key-sequence-in-compose-mode (keyseq)
|
||||||
|
"Execute keysequence KEYSEQ by (temporarily) switching to compose
|
||||||
|
mode."
|
||||||
|
(mu4e-compose-mode)
|
||||||
|
(setq org-mu4e-compose-org-mode t)
|
||||||
|
(add-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t t)
|
||||||
|
(let ((func (lookup-key (current-local-map) key)))
|
||||||
|
(unless (functionp func)
|
||||||
|
(error "Invalid key binding"))
|
||||||
|
(add-hook 'message-send-hook 'org~mu4e-mime-convert-to-html-maybe t t)
|
||||||
|
(funcall func)))
|
||||||
|
|
||||||
(defun org~mu4e-mime-switch-headers-or-body (&optional firsttime)
|
|
||||||
|
(defun org~mu4e-mime-switch-headers-or-body ()
|
||||||
"Switch the buffer to either mu4e-compose-mode (when in headers)
|
"Switch the buffer to either mu4e-compose-mode (when in headers)
|
||||||
or org-mode (when in the body),"
|
or org-mode (when in the body),"
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((sepapoint
|
(let* ((sepapoint
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(goto-char (point-min))
|
(goto-char (point-min))
|
||||||
(search-forward-regexp mail-header-separator)))
|
(search-forward-regexp mail-header-separator nil t))))
|
||||||
(where ;; is point in the headers or in the body?
|
;; only do stuff when the sepapoint exist; note that after sending the
|
||||||
(if (> (point) sepapoint) 'body 'headers))
|
;; message, this function maybe called on a message with the sepapoint
|
||||||
(first-char (buffer-substring (point-min) (1+ (point-min))))
|
;; stripped.
|
||||||
(state-changed
|
(when sepapoint
|
||||||
(if (string= first-char "#")
|
(cond
|
||||||
(eq where 'headers)
|
;; we're in the body, but in mu4e-compose-mode?
|
||||||
(eq where 'body))))
|
;; if so, switch to org-mode
|
||||||
(when (or firsttime state-changed)
|
((and (> (point) sepapoint) (eq major-mode 'mu4e-compose-mode))
|
||||||
;; now make sure the headers correspond to the mode...
|
(org-mode)
|
||||||
(case where
|
(add-hook 'before-save-hook
|
||||||
(headers
|
(lambda ()
|
||||||
(org~mu4e-mime-decorate-headers 'headers)
|
(error "Switch to mu4e-compose-mode (M-m) before saving.")) nil t)
|
||||||
|
(org~mu4e-mime-decorate-headers)
|
||||||
|
(local-set-key (kbd "M-m")
|
||||||
|
(lambda (key)
|
||||||
|
(interactive "kEnter mu4e-compose-mode key sequence: ")
|
||||||
|
(org~mu4e-execute-key-sequence-in-compose-mode key))))
|
||||||
|
;; we're in the headers, but in org-mode?
|
||||||
|
;; if so, switch to mu4e-compose-mode
|
||||||
|
((and (<= (point) sepapoint) (eq major-mode 'org-mode))
|
||||||
|
(org~mu4e-mime-undecorate-headers)
|
||||||
(mu4e-compose-mode)
|
(mu4e-compose-mode)
|
||||||
(add-hook 'message-send-hook
|
(add-hook 'message-send-hook
|
||||||
'org~mu4e-mime-convert-to-html-maybe t t))
|
'org~mu4e-mime-convert-to-html-maybe nil t)))
|
||||||
(body
|
|
||||||
(org-mode)
|
|
||||||
(org~mu4e-mime-decorate-headers 'body)
|
|
||||||
(local-set-key (kbd "M-m")
|
|
||||||
(lambda ()
|
|
||||||
(interactive)
|
|
||||||
(org~mu4e-mime-decorate-headers 'headers)
|
|
||||||
(mu4e-compose-mode)
|
|
||||||
(setq org-mu4e-compose-org-mode t)
|
|
||||||
(add-hook 'message-send-hook
|
|
||||||
'org~mu4e-mime-convert-to-html-maybe t t))))
|
|
||||||
(otherwise (error "unexpected where %S" where)))
|
|
||||||
(setq org-mu4e-compose-org-mode t)
|
|
||||||
;; and add the hook
|
;; and add the hook
|
||||||
(add-hook 'post-command-hook
|
(add-hook 'post-command-hook
|
||||||
'org~mu4e-mime-switch-headers-or-body t t))))
|
'org~mu4e-mime-switch-headers-or-body t t))))
|
||||||
|
|
||||||
(defvar org-mu4e-compose-org-mode nil)
|
|
||||||
(setq org-mu4e-compose-org-mode nil)
|
|
||||||
|
|
||||||
(defun org-mu4e-compose-org-mode ()
|
(defun org-mu4e-compose-org-mode ()
|
||||||
"Pseudo-Minor mode for mu4e-compose-mode, to edit the message
|
"Pseudo-Minor mode for mu4e-compose-mode, to edit the message
|
||||||
@ -236,19 +244,23 @@ or org-mode (when in the body),"
|
|||||||
(interactive)
|
(interactive)
|
||||||
(unless (member major-mode '(org-mode mu4e-compose-mode))
|
(unless (member major-mode '(org-mode mu4e-compose-mode))
|
||||||
(error "Need org-mode or mu4e-compose-mode"))
|
(error "Need org-mode or mu4e-compose-mode"))
|
||||||
(if (not org-mu4e-compose-org-mode)
|
(unless (executable-find "dvipng")
|
||||||
|
(error "Required program dvipng not found"))
|
||||||
|
;; we can check if we're already in mu4e-compose-mode by checking
|
||||||
|
;; if the post-command-hook is set; hackish...
|
||||||
|
(if (not (member 'org~mu4e-mime-switch-headers-or-body post-command-hook))
|
||||||
(progn
|
(progn
|
||||||
(org~mu4e-mime-switch-headers-or-body t)
|
(org~mu4e-mime-switch-headers-or-body)
|
||||||
(message "org-mu4e-compose-org-mode enabled"))
|
(message
|
||||||
|
(concat
|
||||||
|
"org-mu4e-compose-org-mode enabled; "
|
||||||
|
"press M-m before issuing message-mode commands")))
|
||||||
(progn ;; otherwise, remove crap
|
(progn ;; otherwise, remove crap
|
||||||
(remove-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t)
|
(remove-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t)
|
||||||
(org~mu4e-mime-decorate-headers 'headers) ;; shut off org-mode stuff
|
(org~mu4e-mime-undecorate-headers) ;; shut off org-mode stuff
|
||||||
(setq org-mu4e-compose-org-mode nil)
|
(setq org-mu4e-compose-org-mode nil)
|
||||||
(mu4e-compose-mode)
|
(mu4e-compose-mode)
|
||||||
(message "org-mu4e-compose-org-mode disabled"))))
|
(message "org-mu4e-compose-org-mode disabled"))))
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(provide 'org-mu4e)
|
(provide 'org-mu4e)
|
||||||
|
|||||||
Reference in New Issue
Block a user