* WIP: use org-mode for writing rich-text messages
This commit is contained in:
@ -237,7 +237,7 @@ separator is never written to file. Also see
|
|||||||
;; search for the first empty line
|
;; search for the first empty line
|
||||||
(if (search-forward-regexp (concat "^$"))
|
(if (search-forward-regexp (concat "^$"))
|
||||||
(replace-match
|
(replace-match
|
||||||
(concat (propertize mail-header-separator 'read-only t 'intangible t)))
|
(concat (propertize mail-header-separator 'intangible t)))
|
||||||
;; no empty line? then append one
|
;; no empty line? then append one
|
||||||
(progn
|
(progn
|
||||||
(goto-char (point-max))
|
(goto-char (point-max))
|
||||||
@ -423,15 +423,19 @@ needed, set the Fcc header, and register the handler function."
|
|||||||
;; update the file on disk -- ie., without the separator
|
;; update the file on disk -- ie., without the separator
|
||||||
(mu4e~proc-add (buffer-file-name) mu4e-drafts-folder)) nil t))
|
(mu4e~proc-add (buffer-file-name) mu4e-drafts-folder)) nil t))
|
||||||
|
|
||||||
|
(defconst mu4e~compose-hidden-headers
|
||||||
|
`("^References:" "^Face:" "^X-Face:"
|
||||||
|
"^X-Draft-From:" "^User-agent:")
|
||||||
|
"Hidden headers when composing.")
|
||||||
|
|
||||||
|
|
||||||
(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
|
(define-derived-mode mu4e-compose-mode message-mode "mu4e:compose"
|
||||||
"Major mode for the mu4e message composition, derived from `message-mode'.
|
"Major mode for the mu4e message composition, derived from `message-mode'.
|
||||||
\\{message-mode-map}."
|
\\{message-mode-map}."
|
||||||
(let ((message-hidden-headers
|
(let ((message-hidden-headers mu4e~compose-hidden-headers))
|
||||||
`("^References:" "^Face:" "^X-Face:" "^X-Draft-From:" "^User-agent:")))
|
|
||||||
(use-local-map mu4e-compose-mode-map)
|
(use-local-map mu4e-compose-mode-map)
|
||||||
(message-hide-headers)
|
|
||||||
(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
|
||||||
@ -443,7 +447,21 @@ needed, set the Fcc header, and register the handler function."
|
|||||||
;; set the default directory to the user's home dir; this is probably more
|
;; set the default directory to the user's home dir; this is probably more
|
||||||
;; useful e.g. when finding an attachment file the directory the current
|
;; useful e.g. when finding an attachment file the directory the current
|
||||||
;; mail files lives in...
|
;; mail files lives in...
|
||||||
(setq default-directory (expand-file-name "~/"))))
|
(setq default-directory (expand-file-name "~/"))
|
||||||
|
|
||||||
|
;; setup the fcc-stuff, if needed
|
||||||
|
(add-hook 'message-send-hook
|
||||||
|
(lambda ()
|
||||||
|
;; for safety, always save the draft before sending
|
||||||
|
(set-buffer-modified-p t)
|
||||||
|
(save-buffer)
|
||||||
|
(mu4e~setup-fcc-maybe)) nil t)
|
||||||
|
|
||||||
|
;; when the message has been sent.
|
||||||
|
(add-hook 'message-sent-hook
|
||||||
|
(lambda ()
|
||||||
|
(setq mu4e-sent-func 'mu4e-sent-handler)
|
||||||
|
(mu4e~proc-sent (buffer-file-name) mu4e-drafts-folder)) nil)))
|
||||||
|
|
||||||
|
|
||||||
(defconst mu4e~compose-buffer-max-name-length 30
|
(defconst mu4e~compose-buffer-max-name-length 30
|
||||||
@ -524,20 +542,6 @@ 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)
|
||||||
|
|
||||||
;; setup the fcc-stuff, if needed
|
|
||||||
(add-hook 'message-send-hook
|
|
||||||
(lambda ()
|
|
||||||
;; for safety, always save the draft before sending
|
|
||||||
(set-buffer-modified-p t)
|
|
||||||
(save-buffer)
|
|
||||||
(mu4e~setup-fcc-maybe)) nil t)
|
|
||||||
|
|
||||||
;; when the message has been sent.
|
|
||||||
(add-hook 'message-sent-hook
|
|
||||||
(lambda ()
|
|
||||||
(setq mu4e-sent-func 'mu4e-sent-handler)
|
|
||||||
(mu4e~proc-sent (buffer-file-name) mu4e-drafts-folder)) nil t))
|
|
||||||
|
|
||||||
;; buffer is not user-modified yet
|
;; buffer is not user-modified yet
|
||||||
(mu4e~compose-set-friendly-buffer-name compose-type)
|
(mu4e~compose-set-friendly-buffer-name compose-type)
|
||||||
(set-buffer-modified-p nil)
|
(set-buffer-modified-p nil)
|
||||||
@ -545,8 +549,7 @@ Gnus' `message-mode'."
|
|||||||
;; now jump to some use positions, and start writing that mail!
|
;; now jump to some use positions, and start writing that mail!
|
||||||
(if (member compose-type '(new forward))
|
(if (member compose-type '(new forward))
|
||||||
(message-goto-to)
|
(message-goto-to)
|
||||||
(message-goto-body)))
|
(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
|
||||||
@ -695,5 +698,8 @@ message."
|
|||||||
'message-kill-buffer
|
'message-kill-buffer
|
||||||
'message-send-hook)
|
'message-send-hook)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(provide 'mu4e-compose)
|
(provide 'mu4e-compose)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
;;; org-mu4e -- Support for links to mu4e messages/queries from within org-mode
|
;;; org-mu4e -- Support for links to mu4e messages/queries from within org-mode,
|
||||||
|
;;; and for writing message in org-mode, sending them as rich-text
|
||||||
;;
|
;;
|
||||||
;; Copyright (C) 2012 Dirk-Jan C. Binnema
|
;; Copyright (C) 2012 Dirk-Jan C. Binnema
|
||||||
|
|
||||||
@ -71,6 +72,184 @@ the query (for paths starting with 'query:')."
|
|||||||
(t (message "mu4e: unrecognized link type '%s'" path))))
|
(t (message "mu4e: unrecognized link type '%s'" path))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;;; editing with org-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;; below, some functions for the org->html conversion
|
||||||
|
;; based on / inspired by Eric Schulte's org-mime.el
|
||||||
|
;; Homepage: http://orgmode.org/worg/org-contrib/org-mime.php
|
||||||
|
(defun org~mu4e-mime-file (ext path id)
|
||||||
|
"Create a file for an attachment."
|
||||||
|
(format (concat "<#part type=\"%s\" filename=\"%s\" "
|
||||||
|
"disposition=inline id=\"<%s>\">\n<#/part>\n")
|
||||||
|
ext path id))
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-multipart (plain html &optional images)
|
||||||
|
"Create a multipart/alternative with text/plain and text/html alternatives.
|
||||||
|
If the html portion of the message includes images, wrap the html
|
||||||
|
and images in a multipart/related part."
|
||||||
|
(concat "<#multipart type=alternative><#part type=text/plain>"
|
||||||
|
plain
|
||||||
|
(when images "<#multipart type=related>")
|
||||||
|
"<#part type=text/html>"
|
||||||
|
html
|
||||||
|
images
|
||||||
|
(when images "<#/multipart>\n")
|
||||||
|
"<#/multipart>\n"))
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-replace-images (str current-file)
|
||||||
|
"Replace images in html files with cid links."
|
||||||
|
(let (html-images)
|
||||||
|
(cons
|
||||||
|
(replace-regexp-in-string ;; replace images in html
|
||||||
|
"src=\"\\([^\"]+\\)\""
|
||||||
|
(lambda (text)
|
||||||
|
(format
|
||||||
|
"src=\"cid:%s\""
|
||||||
|
(let* ((url (and (string-match "src=\"\\([^\"]+\\)\"" text)
|
||||||
|
(match-string 1 text)))
|
||||||
|
(path (expand-file-name
|
||||||
|
url (file-name-directory current-file)))
|
||||||
|
(ext (file-name-extension path))
|
||||||
|
(id (replace-regexp-in-string "[\/\\\\]" "_" path)))
|
||||||
|
(add-to-list 'html-images
|
||||||
|
(org~mu4e-mime-file
|
||||||
|
(concat "image/" ext) path id))
|
||||||
|
id)))
|
||||||
|
str)
|
||||||
|
html-images)))
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-convert-to-html ()
|
||||||
|
"Convert the current body to html."
|
||||||
|
(let* ((begin
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward mail-header-separator)))
|
||||||
|
(end (point-max))
|
||||||
|
(raw-body (buffer-substring begin end))
|
||||||
|
(tmp-file (make-temp-name (expand-file-name "mail"
|
||||||
|
temporary-file-directory)))
|
||||||
|
(body (org-export-string raw-body 'org (file-name-directory tmp-file)))
|
||||||
|
;; because we probably don't want to skip part of our mail
|
||||||
|
(org-export-skip-text-before-1st-heading nil)
|
||||||
|
;; because we probably don't want to export a huge style file
|
||||||
|
(org-export-htmlize-output-type 'inline-css)
|
||||||
|
;; makes the replies with ">"s look nicer
|
||||||
|
(org-export-preserve-breaks t)
|
||||||
|
;; dvipng for inline latex because MathJax doesn't work in mail
|
||||||
|
(org-export-with-LaTeX-fragments 'dvipng)
|
||||||
|
;; to hold attachments for inline html images
|
||||||
|
(html-and-images
|
||||||
|
(org~mu4e-mime-replace-images
|
||||||
|
(org-export-string raw-body 'html (file-name-directory tmp-file))
|
||||||
|
tmp-file))
|
||||||
|
(html-images (cdr html-and-images))
|
||||||
|
(html (car html-and-images)))
|
||||||
|
(delete-region begin end)
|
||||||
|
(save-excursion
|
||||||
|
(goto-char begin)
|
||||||
|
(newline)
|
||||||
|
(insert (org~mu4e-mime-multipart
|
||||||
|
body html (mapconcat 'identity html-images "\n"))))))
|
||||||
|
|
||||||
|
;; next some functions to make the org/mu4e-compose-mode switch as smooth as
|
||||||
|
;; possible.
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-decorate-headers (where)
|
||||||
|
"If WHERE is the symbol 'body, prefix headers with '#' block;
|
||||||
|
when it is 'headers, remove this."
|
||||||
|
(save-excursion
|
||||||
|
(message-narrow-to-headers)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(case where
|
||||||
|
(headers
|
||||||
|
(when (looking-at "#")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (re-search-forward "^#" nil t)
|
||||||
|
(replace-match ""))))
|
||||||
|
(body
|
||||||
|
(unless (looking-at "#")
|
||||||
|
(goto-char (point-min))
|
||||||
|
(while (re-search-forward "^" nil t)
|
||||||
|
(replace-match "#")))))
|
||||||
|
(widen)))
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-convert-to-html-maybe ()
|
||||||
|
"Convert to html if `org-mu4e-convert-to-html' is non-nil. This
|
||||||
|
function is called when sending a message (from
|
||||||
|
`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."
|
||||||
|
(message "Converting to html")
|
||||||
|
(when org-mu4e-convert-to-html
|
||||||
|
(org~mu4e-mime-convert-to-html)))
|
||||||
|
|
||||||
|
|
||||||
|
(defun org~mu4e-mime-switch-headers-or-body (&optional firsttime)
|
||||||
|
"Switch the buffer to either mu4e-compose-mode (when in headers)
|
||||||
|
or org-mode (when in the body),"
|
||||||
|
(interactive)
|
||||||
|
(let* ((sepapoint
|
||||||
|
(save-excursion
|
||||||
|
(goto-char (point-min))
|
||||||
|
(search-forward-regexp mail-header-separator)))
|
||||||
|
(where ;; is point in the headers or in the body?
|
||||||
|
(if (> (point) sepapoint) 'body 'headers))
|
||||||
|
(first-char (buffer-substring (point-min) (1+ (point-min))))
|
||||||
|
(state-changed
|
||||||
|
(if (string= first-char "#")
|
||||||
|
(eq where 'headers)
|
||||||
|
(eq where 'body))))
|
||||||
|
(when (or firsttime state-changed)
|
||||||
|
;; now make sure the headers correspond to the mode...
|
||||||
|
(case where
|
||||||
|
(headers
|
||||||
|
(org~mu4e-mime-decorate-headers 'headers)
|
||||||
|
(mu4e-compose-mode)
|
||||||
|
(add-hook 'message-send-hook
|
||||||
|
'org~mu4e-mime-convert-to-html-maybe t 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
|
||||||
|
(add-hook 'post-command-hook
|
||||||
|
'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 ()
|
||||||
|
"Pseudo-Minor mode for mu4e-compose-mode, to edit the message
|
||||||
|
body using org-mode."
|
||||||
|
(interactive)
|
||||||
|
(unless (member major-mode '(org-mode mu4e-compose-mode))
|
||||||
|
(error "Need org-mode or mu4e-compose-mode"))
|
||||||
|
(if (not org-mu4e-compose-org-mode)
|
||||||
|
(progn
|
||||||
|
(org~mu4e-mime-switch-headers-or-body t)
|
||||||
|
(message "org-mu4e-compose-org-mode enabled"))
|
||||||
|
(progn ;; otherwise, remove crap
|
||||||
|
(remove-hook 'post-command-hook 'org~mu4e-mime-switch-headers-or-body t)
|
||||||
|
(org~mu4e-mime-decorate-headers 'headers) ;; shut off org-mode stuff
|
||||||
|
(setq org-mu4e-compose-org-mode nil)
|
||||||
|
(mu4e-compose-mode)
|
||||||
|
(message "org-mu4e-compose-org-mode disabled"))))
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
(provide 'org-mu4e)
|
(provide 'org-mu4e)
|
||||||
;;; org-mu4e.el ends here
|
;;; org-mu4e.el ends here
|
||||||
|
|||||||
Reference in New Issue
Block a user