Troubling News

I’ve just learned that the being I thought was a cute dancing penguin is in fact an evil alien creature bent on destruction and domination. This creature has invaded a number of projects that I’m a part of, including Civic Tech, EDGI, and my classes “Technologies for Democracy” and “Digital History”. I can only assume that these projects have a hitherto-unrealized importance to the survival of the planet.

giphy.gif

Eternal Vigilance!

Org-Mode: Run Code Live in a Reveal slideshow with Klipse

Slide-based presentations are pretty useful and in any case de rigeur for contemporary lecturing, but it can be a REAL pain to switch form a slideshow to a code editor and then back again to a web browser to show the results of the code… Much better would be to have all the code embedded directly in your presentation. This also makes it easier to check over your code when you’re rewriting your lectures from year to year, and also helps with literate programming.

It turns out that there are actually several solutions for doing this with Org. They are:

  • directly embed JSBin in your slideshow
  • Use RevealEditor for HTML, CSS, and JS that you want to render in a web page.
  • Use the new klipse plugin for code that you want to evaluate (rather than render)

Each method has advantages and disadvantages, and each requires a certain amount of setup in your config. In this post I’m talking about using Klipse because I’ve bene able to make it actually work!

The Basics: ox-reveal

To start with, you will need to install org-reveal as well as its dependencies (org-mode and reveal.js). I install org-reveal from github instead of from ELPA, in part because I need to make modifications to both it and reveal.js.

Follow the detailed instructions until you have a working export to reveal. Make sure that you have activated the “highlight” plugin in org-reveal-plugins, which you can do with M-x customize-variable org-reveal-plugins. None of the tricks described below will work without that plugin!

Running Code in Klipse

Klipse is a web repl that supports a handful of languages, and can be extended to support more. Code is embedded in a CodeMirror instance and evaluated as you type (pretty cool).

Unfortunately CodeMirror and Reveal don’t get along very well so it’s not completely straightforward to embed Klipse directly into a reveal.js slideshow. The workaround is to replace a code-snippet tag with an iframe element containing the code in a form that klipse can read. Klipse’s author @viebel has kindly provided an example of how to do this here.

The next trick is to set up org-reveal to use this snippet. After messing around for a bit it became obvious that org-mode’s standard Export filters were going to be pretty hard to use, so I decided to rewrite some of org-reveal’s functionality:

Add Support for Third-Party Plugins

Org-reveal has excellent support for reveal.js’s “build-in” plugins, but using third-party plugins requires you to use the or-reveal~postamble variable~, which didn’t seem right to me. So I added a defcustom and a few small generic tricks to support loading third-party plugins. Then I downloaded @viebel’s klipse_reveal.js script and saved it to the plugin folder of my reveal.js installation. Finally, I set org-reveal-external-plugins to ((klipse . "{src: '%splugin/klipse_reveal.js'}")) using the customize-variable interface.

Rewrite org-reveal-src-block

At this point, the plugin loads, but it couldn’t find the exported src blocks. As I mentioned above, I was unable to find a satisfactory solution using export filters, so in the end I just rewrote the function org-reveal-src-block, which ox-reveal uses to process src blocks. I decided to keep the original <pre><code> syntax and simply add on an additional <klipse-snippet block, using the display:none CSS property for the old code block. This ensures compatibility with the reveal-editor solution (which isn’t currently working for me, but offers some other features.

Here’s my modified function:

(defun org-reveal-src-block (src-block contents info)
  "Transcode a SRC-BLOCK element from Org to Reveal.
CONTENTS holds the contents of the item.  INFO is a plist holding
contextual information."
  (if (org-export-read-attribute :attr_html src-block :textarea)
      (org-html--textarea-block src-block)
    (let* ((use-highlight (org-reveal--using-highlight.js info))
           (lang (org-element-property :language src-block))
           (caption (org-export-get-caption src-block))
           (code (if (not use-highlight)
                     (org-html-format-code src-block info)
                   (cl-letf (((symbol-function 'org-html-htmlize-region-for-paste)
                              #'buffer-substring))
                     (org-html-format-code src-block info))))
           (frag (org-export-read-attribute :attr_reveal src-block :frag))
           (code-attribs (or (org-export-read-attribute
                         :attr_reveal src-block :code_attribs) ""))
           (label (let ((lbl (org-element-property :name src-block)))
                    (if (not lbl) ""
                      (format " id=\"%s\"" lbl))))
           (klipsify  (and (assoc 'klipse org-reveal-external-plugins)
                           (member lang '("javascript" "ruby" "scheme" "clojure" "php"))))
                      )
      (if (not lang)
          (format "<pre %s%s>\n%s</pre>"
                  (or (frag-class frag info) " class=\"example\"")
                  label
                  code)
        (format
         "<div %sclass=\"org-src-container\">\n%s%s\n</div>%s"
         (if klipsify "style=\"display:none;\" " "")
         (if (not caption) ""
           (format "<label class=\"org-src-name\">%s</label>"
                   (org-export-data caption info)))
         (if use-highlight
             (format "\n<pre%s%s><code class=\"%s\" %s>%s</code></pre>"
                     (or (frag-class frag info) "")
                     label lang code-attribs code)
           (format "\n<pre %s%s>%s</pre>"
                   (or (frag-class frag info)
                       (format " class=\"src src-%s\"" lang))
                   label code)
           )
         (if klipsify (format "<klipse-snippet data-language=\"%s\">%s</klipse-snippet>"
                              lang code) ""))))))

As you can see, it now decides whether or not to “klipsify” the code aafter checking if the klipse plugin is loaded, and if it supports the language of the src block. Klipsifying code just means hiding the original code block and instead showing the klipse-snippet, which will be replaced by an iframe in javascript. I guess I could just do that work right in this same elisp function! Oh well, maybe next time.

So far, this seems to work great, though I don’t have access to the DOM of the larger document ,which is sort of a drag.

[UPDATE 2016-12-11 Sun]: However, @viebel pointed out to me that you can access the parent frame with:

var d = parent.document

Pretty cool! Note: klipse will hang if you try to type this in directly. It will only work if you pass the completed line in as part of the initial code block.

Also, this is not a solution that works for rendering HTML pages. For this we need something else. I would like to use RevealEditor, but am not quite there yet (can’t get its code to load properly). So for now I’m embedding a JSBin instance with preoaded code – I just add an iframe tag directly in my org-mode file. It’s not a s pretty or robust, and I can’t see my code when I’m writing, which is too bad, but it’s still pretty useful.

I would really like to hear what other people think! I’d especially like to hear about alternatives or improvements.

Using mu4e and org-mime together

I use org-mime in my grading system, to email my comments on student papers. One frustrating element has always been that messages sent by org-mime were never saved to my sent-mail folder. I realized just recently that this was because I had failed to set emacs’s mail-user-agent, which I now do in my initial mu4e setup:

(setq mail-user-agent 'mu4e-user-agent)

Now org-mime attempts to send messages using mu4e’s internal compose functions. Unfortunately some of the information passed by org-mime is in a format that mu4e~compose-mail doesn’t like, so I had to make some very slight changes to that function:

diff --git a/mu4e/mu4e-compose.el b/mu4e/mu4e-compose.el
index a24e74a..0c7ec3c 100644
--- a/mu4e/mu4e-compose.el
+++ b/mu4e/mu4e-compose.el
@@ -780,7 +780,14 @@ draft message."

   ;; add any other headers specified
   (when other-headers
-    (message-add-header other-headers))
+    (dolist (h other-headers other-headers)
+      (if (symbolp (car h)) (setcar h (symbol-name (car h))))
+      (message-add-header (concat (capitalize (car h)) ": " (cdr h) "\n"  ))
+      )
+    ;; (dolist (h other-headers)
+    ;;  (message-add-header h) )
+    ;;(message-add-header other-headers)
+    )

   ;; yank message
   (if (bufferp yank-action)

The commit is in its my org-mu4e-compose branch, contianing a couple of other fixes, and in its own branch on github, if prefer to pull from there. A patch has been submitted, we’ll see what dcjb thinks of it.

With these changes, org-mime now works perfectly for me.

Sending HTML Mail with mu4e

John Kitchin has a terrific post detailing some configuration/improvements to mu4e that make it easier to send html mail. This original post is really great, but I ran into quite a bit of trouble following this advice.

He uses a cool feature of mu4e, org-mu4e-compose-org-mode, which toggles the major mode of the message buffer between org-mode (when you’re in the message body) and mu4e-compose-mode (when you’re in the headers area). With a couple of custom functions, it’s easy to convert the org text to html and send a mime-multipart email from Emacs, which is quite convenient. If you add org-mu4e-compose-org-mode as a hook to mu4e-compose-mode, you can compose in html by default, which is great.

Unfortunately, org-mu4e-compose-org-mode is deprecated on account of its instability, and while John doesn’t have any problems with it, for me it was unworkable. It turns out that this “mode” isn’t really a standard emacs mode at all – instead, it’s a sly workaround that trickily adds an internal function to the ’post-command-hook in the draft buffer and switches major modes based on position. This is a neat hack, but since the function invokes the major modes directly, setting thefunction as a hook to mu4e-compose-mode leads to some funky, inadvertent looping effects. On my machine, for some reason, those effects send the underlying message-send function crazy, and instead of sending directly, I get this amazingly annoying question:

Already sent message via mail; resend? (y or n) y

On its own, that is already annoying; but worse, the sent message doesn’t get saved to my Sent folder. Instead, it’s lost completely.

Anyway, after fruitless hours of paying around with this, I realized that the problem could be fixed by adding a new hook to the mu4e compose functions (rather than to the compose mode). I’ve submitted those changes as a pull request and hopefully they will be accepted; if not, though, feel free to navigate back to my branch and pull/install mu from there. With those small changes, I now have frictionless html email working very quickly within emacs, using this small bit of code. It is at least 90% stolen:

;; this is stolen from John but it didn't work for me until I
;; made those changes to mu4e-compose.el
(defun htmlize-and-send ()
  "When in an org-mu4e-compose-org-mode message, htmlize and send it."
  (interactive)
  (when (member 'org~mu4e-mime-switch-headers-or-body post-command-hook)
    (org-mime-htmlize)
    (org-mu4e-compose-org-mode)
    (mu4e-compose-mode)
    (message-send-and-exit)))


;; This overloads the amazing C-c C-c commands in org-mode with one more function
;; namely the htmlize-and-send, above.
(add-hook 'org-ctrl-c-ctrl-c-hook 'htmlize-and-send t)

;; Originally, I set the `mu4e-compose-mode-hook' here, but
;; this new hook works much, much better for me.  
(add-hook 'mu4e-compose-post-hook
          (defun do-compose-stuff ()
            "My settings for message composition."
            (org-mu4e-compose-org-mode)))

It feels great to have gotten this far. There are still some small things I’d like to be able to improve; and I think I would like to add a few wrapper functions and keybindings to my setup, but for now I’m pretty efficient.

Still missing:

  • a better HTML viewing interface! right now, html mails render as mostly text – it would be nice to have a rendered html message by default in emacs. This is an issue several times a day when I get promotional emails from organizations I work with – usually the html part is really important. I can access these in the browser but it’s comparatively awkward.
  • a way to forward these html emails to someone intact – right now, the html parts are discarded. No idea how hard it would be to do this.

Thanks John and Dirk-Jan for these great tools!

It’s Time to Finally Take Encryption Seriously!

We have known for a long time that the American intelligence agencies don’t flinch at violating our privacy. Edward Snowden’s revelations have forced some small reforms at the NSA and elsewhere; but those small improvements will doubtless be rolled back, and XKeyscore, PRISM and, ECHELON will be replaced by even more invasive and dangerous technologies. They will be controlled by a government with strong ties to White Nationalists, militias, and various hate groups. In other times and places, governments have circulated death lists of dissenters to paramilitary organizations. I don’t think that’s likely in the USA; but I also think that as citizens, we need to act to protect ourselves from the abuse of power.

The only effective tool against government surveillance is end-to-end encryption of communication. Even encryption is likely to be inadequate against a targeted attack by the NSA’s supercomputers and tactical operations; but when it comes to the mass collection of data, properly-practiced encryption protects you from being targeted for more detailed analysis. As a by-product, encrypting your communications makes it a little bit harder (but not much!) for corporations to track your preferences, and also adds a little bit of protection from other malicious agents.

The basic building blocks of encrypted communication have been available for a long time, but until recently encryption was clumsy and error-prone. Encryption of email is still somewhat challenging. In the last year, though, two excellent solutions, and one adequate one, have emerged for messaging. I’ll talk about all these in a minute, but first let’s quickly review how encryption works.

Encryption Review

Almost all contemporary encryption is “dual-key” – each person in a conversation has both a private and a public “key”. If I want to send you a message, I encrypt it with your public key. After that, even I can’t read my message – only you can, with your private key, which you don’t show to anyone.

This sounds simple, but it is actually quite difficult to implement encrypted communication in a simple, easy-to-understand interface. Software developers have struggled with this for a long time. It’s worth reading a little more about this:

Messaging

The messaging landscape has been changing rapidly. After a long delay, there is now really good encryption available, but some of the interfaces are a little confusing or misleading.

  • Signal allows you to send text messages and make phone calls with full, end-to-end, dual-key encryption. You should switch to it immediately. You won’t be able to make encrypted phone calls over the regular phone network – you will need to use data instead.
  • As of April, WhatsApp now offers dual-key encryption by default, including a key-verification interface. Among the mainstram apps, WhatsApp is probably the best option, but it’s not perfect, and Signal is better if you can use it.
  • In June, Facebook Messenger enabled end-to-end encryption. Encryption is turned off by default and has to be turned on for every conversation, so this is not as good as WhatsApp, but if your social media life mostly moves through Facebook, you can enable encryption, and it’s worth doing.
  • Google’s new Allo messenging app also permits end-to-end encryption, but it’s not on by default, and it’s a bit confusing to use. Signal is still ldefinitely preferable.

Email

Email is tricky. For most people, Thunderbird plus EnigMail is probably the best option, but it is confusing to use and it also “leaks” information (as will always be the case for email sent through traditional channels). Various projects to fix email were announced in the wake of the Snowden revelations, but the onse I know about have mostly stopped development. It’s a hard nut to crack.

Still: you should aim to encrypt a relatively high percentage of your emails if you can. It’s not good enough to just encrypt a few; the important ones need to be masked by the trivial ones. And encrypting your emails also helps to protect the journalists, activists, ando thers who need to encrypt their communication for their own protection. We encrypt not just for ourselves, but to preserve essential freedoms for others.

Over the next few months I hope to transition to encryption in most of my messaging; that means that, if you want to be in touch with me online, you’ll need to start figuring this stuff out too. Let me know what I can do to help!

Using the Sikkim Website to keep in touch

OK, we’re getting down to the wire. This post is for the Sikkim trip students….

I know most of you will keep in touch with friends and family via Facebook & other social media. However, I’m hoping that we will also use the group website as a way to keep in touch while we’re gone. As you know, I’ve set the site up as a major part of our intellectual output for this project, and yo all have accounts that should let you write and publish blog posts & webpages.

Here are just a few short tips on how to use the site. I’m hoping those of you who are familiar with WordPress can help the others.

Getting Started

Use your username and password to get into the site at the admin/login page. To create a new journal entry, go to Posts/New (or tap the + button in the top menu. Compose your message in the editor box, add pictures using the “add media’ button in the editor toolbar, press ”Publish“, and you’re done.

Location Data

Since we’re thinking of this as “place-based” education, location data is pretty important to us. You can add three kinds of location information to your post (underneath the editor box):

GPX Tracks
These are recordings of where you’ve been (discussed in my last post). If you attach one of these in the “GPX File” upload box, it will display in your post (but not in the list of all the posts…). We’ll use these a lot when we’re in Sikkim.
KML FIles
There’s a lot of stuff these can do, but the main reason I included them is that in some cases they can be easier to export than gpx tracks.
Co-ordinates
You can use this to indicate where you are in the moment that you’re writing your post. This is what will show up in the “archive” and “our journey” pages (yo can look at the test pages for examples of how it will look).

I strongly encourage you to use the geodata options here! It’ll help our site out a lot.

Featured Image

You’ll notice that each post has one special image associated with it, that is used as a packground image in a couple of places. This is the “featured image”, which you set in a box on the lower right of the writing interface page (or, if you’re using your phone, just really far down in the interface). I’d really like it if every post had a featured image, so try to choose a good one!

Showing a post on the “Our Journey” Page

“Our Journey” is, as it were, the “official” record of our trip. Each day one or two of us will write a special post about the day’s events; it’s expected to be perhaps a little more substantive or public-facing than your regular travel diary. You can move a post into the “Our Journey” feed by adding the “Our Journey” category – in the “Category” box.

Youu may also notice the “Tags” box – we an talk later about how to make use of tags on the site.

See yo all in India!

Android Mapping Apps

For our trip to Sikkim, I would like students to have a way to map our progress on their phones so that it can be displayed on our website. I’ve spent a bit of time looking at various Android apps (I don’t have an iPhone) and have some thoughts about how to do this; here are my notes, intended mostly for the students but also potentially for other people looking to do something similar.

Offline Map Display

Most mapping applications rely on a continuous data feed, which will not be available to us in Sikkim. So, what applications offer offline viewing?

Google Maps allows downloading of areas for offline use. Here are the official instructions for doing so. The Sikkim area takes about 500mb of space so be aware of that.

OSMAnd is an interesting alternative. It is mostly designed for offline use. If you buy the countour Lines Plugin for $1.99, you can add topographic map contour lines to your map, which is pretty cool. I kind of love it myself, and it’s what I’ll be using. The interface is perhaps a little less intuitive than the Google Maps interface, but I’m getting used to it. You will need to download the world base map, as well as the detailed map for all of India (if you have enough space on your phone) or the information for Sikkim only (if you don’t have enough room). The contour map is a separate download, and to be able to use it, you will need to purchase the plugin linked to above.

The free version of the app allows only 7 downloads in total, but we shouldn’t need more than that for our trip. I myself bought the full paid version but that shouldn’t be necessary IIUC.

Data Storage

If your phone supports the use of a removable microSD card (most do, mine doesn’t), I would highly recommend buying one. You can get an 8gb card for $4.99 at Canada Computers on College St, and that will give you plenty of space for everything you want to put on it. Buy it before you install the app, so that you can choose to store your data on the card rather than the phone’s internal memory.

GPS Tracking

It’s nice to have a map on your phone, but most of the time you won’t be using the map at all. The real reason for this post is that we’d like to place at least one of you on “backup tracking” duty every day of the trip. For this you need some kind of tracking software, which can generate files in either .gpx or .klm format; and you also need a way to get the data out of the phone and into one of our tablets for writing.

There are a number of apps that let you do this. OSMAnd+ (see above) lets you create gpx tracks, though it doesn’t let you rename or share them direcly, which is a bit of a pain. Here are some alternatives:

GPS Logger is a very simple, lightweight app that only records gpx/kml tracks for you. It doesn’t display a map or anything. Its main advantage is that it doesn’t use much energy compared to other apps.

Geo tracker is a pretty user-friendly app. Unfortunately it can’t make use of OSMAnd’s downloaded maps so I’m not sur what the display will look like when you don’t have any data.

There are http://wiki.openstreetmap.org/wiki/Android#Track-making_features]][zillions of other apps]]. Hopefully one of the above works for you but if not the Open Stree Map wiki is likely your best bet.

Getting your data out REVISED

Unfortunately our tablet doesn’t support NFC!! So you will need to transfer your files manually onto a USB thumb drive. To do this you will need a file manager. There are lots of them; you can find lists here and here. I don’t like any of them all that much but they should work, at least a little. If you have a brand-new version of Android you can also use the built-in file manager. In general it’s something of a pain, hopefully we will only be doing that for a few days when we’re completely off the grid.

NEW: I had a lot of trouble with most of the file managers, and ended up installing ES File Explorer. Now I’m able to move files easily onto an external thumb drive attached to my phone with an On The Go cable (I’m bringing 2 so we should be fine on the trip). Note that some users have reported concerns about personal data being stored on Baidu, the Chinese Google. If you have anything on your phone that you don’t want the Chinese government to know about, you may want to consider another option.

However: ES File Explorer is MUCH less painful than other options. With “ES Save To”, you can save files directly to an external USB drive attached to your phone, which can then be transferred easily to a tablet or compter.

It’s still a pain to locate your .gpx files.

Elfeed

I’ve become pretty addicted to the Elfeed RSS reader. However, I haven’t made much use of its tagging capabilities until recently. I noticed that, whenever I thought I might want to keep track of the content of a blog, I would mark it “unread” after I’d read it, to make sure I didn’t lose it in the deep mists of time. This worked for the first 4 months or so, but now my list of unreads is growing enormously. So I wanted to have a “starred” tag which would do this work for me. I also wanted a keybinding that would apply the tag right away.

The code below does that for me.

(eval-after-load 'elfeed-search 
  '(define-key elfeed-search-mode-map (kbd "<tab>") 'mwp/elfeed-star))

(defun mwp/elfeed-star ()
  "add a star tag to marked"

  (interactive)
  (elfeed-search-tag-all (list starred))
  )

(defun mwp/elfeed-star ()
  "Apply TAG to all selected entries."
  (interactive )
  (let* ((entries (elfeed-search-selected))
         (tag (intern "starred")))

    (cl-loop for entry in entries do (elfeed-tag entry tag))
    (mapc #'elfeed-search-update-entry entries)
    (unless (use-region-p) (forward-line))))

I also wanted a visual cue to tell me the tagging had been successful – now easy to do, thanks to a very recent commit. I filed an issue as a question – can I do this? – and within 24 hours skeeto had added the functionality. First you have to define an appropriate face for the tag:

(defface elfeed-search-starred-title-face
 '((t :foreground "#f77"))
 "Marks a starred Elfeed entry.")

And then simply add an entry to elfeed-search-face-alist. You can do this via Customize, or follow the instructions in the README, which instruct you to (push '(starred elfeed-search-starred-title-face) elfeed-search-face-alist).

All of this works great for me, and simplifies things quite a bit.

Note Taking with PDF Tools

NOTE: This post has been modified as of 2015-11-22 Sun – the new code is a little cleaner, and I think the discussion a little fuller.

Almost all of my job-related reading is now done on a screen. There are still disadvantages – I find it much harder to concentrate when reading online – but in other ways it is markedly more convenient.

In particular, it is now much easier to assemble quotations from sources; and now that I’ve found PDF Tools, it has become even easier. I’ve just started to use it to extract annotations from my PDF’s, and it works much better than the lousy command-line hack I was using previously.

As we’re mid-semester, most of my reading is for classes I teach. My current workflow is as follows:

  • Assemble the relevant readings in a Dropbox-synced directory (ClassName/Readings)
  • Using Repligo Reader (apparently no longer available in the app store?), highlight the passages I’m interested in.
  • execute code block (see below) to insert org headings with all highlights from one or more pdfs
  • Assemble reveal.js lecture presentation around those highlights, using org-reveal or Pandoc.

Activating PDF Tools

Begin by installing pdf-tools and org-pdfview from ELPA with package-list~packages or package-install.

Then make sure they are activated by adding these lines in your init file:

(pdf-tools-install)
(eval-after-load 'org '(require 'org-pdfview))
(add-to-list 'org-file-apps '("\\.pdf\\'" . org-pdfview-open))
(add-to-list 'org-file-apps '("\\.pdf::\\([[:digit:]]+\\)\\'" . org-pdfview-open))

Switching to PDF Tools for annotating and extracting PDF’s

Last month Penguim proposed some changes in a pull request, that export annotations as a set of org headlines. It’s potentially very interesting but not quite what I want to do, so I modified this code. pdf-annot-markups-as-org-text extracts the text of an annotation (stored as the subject attribute in an alist), and also generates a link back to the page in the pdf. mwp/pdf-multi-extract is just a helper function that makes it easier to construct elisp source blocks the way I’m used to doing:

;; modified from https://github.com/politza/pdf-tools/pull/133 

(defun mwp/pdf-multi-extract (sources)
  "Helper function to print highlighted text from a list of pdf's, with one org header per pdf, 
and links back to page of highlight."
  (let (
        (output ""))
    (dolist (thispdf sources)
      (setq output (concat output (pdf-annot-markups-as-org-text thispdf nil level ))))
    (princ output))
  )

;; this is stolen from https://github.com/pinguim06/pdf-tools/commit/22629c746878f4e554d4e530306f3433d594a654
(defun pdf-annot-edges-to-region (edges)
  "Attempt to get 4-entry region \(LEFT TOP RIGHT BOTTOM\) from several edges.
We need this to import annotations and to get marked-up text, because annotations
are referenced by its edges, but functions for these tasks need region."

  (let ((left0 (nth 0 (car edges)))
        (top0 (nth 1 (car edges)))
        (bottom0 (nth 3 (car edges)))
        (top1 (nth 1 (car (last edges))))
        (right1 (nth 2 (car (last edges))))
        (bottom1 (nth 3 (car (last edges))))
        (n (safe-length edges)))
    ;; we try to guess the line height to move
    ;; the region away from the boundary and
    ;; avoid double lines
    (list left0
          (+ top0 (/ (- bottom0 top0) 2))
          right1
          (- bottom1 (/ (- bottom1 top1) 2 )))))

(defun pdf-annot-markups-as-org-text (pdfpath &optional title level)
  "Acquire highligh annotations as text, and return as org-heading"

  (interactive "fPath to PDF: ")  
  (let* ((outputstring "") ;; the text to be returned
         (title (or title (replace-regexp-in-string "-" " " (file-name-base pdfpath ))))
         (level (or level (1+ (org-current-level)))) ;; I guess if we're not in an org-buffer this will fail
         (levelstring (make-string level ?*)) ;; set headline to proper level
         (annots (sort (pdf-info-getannots nil pdfpath)  ;; get and sort all annots
                       'pdf-annot-compare-annotations))
         )
    ;; create the header
    (setq outputstring (concat levelstring " Quotes From " title "\n\n")) ;; create heading

    ;; extract text
    (mapc
     (lambda (annot) ;; traverse all annotations
       (if (eq 'highlight (assoc-default 'type annot))
           (let* ((page (assoc-default 'page annot))
                  ;; use pdf-annot-edges-to-region to get correct boundaries of highlight
                  (real-edges (pdf-annot-edges-to-region
                               (pdf-annot-get annot 'markup-edges)))
                  (text (or (assoc-default 'subject annot) (assoc-default 'content annot)
                            (replace-regexp-in-string "\n" " " (pdf-info-gettext page real-edges nil pdfpath)
                                                      ) ))

                  (height (nth 1 real-edges)) ;; distance down the page
                  ;; use pdfview link directly to page number
                  (linktext (concat "[[pdfview:" pdfpath "::" (number-to-string page) 
                                    "++" (number-to-string height) "][" title  "]]" ))
                  )
             (setq outputstring (concat outputstring text " ("
                                        linktext ", " (number-to-string page) ")\n\n"))
             )))
     annots)
    outputstring ;; return the header
    )
  )

Using in Org with a Source Block

Now it’s more or less trivial to quickly generate the org headers using a source block:

#+BEGIN_SRC elisp :results output raw :var level=(1+ (org-current-level))
(mwp/pdf-multi-extract '(
                   "/home/matt/HackingHistory/readings/Troper-becoming-immigrant-city.pdf"  "/home/matt/HackingHistory/readings/historical-authority-hampton.pdf"))

#+END_SRC

And the output gives something like

#+BEGIN_EXAMPLE

Quotes From Troper becoming immigrant city

Included in the Greater Toronto Area multiethnic mix are an estimated 450,000 Chinese, 400,000 Italians, and 250,000 African Canadians, the largest component of which are ofCar- ibbean background, although a separate and distinct infusion of Soma- lis, Ethiopians, and other Africans is currently taking place. (Troper becoming immigrant city, 3)

Although Toronto is Canada’s leading immigrant-receiving centre, city officials have neither a hands-on role in immigrant selection nor an official voice in deciding immigration policy. In Canada, immigration policy and administration is a constitutional responsibility of the fed- eral government, worked out in consultation with the provinces. (Troper becoming immigrant city, 4)

#+END_EXAMPLE

Alternative: Temporary buffer with custom link type

An alternative workflow would be to pop to a second, temporary buffer and insert the annotations there; one could do this with a custom link type. PDF-Tools already has a mechanism for listing annotations in a separate buffer, but it’s not designed for quick access to all annotations at once. Anyway, here’s one way to do this; I’m not really using it at the moment.

(org-add-link-type "pdfquote" 'org-pdfquote-open 'org-pdfquote-export)

(defun org-pdfquote-open (link)
  "Open a new buffer with all markup annotations in an org headline."
  (interactive)
  (pop-to-buffer
   (format "*Quotes from %s*"
           (file-name-base link)))
  (org-mode)
  (erase-buffer)
  (insert (pdf-annot-markups-as-org-text link nil 1))
  (goto-char 0)
  )

(defun org-pdfquote-export (link description format)
  "Export the pdfview LINK with DESCRIPTION for FORMAT from Org files."
  (let* ((path (when (string-match "\\(.+\\)::.+" link)
                 (match-string 1 link)))
         (desc (or description link)))
    (when (stringp path)
      (setq path (org-link-escape (expand-file-name path)))
      (cond
       ((eq format 'html) (format "<a href=\"%s\">%s</a>" path desc))
       ((eq format 'latex) (format "\href{%s}{%s}" path desc))
       ((eq format 'ascii) (format "%s (%s)" desc path))
       (t path)))))

(defun org-pdfquote-complete-link ()
  "Use the existing file name completion for file.
Links to get the file name, then ask the user for the page number
and append it."                                  

  (replace-regexp-in-string "^file:" "pdfquote:" (org-file-complete-link)))

I’ve also added two bindings to make highlighting easier from the PDF buffer:

(eval-after-load 'pdf-view 
                    '(define-key pdf-view-mode-map (kbd "M-h") 'pdf-annot-add-highlight-markup-annotation))
(eval-after-load 'pdf-view 
                    '(define-key pdf-view-mode-map (kbd "<tab>") 'pdf-annot-add-highlight-markup-annotation))

All of this is getting me very close to using Emacs for all my PDF work. I am doing maybe 50% of my PDF work in Emacs instead of on my tablet. It’s incredibly convenient, although I still find it a little harder to concentrate on my laptop than on the tablet (for reasons ergonomic, optical, and psychological). Here are the remaining papercuts from my perspective:

  • Highlighting text with the mouse is more awkward and less intimate than using my fingertip on a laptop. I often find mouse movement a little awkward in Emacs, but pdf-view purposely relies on the mouse for movement (for good reasons).
  • Scrolling in pdf-view is also a bit awkward, and there’s no “continuous” mode as one might find in Evince or acroread. Again, I often find scrolling an issue in Emacs, so this might not be so easy to fix.
  • Finally, the laptop screen is just harder on my eyes than my high-res tablet. pdf-view hasa “midnight mode” which makes it a little easier to read, but it’s not quite enough.

So, for the time being I will probably do much of my reading on the tablet. But for short pieces and for review (e.g., papers that I’m reading for the third year in a row in a graduate seminar…) PDF Tools is now my main interface. Which is pretty sweet.

Todo

UPDATE: I would like to extend the pdfview link type (in org-pdfview) to permit me to specify the precise location of an annotation, so I can jump precisely to that part of the page. This has now been done and the code above has been updated to reflect the new syntax.

UPDATE: Also, now that I think about it, it might be interesting to just have a link type that pops up a temporary buffer with all of the annotations; I could then cut and paste the annotations into the master document. This might be even more convenient. OK, I’ve implemented this, see above!

Update 2015-11-22 Sun: I’ve cleaned up some of the code, and added a bit more commentary at the end.

Mailing subtrees with Attachments

This is pretty exciting; I’ve figured out how to quickly send org-mode subtrees as MIME-encoded emails. That means that, essentially, I can write in org, as plain text, and very quickly export to HTML, add attachments, and send. The exciting part about this for me is that it should streamline my communications with students, while also letting me stay in Org and keep my records in order. Let’s walk through the process.

Use-case

For the moment, I still use Thunderbird as my primary MUA. It’s pretty easy to use, minimal configuration compared to all things Emacs, and if something goes wrong with it I don’t have to quit Emacs (!), just Thunderbird.

In some cases, though, Thunderbird makes for an awkward workflow. That’s certainly the case for grading, whih has many, poorly-integrated elements. To mark an assignment I need to:

  • log in to Blackboard (in Firefox; I wonder if I could do that in Emacs?)
  • download the set of student papers (one at a time, 2 clicks per paper) to Downlads
  • move papers to a directory (usually ~/COURSENAME/Grading/ASSIGNMENTNAME )
  • Read papers in LigreOffice, comment inline
  • record mark in Libreoffice Calc spreadsheet
  • email paper back to student with comments
  • upload marks back into Blackboard
  • find a place to archive the student paper in case I need it later, e.g. for a contested grade.

The while process basically sucks. I spend maybe 20% of my time fussing with paths and mouse clicks and email addresses. So I am experimenting with moving as much of this process into Emacs. So far, I don’t think there’s any way at all to bulk-download the papers – that sucks, but I can live with it I guess (I have to!). So I start the optimization at the point where I have all my papers ready to go in a subdir.

Org-mime

Org-mime is the library that allows org buffers and other elements to be quickly converted to HTML and prepared for multi-part messaging. Load it and set it up (see http://orgmode.org/worg/org-contrib/org-mime.html):

;; enable HTML email from org
(require 'org-mime)
;; setup org-mime for wanderlust
;; (setq org-mime-library 'semi)
;; or for gnus/message-mode
(setq org-mime-library 'mml)

;; easy access to htmlize in message-mode
(add-hook 'message-mode-hook
          (lambda ()
            (local-set-key "\C-c\M-o" 'org-mime-htmlize)))

;; uncomment this to use the org-mome native functions for htmlizing.
;; (add-hook 'org-mode-hook
;;           (lambda ()
;;             (local-set-key "\C-c\M-o" 'org-mime-org-buffer-htmlize)))

;; uncomment to displyay src blocks with a dark background
;; (add-hook 'org-mime-html-hook
;;           (lambda ()
;;             (org-mime-change-element-style
;;              "pre" (format "color: %s; background-color: %s; padding: 0.5em;"
;;                            "#E6E1DC" "#232323"))))

;; pretty blockquotes
(add-hook 'org-mime-html-hook
          (lambda ()
            (org-mime-change-element-style

             "blockquote" "border-left: 2px solid gray; padding-left: 4px;")))

Fix htmlization

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.

(defun org-mime-htmlize (&optional arg)
"Export a portion of an email body composed using `mml-mode' to
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"))))))

Actually perform the export!

These functions are crude helpers that gather extra information about the org subtree, of which org-mime is unaware.

  • mwp-org-get-parent-headline traverses the tree to the ancestor headline, because that’s what I want to set the subject to.
  • mwp-org-attachment-list 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.
  • mwp-send-subtree-with-attachments performs the export and is bound to C-c M-o

So, if I want to mail a subtree, I just C-c M-o and I’m almost done – the html mail is ready to go, and all org attachments are also attached to the email.

Note there are some real weaknesses here: mwp-org-get-parent-headline actually gets the top-level ancestor – 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.

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).

(defun mwp-org-get-parent-headline ()
  "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)))

Contacts

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

(require 'org-contacts)

With this single line, org-contacts now provides TAB completion in message-mode to headers. It’s very fast, so feels more convenient than Thunderbird.

Making it better
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 TAB to that completion function.

Adding Attachments with Drag & Drop

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.

Even better
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.

Anyway: unfortunately the following code doesn’t work right, so don’t just cut and paste this code!). I *ought 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 here. But I do the following instead, which is also broken right now:

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.

(require 'org-download)
(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)

    ))

(mwp-org-file-link-enable)

Exporting org-files to a temporary location

I have a private journal, which lives in an encrypted file in a Dropbox-backed-up directory. I use html export to examine the contents sometimes – there are some big old tables that are hard to read in org-mode – but I don’t want the html file to end up in Dropbox.

So I just copied the definition of org-export-html-as-html and made trivial modifications. There’s probably a better way to do this.

;; export html to tmp dir
(defun mwp-org-html-to-tmp
    (&optional async subtreep visible-only body-only ext-plist)
  "Export current buffer to a HTML file in the tmp directory.

If narrowing is active in the current buffer, only export its
narrowed part.

If a region is active, export that region.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting file should be accessible through
the `org-export-stack' interface.

When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

When optional argument BODY-ONLY is non-nil, only write code
between \"<body>\" and \"</body>\" tags.


EXT-PLIST, when provided, is a property list with external
parameters overriding Org default settings, but still inferior to
file-local settings.

Return output file's name."
  (interactive)
  (let* ((extension (concat "." (or (plist-get ext-plist :html-extension)
                                    org-html-extension
                                    "html")))
;; this is the code I've changed from the original function. 
         (file (org-export-output-file-name extension subtreep "/home/matt/tmp/"))

         (org-export-coding-system org-html-coding-system))
    (org-export-to-file 'html file
      async subtreep visible-only body-only ext-plist)
    (org-open-file file)))

(org-defkey org-mode-map
            (kbd "C-c 0") 'mwp-org-html-to-tmp)

Creating and Publishing Presentations with Org-reveal

For several years, I’ve been using Org-mode to compose slides for my lectures. This method is great, because I get to work in plain-text and focus on the content of my lectures rather than animations; but it’s meant that when I want to share my presentations with others, there’s a certain amount of work involved as I move from a local copy on my computer to a web-based version. (This has largely been an issue because I sometimes compose my lectures sitting in a café with lousy Internet, and I sometimes give my lectures in a horrible classroom at U of T with terrible Internet reception.) I’ve now largely solved this problem, though there is hopefully an improvement coming down the pipe which will make it even easier.

Org-mode has the capacity to export to a number of slide-like formats, including the LaTeX-based Beamer format, which also makes good PDF presentations, a couple of Emacs-based presentation tools, and a number of HTML5 formats. Since I teach about the web all the time, the HTML5 formats have always been the most appealing to me.

Org-Reveal Setup

I have used and still very much like deck.js (exporter here), but have recently switched to org-reveal, which I really like a lot. It’s not part of the official org distribution, so installation and setup are a little more involved, but not difficult. I just cloned the org-reveal and reveal.js repositories:

cd ~/src
git clone https://github.com/hakimel/reveal.js.git
git clone https://github.com/yjwen/org-reveal.git

and put this in my emacs-init.el:

;; org-reveal
(add-to-list 'load-path "~/src/org-reveal")
(require 'ox-reveal)
;; set local root
(setq org-reveal-root "file:///home/matt/src/reveal.js")

That’s all that’s needed to get export working! I find it’s really fast to prepare lectures.

Publishing

That’s great for giving lectures, and is all I really need at 9:55 when I’m trying to type my lecture and walk to class at the same time. But after lecture I want to put my slides somewhere my students can see them. Even if I wanted to, it would be impossible for me to post to Blackboard, which turns these files into garbage. What I want to do is publish them to the web; but I need to make sure that all the JS and CSS links are pointing to the web-based libraries and not my local copies, which of course no one but me can see. To do this I had to make one small change to org-reveal.el, which I have submitted as a pull request. This creates a new variable, org-reveal-extra-css, which I can refer to in my own functions.

Then I use org-mode’s fantastic built-in Publishing functions to push my slides to a public website. Publishing allows you to perform an export on many files, and customize the output in powerful ways that are mostly beyond me, actually. Still, I have a setup that I like a lot.

First, my org-publish-project-alist, which defines the publishing targets. Note especially the top part, which defines “meta-projects”: for instance, I can publish all the slides and source files for all my classes with one command, M-x org-publish-projects [RET] courses.

(setq org-publish-project-alist
      '(
        ("courses"
         :components ("dh" "rlg231"))
        ("rlg231"
         :components ("rlg231-lecture-slides" "rlg231-lecture-source"))
        ("dh"
         :components ("digital-history-lecture-slides" "digital-history-lecture-source"))

        ("rlg231-lecture-slides"
         :base-directory "~/RLG231/Lectures/"
         :base-extension "org"
         :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/RLG231/Lectures/Slides"
         :recursive t
         :publishing-function mwp-org-reveal-publish-to-html
         :preparation-function nil 
         :completion-function nil
         :headline-levels 4             ; Just the default for this project.
         :exclude "LectureOutlines.org"
         :exclude-tags note noexport
         :auto-preamble t)

        ("rlg231-lecture-source"
         :base-directory "~/RLG231/Lectures/"
         :base-extension "org"
         :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/RLG231/Lectures/Source"
         :recursive t
         :publishing-function org-org-publish-to-org
         :preparation-function nil
         :completion-function nil
         :headline-levels 4             ; Just the default for this project.
         :exclude "LecturePlans.org"
         ;; :exclude "LectureOutlines.org"
         :exclude-tags note noexport
         :auto-preamble t)

        ("digital-history-lecture-source"
         :base-directory "~/DH/Lectures"
         :base-extension "org"
         :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/DigitalHistory/Lectures/Source"
         :recursive t
         :publishing-function org-org-publish-to-org
         :preparation-function 
         :completion-function 
         :headline-levels 4             ; Just the default for this project.
         ;; :exclude "LecturePlans.org"
         :exclude "LectureOutlines.org"
         :exclude-tags note noexport
         :auto-preamble t)

        ("digital-history-lecture-slides"
         :base-directory "~/DH/Lectures"
         :base-extension "org"
         :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/DigitalHistory/Lectures/Slides"
         :recursive t
         :publishing-function mwp-org-reveal-publish-to-html
         :preparation-function 
         :completion-function 
         :headline-levels 4             ; Just the default for this project.
         ;; :exclude "LecturePlans.org"
         :exclude "LectureOutlines.org"
         :exclude-tags note noexport
         :auto-preamble t)

        ;; ("newone-lecture-slides"
        ;;  :base-directory "~/NewOne/Lectures/"
        ;;  :base-extension "org"
        ;;  :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/NewOne/Lectures"
        ;;  :recursive t
        ;;  :publishing-function org-deck-publish-to-html
        ;;  :headline-levels 4             ; Just the default for this project.
        ;;  :exclude-tags note noexport
        ;;  :auto-preamble t)

        ;; ("newone-lecture-notes"
        ;;  :base-directory "~/NewOne/Lectures/"
        ;;  :base-extension "org"
        ;;  :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/NewOne/Lectures-with-notes"
        ;;  :recursive t
        ;;  :publishing-function org-html-publish-to-html
        ;;  :headline-levels 4             ; Just the default for this project.
        ;;  :exclude-tags noexport
        ;;  :auto-preamble t)

        ;;  ("newone-images"
        ;;        :base-directory "~/NewOne/Images/"
        ;;        :base-extension "jpg\\|gif\\|png"
        ;;        :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/NewOne/Images"
        ;;        :publishing-function org-publish-attachment)

        ;;  ("newone" :components ("newone-lecture-slides" "newone-lecture-notes" "newone-images") )

        ;;  ("presentations"
        ;;   :base-directory "~/Dropbox/Work/Talks/"
        ;;   :base-extension "org"
        ;;   :publishing-directory "/ssh:matt@shimano:/var/www/sandbox/Presentations"
        ;;   :headline-levels 4 ; just the default for this project
        ;;   :exclude-tags noexport
        ;;   :auto-preamble t
        ;;   :publishing-function mwp-org-deck-publish-to-html
        ;;   ;; :completion-function mwp-update-published-paths
        ;;   )

      ))

Notice the publishing function, which is set to mwp-org-deck-publish-to-html. This is a simple function that resets the base url and extra-css values to web-based ones before publication, so that the presentations work when online. Notice I’ve also reset the deck.js base url, in case I ever decide to change back to deck.

(defun mwp-org-reveal-publish-to-html (plist filename pub-dir)
  "Publish an org file to reveal.js HTML Presentation.
FILENAME is the filename of the Org file to be published.  PLIST
is the property list for the given project.  PUB-DIR is the
publishing directory. Returns output file name."
  (let ((org-deck-base-url "http://sandbox.hackinghistory.ca/Tools/deck.js/")
        (org-reveal-root "http://sandbox.hackinghistory.ca/Tools/reveal.js/")
        (org-reveal-extra-css "http://sandbox.hackinghistory.ca/Tools/reveal.js/css/local.css"))

    (org-publish-org-to 'reveal filename ".html" plist pub-dir))
  )

And that’s it, magic!

Still to do

I like this a lot, but there are a couple of pieces I’d still like to implement.

Fix all local file URL’s
I’d like to write a function to take a final pass through all the links and change file:/// links to HTML relative links. That will take some work though.
Export as standalone
There is work underway to allow presentations to be generated as stand-alone files that can be, e.g, sent by email. I like this idea a lot. See this Github issue.
Standardize notes, fragments
Every time I switch from one presentation framework to another, I have to learn a whole different syntax for things like fragments (bits of content that don’t appear on the slide immediately, but are instead stepped through) and speaker notes (that don’t appear on the slide that your viewers see, but are only visible to you in some kind of preview mode). It would be great if the various slide modes could work towards a common syntax for these things. If I have time, energy, and skills, I would like to help develop this a little.

See my slides

If you want to see some examples of the end product, here is a link to my Digital History lecture archive (still being built!). Many of my course materials are also online at Github.