Upstream org-mime-htmlize unfortunately can’t be called uninteractively (bummer!), so we have to rewrite it to make programmatic calls work properly. I found the solution Emacs Stackexchange.
<div class="org-src-container">
<pre class="src src-emacs-lisp"><span style="color: #707183;">(</span><span style="color: #a020f0;">defun</span> <span style="color: #0000ff;">org-mime-htmlize</span> <span style="color: #707183;">(</span><span style="color: #228b22;">&optional</span> arg<span style="color: #707183;">)</span>
“Export a portion of an email body composed using </span><span style="color: #008b8b;">mml-mode</span><span style="color: #8b2252;">' to</span> <span style="color: #8b2252;">html using
org-mode’. If called with an active region only
export that region, otherwise export the entire body."
(interactive “P”)
(require ‘ox-org)
(require ‘ox-html)
(let* ((region-p (org-region-active-p))
(html-start (or (and region-p (region-beginning))
(save-excursion
(goto-char (point-min))
(search-forward mail-header-separator)
(+ (point) 1))))
(html-end (or (and region-p (region-end))
;; TODO: should catch signature…
(point-max)))
(raw-body (concat org-mime-default-header
(buffer-substring html-start html-end)))
(tmp-file (make-temp-name (expand-file-name
“mail” temporary-file-directory)))
(body (org-export-string-as raw-body ‘org t))
;; 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 org-mime-preserve-breaks)
;; dvipng for inline latex because MathJax doesn’t work in mail
(org-html-with-latex ‘dvipng)
;; to hold attachments for inline html images
(html-and-images
(org-mime-replace-images
(org-export-string-as raw-body ‘html t) tmp-file))
(html-images (unless arg (cdr html-and-images)))
(html (org-mime-apply-html-hook
(if arg
(format org-mime-fixedwith-wrap body)
(car html-and-images)))))
(delete-region html-start html-end)
(save-excursion
(goto-char html-start)
(insert (org-mime-multipart
body html (mapconcat ‘identity html-images "\n”))))))
These functions are crude helpers that gather extra information about the org subtree, of which org-mime is unaware.
<ul class="org-ul">
<li>
<code>mwp-org-get-parent-headline</code> traverses the tree to the ancestor headline, because that’s what I want to set the subject to.
</li>
<li>
<code>mwp-org-attachment-list</code> is stolen directly from the Gnorb package, which looks cool, awesome ,and kinda complex; it just iterates through a subtree’s attachments and grabs URLs.
</li>
<li>
<code>mwp-send-subtree-with-attachments</code> performs the export and is bound to <code>C-c M-o</code>
</li>
</ul>
<p>
So, if I want to mail a subtree, I just <code>C-c M-o</code> and I’m almost done – the html mail is ready to go, and all org attachments are also attached to the email.
</p>
<p>
Note there are some real weaknesses here: <code>mwp-org-get-parent-headline</code> actually gets the top-level <i>ancestor</i> – which only happens to be what I want right now. Better would be to use org-element to locate the parent (and other headline attributes) directly, but I’m not sure how to do that.
</p>
<p>
Similarly, the initial greeting is generated from the current headline value – so this only works because I name my subtrees after the addressee (which I only do because of my use case).
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><span style="color: #707183;">(</span><span style="color: #a020f0;">defun</span> <span style="color: #0000ff;">mwp-org-get-parent-headline</span> <span style="color: #707183;">()</span>
“Acquire the parent headline & return." (save-excursion (org-mark-subtree) (re-search-backward "^\* “) (nth 4 (org-heading-components))))
(defun mwp-send-subtree-with-attachments () “org-mime-subtree and HTMLize” (interactive) (org-mark-subtree) (let ((attachments (mwp-org-attachment-list)) (subject (mwp-org-get-parent-headline))) (insert “Hello “ (nth 4 org-heading-components) ”,\n”) (org-mime-subtree) (insert "\nBest,\nMP.\n”) (message “subject is” ) (message subject) ;;(message-to) (org-mime-htmlize) ;; this comes from gnorb ;; I will reintroduce it if I want to reinstate questions. ;; (map-y-or-n-p ;; ;; (lambda (a) (format “Attach %s to outgoing message? “ ;; ;; (file-name-nondirectory a))) ;; (lambda (a) ;; (mml-attach-file a (mm-default-file-encoding a) ;; nil “attachment”)) ;; attachments ;; '(“file” “files” “attach”)) ;; (message “Attachments: %s” attachments) (dolist (a attachments) (message “Attachment: %s” a) (mml-attach-file a (mm-default-file-encoding a) nil “attachment”)) (message-goto-to) ))
;; add a keybinding for org-mode (add-hook ‘org-mode-hook (lambda () (local-set-key "\C-c\M-o” ‘mwp-send-subtree-with-attachments)))
;; stolen from gnorb; finds attachments in subtree (defun mwp-org-attachment-list (&optional id) “Get a list of files (absolute filenames) attached to the current heading, or the heading indicated by optional argument ID." (when (featurep ‘org-attach) (let* ((attach-dir (save-excursion (when id (org-id-goto id)) (org-attach-dir t))) (files (mapcar (lambda (f) (expand-file-name f attach-dir)) (org-attach-file-list attach-dir)))) files)))
That’s a good start, but there are still some steps to make this truly convenient. For instance, I certainly don’t want to type in students’ email addresses by hand. So I imported my contacts from thunderbird to org-contacts. This was a pain – the process was Thunderbird → Gmail (via gsync plugin) → vcard (via gmail export) → org-contacts (via Titus’s python importer). I wish there was a CSV importer for org-contacts; probably this would be easy to write but I’m so slooooowwww at coding. My org contacts live in GTD/Contacts.org
, which is set in Customize
, and org reads them on startup with this line
<div class="org-src-container">
<pre class="src src-emacs-lisp"><span style="color: #707183;">(</span><span style="color: #a020f0;">require</span> '<span style="color: #008b8b;">org-contacts</span><span style="color: #707183;">)</span>
<p>
With this single line, org-contacts now provides <kbd>TAB</kbd> completion in message-mode to headers. It’s very fast, so feels more convenient than Thunderbird.
</p>
<dl class="org-dl">
<dt>
Making it better
</dt>
<dd>
I wish I could get org-contacts to provide tab completion in my subtrees (see below). I would need to access the completion function directly and somehow set the binding for <kbd>TAB</kbd> to that completion function.
</dd>
</dl>
After I make inline comments, I fill out a grading template and attach the paper to the resultant subtree (C-c C-a a PATH). This is OK, but sometimes it would nice to be able to drag and drom the files, so I am working on these functions.
<dl class="org-dl">
<dt>
Even better
</dt>
<dd>
an even better solution be to add the attachments programmatically. The studnet papes follow a strict naming convention, so I should be able to crawl the directory and find the most recent paper with the student’s name in it… I’m worried it wil lbe too error prone though.
</dd>
</dl>
<p>
Anyway: unfortunately the following code doesn’t work right, so <b>don’t just cut and paste this code!). I *ought</b> to be able to bind the drag and drop action to a function – even several functions – and, if conditions are right, attach the dragged file to the current org header. John Kitchin describes this method <a href="http://kitchingroup.cheme.cmu.edu/blog/2015/07/10/Drag-images-and-files-onto-org-mode-and-insert-a-link-to-them/">here</a>. But I do the following instead, which is also broken right now:
</p>
<p>
Start by loading org-download, which downloads dragged images as attachments and inserts a link. (yay). THen a modification which fixes handling of file links allowing me to drag-n-drop files links onto org as attachments. Unfortunately, I can’t get org-attach to process the URI’s properly. Darn it.
</p>
<div class="org-src-container">
<pre class="src src-emacs-lisp"><span style="color: #707183;">(</span><span style="color: #a020f0;">require</span> '<span style="color: #008b8b;">org-download</span><span style="color: #707183;">)</span>
(require ‘org-attach) ;; extending the dnd functionality ;; but doesn’t actually work… (defun mwp-org-file-link-dnd (uri action) “When in `org-mode’ and URI points to local file, add as attachment and also add a link. Otherwise, pass URI and Action back to dnd dispatch” (let ((img-regexp "\(png$\|jp[e]?g$\)") (newuri (replace-regexp-in-string “file:///" "/" uri))) (cond ((eq major-mode ‘org-mode) (message “Hi! newuri: %s “ (file-relative-name newuri)) (cond ((string-match img-regexp newuri) (insert "#+ATTR_ORG: :width 300\n”) (insert (concat "#+CAPTION: “ (read-input “Caption: “) "\n”)) (insert (format "[[%s]]" uri)) (org-display-inline-images t t)) (t (org-attach-new newuri) (insert (format "[[%s]]" uri)))) ) (t (let ((dnd-protocol-alist (rassq-delete-all ‘mwp-org-file-link-dnd (copy-alist dnd-protocol-alist)))) (dnd-handle-one-url nil action uri))) )))
;; add a new function that DOESN’T open the attachment! (defun org-attach-new-dont-open (file) “Create a new attachment FILE for the current task. The attachment is created as an Emacs buffer." (interactive “sCreate attachment named: “) (when (and org-attach-file-list-property (not org-attach-inherited)) (org-entry-add-to-multivalued-property (point) org-attach-file-list-property file)) )
(defun mwp-org-file-link-enable () “Enable file drag and drop attachments." (unless (eq (cdr (assoc "^\(file\)://" dnd-protocol-alist)) ‘mwp-org-file-link-dnd) (setq dnd-protocol-alist `(("^\(file\)://" . mwp-org-file-link-dnd) ,@dnd-protocol-alist))))
(defun mwp-org-file-link-disable () “Enable file drag and drop attachments." (if (eq (cdr (assoc "^\(file\)://" dnd-protocol-alist)) ‘mwp-org-file-link-dnd) (rassq-delete-all ‘mwp-org-file-link-dnd dnd-protocol-alist)
<span style="color: #707183;">))</span>
(mwp-org-file-link-enable)