Slideshow bugfixes and sync with ox-html function name changes.
[org-mode/org-kjn.git] / contrib / lisp / ox-deck.el
blob5d253695379e8e04edde68a9ae7eedc1e7226a55
1 ;;; ox-deck.el --- deck.js Presentation Back-End for Org Export Engine
3 ;; Copyright (C) 2013 Rick Frankel
5 ;; Author: Rick Frankel <emacs at rickster dot com>
6 ;; Keywords: outlines, hypermedia, slideshow
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation, either version 3 of the License, or
11 ;; (at your option) any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program. If not, see <http://www.gnu.org/licenses/>.
21 ;;; Commentary:
23 ;; This library implements a deck.js presentation back-end for the Org
24 ;; generic exporter.
26 ;; Installation
27 ;; -------------
28 ;; Get a copy of deck.js from http://imakewebthings.com/deck.js/ or
29 ;; the gitub repository at https://github.com/imakewebthings/deck.js.
31 ;; Add the path to the extracted code to the variable
32 ;; `org-deck-directories' There are a number of customization in the
33 ;; org-export-deck group, most of which can be overrriden with buffer
34 ;; local customization (starting with DECK_.)
36 ;; See ox.el and ox-html.el for more details on how this exporter
37 ;; works (it is derived from ox-html.)
39 (require 'ox-html)
40 (eval-when-compile (require 'cl))
42 (org-export-define-derived-backend deck html
43 :menu-entry
44 (?d "Export to deck.js HTML Presentation"
45 ((?H "To temporary buffer" org-deck-export-as-html)
46 (?h "To file" org-deck-export-to-html)
47 (?o "To file and open"
48 (lambda (a s v b)
49 (if a (org-deck-export-to-html t s v b)
50 (org-open-file (org-deck-export-to-html nil s v b)))))))
51 :options-alist
52 ((:html-link-home "HTML_LINK_HOME" nil nil)
53 (:html-link-up "HTML_LINK_UP" nil nil)
54 (:html-mathjax "HTML_MATHJAX" nil "" space)
55 (:html-postamble nil "html-postamble" nil t)
56 (:html-preamble nil "html-preamble" nil t)
57 (:html-style-extra "HTML_STYLE" nil org-html-style-extra newline)
58 (:html-style-include-default "HTML_INCLUDE_DEFAULT" nil nil)
59 (:html-style-include-scripts "HTML_INCLUDE_SCRIPTS" nil nil)
60 (:deck-base-url "DECK_BASE_URL" nil org-deck-base-url)
61 (:deck-theme "DECK_THEME" nil org-deck-theme)
62 (:deck-transition "DECK_TRANSITION" nil org-deck-transition)
63 (:deck-include-extensions "DECK_INCLUDE_EXTENSIONS" nil
64 org-deck-include-extensions split)
65 (:deck-exclude-extensions "DECK_EXCLUDE_EXTENSIONS" nil
66 org-deck-exclude-extensions split))
67 :translate-alist
68 ((headline . org-deck-headline)
69 (inner-template . org-deck-inner-template)
70 (item . org-deck-item)
71 (template . org-deck-template)))
73 (defgroup org-export-deck nil
74 "Options for exporting Org mode files to deck.js HTML Presentations."
75 :tag "Org Export DECK"
76 :group 'org-export-html)
78 (defcustom org-deck-directories '("./deck.js")
79 "Directories to search for deck.js components (jquery,
80 modernizr; core, extensions and themes directories.)"
81 :group 'org-export-deck
82 :type '(repeat (string :tag "Directory")))
84 (defun org-deck--cleanup-components (components)
85 (remove-duplicates
86 (car (remove 'nil components))
87 :test (lambda (x y)
88 (string= (file-name-nondirectory x)
89 (file-name-nondirectory y)))))
91 (defun org-deck--find-extensions ()
92 "Returns a unique list of all extensions found in
93 in the extensions directories under `org-deck-directories'"
94 (org-deck--cleanup-components
95 (mapcar ; extensions under existing dirs
96 (lambda (dir)
97 (when (file-directory-p dir) (directory-files dir t "^[^.]")))
98 (mapcar ; possible extension directories
99 (lambda (x) (expand-file-name "extensions" x))
100 org-deck-directories))))
102 (defun org-deck--find-css (type)
103 "Return a unique list of all the css stylesheets in the themes/TYPE
104 directories under `org-deck-directories'."
105 (org-deck--cleanup-components
106 (mapcar
107 (lambda (dir)
108 (let ((css-dir (expand-file-name
109 (concat (file-name-as-directory "themes") type) dir)))
110 (when (file-directory-p css-dir)
111 (directory-files css-dir t "\\.css$"))))
112 org-deck-directories)))
114 (defun org-deck-list-components ()
115 "List all available deck extensions, styles and
116 transitions (with full paths) to a temporary buffer."
117 (interactive)
118 (let ((outbuf (get-buffer-create "*deck.js Extensions*")))
119 (with-current-buffer outbuf
120 (erase-buffer)
121 (insert "Extensions\n----------\n")
122 (insert (mapconcat 'identity (org-deck--find-extensions) "\n"))
123 (insert "\n\nStyles\n------\n")
124 (insert (mapconcat 'identity (org-deck--find-css "style") "\n"))
125 (insert "\n\nTransitions\n----------\n")
126 (insert (mapconcat 'identity (org-deck--find-css "transition") "\n")))
127 (switch-to-buffer-other-window outbuf)))
129 (defcustom org-deck-include-extensions nil
130 "If non-nil, list of extensions to include instead of all available.
131 Can be overriden or set with the DECK_INCLUDE_EXTENSIONS property.
132 During output generation, the extensions found by
133 `org-deck--find-extensions' are searched for the appropriate
134 files (scripts and/or stylesheets) to include in the generated
135 html. The href/src attributes are created relative to `org-deck-base-url'."
136 :group 'org-export-deck
137 :type '(repeat (string :tag "Extension")))
139 (defcustom org-deck-exclude-extensions nil
140 "If non-nil, list of extensions to exclude.
141 Can be overriden or set with the DECK_EXCLUDE_EXTENSIONS property."
142 :group 'org-export-deck
143 :type '(repeat (string :tag "Extension")))
145 (defcustom org-deck-theme "swiss.css"
146 "deck.js theme. Can be overriden with the DECK_THEME property.
147 If this value contains a path component (\"/\"), it is used as a
148 literal path (url). Otherwise it is prepended with
149 `org-deck-base-url'/themes/style/."
150 :group 'org-export-deck
151 :type 'string)
153 (defcustom org-deck-transition "fade.css"
154 "deck.js transition theme. Can be overriden with the
155 DECK_TRANSITION property.
156 If this value contains a path component (\"/\"), it is used as a
157 literal path (url). Otherwise it is prepended with
158 `org-deck-base-url'/themes/transition/."
159 :group 'org-export-deck
160 :type 'string)
162 (defcustom org-deck-base-url "deck.js"
163 "Url prefix to deck.js base directory containing the core, extensions
164 and themes directories.
165 Can be overriden with the DECK_BASE_URL property."
166 :group 'org-export-deck
167 :type 'string)
169 (defcustom org-deck-footer-template
170 "<h1>%author - %title</h1>"
171 "Format template to specify footer div.
172 Completed using `org-fill-template'.
173 Optional keys include %author, %email, %file, %title and %date.
174 This is included in a <footer> section."
175 :group 'org-export-deck
176 :type 'string)
178 (defcustom org-deck-header-template ""
179 "Format template to specify page. Completed using `org-fill-template'.
180 Optional keys include %author, %email, %file, %title and %date.
181 This is included in a <header> section."
182 :group 'org-export-deck
183 :type 'string)
185 (defcustom org-deck-title-page-style
186 "<style type='text/css'>
187 header, footer { left: 5px; width: 100% }
188 header { position: absolute; top: 10px; }
189 #title-slide h1 {
190 position: static; padding: 0;
191 margin-top: 10%;
192 -webkit-transform: none;
193 -moz-transform: none;
194 -ms-transform: none;
195 -o-transform: none;
196 transform: none;
198 #title-slide h2 {
199 text-align: center;
200 border:none;
201 padding: 0;
202 margin: 0;
204 </style>"
205 "CSS styles to use for title page"
206 :group 'org-export-deck
207 :type 'string)
209 (defcustom org-deck-title-page-template
210 "<div class='slide' id='title-slide'>
211 <h1>%title</h1>
212 <h2>%author</h2>
213 <h2>%email</h2>
214 <h2>%date</h2>
215 </div>"
216 "Format template to specify title page div.
217 Completed using `org-fill-template'.
218 Optional keys include %author, %email, %file, %title and %date.
219 Note that the wrapper div must include the class \"slide\"."
220 :group 'org-export-deck
221 :type 'string)
223 (defcustom org-deck-toc-style
224 "<style type='text/css'>
225 header, footer { left: 5px; width: 100% }
226 header { position: absolute; top: 10px; }
227 #table-of-contents h1 {
228 position: static; padding: 0;
229 margin-top: 10%;
230 -webkit-transform: none;
231 -moz-transform: none;
232 -ms-transform: none;
233 -o-transform: none;
234 Transform: none;
236 #title-slide h2 {
237 text-align: center;
238 border:none;
239 padding: 0;
240 margin: 0;
242 </style>"
243 "CSS styles to use for title page"
244 :group 'org-export-deck
245 :type 'string)
247 (defun org-deck-toc (depth info)
248 (concat
249 "<div id=\"table-of-contents\" class=\"slide\">\n"
250 (format "<h2>%s</h2>\n"
251 (org-html--translate "Table of Contents" info))
252 (org-html--toc-text
253 (mapcar
254 (lambda (headline)
255 (let* ((class (org-element-property :HTML_CONTAINER_CLASS headline))
256 (section-number
257 (when
258 (and (not (org-export-low-level-p headline info))
259 (org-export-numbered-headline-p headline info))
260 (concat
261 (mapconcat
262 'number-to-string
263 (org-export-get-headline-number headline info) ".") ". ")))
264 (title
265 (concat
266 section-number
267 (replace-regexp-in-string ; remove any links in headline...
268 "</?a[^>]*>" ""
269 (org-export-data
270 (org-element-property :title headline) info)))))
271 (cons
272 (if (and class (string-match-p "\\<slide\\>" class))
273 (format
274 "<a href='#outline-container-%s'>%s</a>"
275 (or (org-element-property :CUSTOM_ID headline)
276 (mapconcat
277 'number-to-string
278 (org-export-get-headline-number headline info) "-"))
279 title)
280 title)
281 (org-export-get-relative-level headline info))))
282 (org-export-collect-headlines info depth)))
283 "</div>\n"))
285 (defun org-deck--get-packages (info)
286 (let ((prefix (concat (plist-get info :deck-base-url) "/"))
287 (theme (plist-get info :deck-theme))
288 (transition (plist-get info :deck-transition))
289 (include (plist-get info :deck-include-extensions))
290 (exclude (plist-get info :deck-exclude-extensions))
291 (scripts '()) (sheets '()) (snippets '()))
292 (add-to-list 'scripts (concat prefix "jquery-1.7.2.min.js"))
293 (add-to-list 'scripts (concat prefix "core/deck.core.js"))
294 (add-to-list 'scripts (concat prefix "modernizr.custom.js"))
295 (add-to-list 'sheets (concat prefix "core/deck.core.css"))
296 (mapc
297 (lambda (extdir)
298 (let* ((name (file-name-nondirectory extdir))
299 (dir (file-name-as-directory extdir))
300 (path (concat prefix "extensions/" name "/"))
301 (base (format "deck.%s." name)))
302 (when (and (or (eq nil include) (member name include))
303 (not (member name exclude)))
304 (when (file-exists-p (concat dir base "js"))
305 (add-to-list 'scripts (concat path base "js")))
306 (when (file-exists-p (concat dir base "css"))
307 (add-to-list 'sheets (concat path base "css")))
308 (when (file-exists-p (concat dir base "html"))
309 (add-to-list 'snippets (concat dir base "html"))))))
310 (org-deck--find-extensions))
311 (add-to-list 'sheets
312 (if (file-name-directory theme) theme
313 (format "%sthemes/style/%s" prefix theme)))
314 (add-to-list
315 'sheets
316 (if (file-name-directory transition) transition
317 (format "%sthemes/transition/%s" prefix transition)))
318 (list :scripts (nreverse scripts) :sheets (nreverse sheets)
319 :snippets snippets)))
321 (defun org-deck-inner-template (contents info)
322 "Return body of document string after HTML conversion.
323 CONTENTS is the transcoded contents string. INFO is a plist
324 holding export options."
325 (concat contents "\n"))
327 (defun org-deck-headline (headline contents info)
328 (let ((org-html-toplevel-hlevel 2)
329 (class (or (org-element-property :HTML_CONTAINER_CLASS headline) ""))
330 (level (org-export-get-relative-level headline info)))
331 (when (and (= 1 level) (not (string-match-p "\\<slide\\>" class)))
332 (org-element-put-property headline :HTML_CONTAINER_CLASS (concat class " slide")))
333 (org-html-headline headline contents info)))
335 (defun org-deck-item (item contents info)
336 "Transcode an ITEM element from Org to HTML.
337 CONTENTS holds the contents of the item. INFO is a plist holding
338 contextual information.
339 If the containing headline has the property :slide, then
340 the \"slide\" class will be added to the to the list element,
341 which will make the list into a \"build\"."
342 (let ((text (org-html-item item contents info)))
343 (if (org-export-get-node-property :STEP item t)
344 (replace-regexp-in-string "^<li>" "<li class='slide'>" text)
345 text)))
347 (defun org-deck-template-alist (info)
348 (list
349 `("title" . ,(car (plist-get info :title)))
350 `("author" . ,(car (plist-get info :author)))
351 `("email" . ,(plist-get info :email))
352 `("date" . ,(nth 0 (plist-get info :date)))
353 `("file" . ,(plist-get info :input-file))))
355 (defun org-deck-template (contents info)
356 "Return complete document string after HTML conversion.
357 CONTENTS is the transcoded contents string. INFO is a plist
358 holding export options."
359 (let ((pkg-info (org-deck--get-packages info)))
360 (mapconcat
361 'identity
362 (list
363 "<!DOCTYPE html>"
364 (let ((lang (plist-get info :language)))
365 (mapconcat
366 (lambda (x)
367 (apply
368 'format
369 "<!--%s <html class='no-js %s' lang='%s'> %s<![endif]-->"
371 (list `("[if lt IE 7]>" "ie6" ,lang "")
372 `("[if IE 7]>" "ie7" ,lang "")
373 `("[if IE 8]>" "ie8" ,lang "")
374 `("[if gt IE 8]><!-->" "" ,lang "<!--")) "\n"))
375 "<head>"
376 (org-deck--build-meta-info info)
377 (mapconcat
378 (lambda (sheet)
379 (format
380 "<link rel='stylesheet' href='%s' type='text/css' />" sheet))
381 (plist-get pkg-info :sheets) "\n")
382 "<style type='text/css'>"
383 "#table-of-contents a {color: inherit;}"
384 "#table-of-contents ul {margin-bottom: 0;}"
385 (when (plist-get info :section-numbers)
386 "#table-of-contents ul li {list-style-type: none;}")
387 "</style>"
389 (mapconcat
390 (lambda (script)
391 (format
392 "<script src='%s' type='text/javascript'></script>" script))
393 (plist-get pkg-info :scripts) "\n")
394 (org-html--build-mathjax-config info)
395 "<script type='text/javascript'>"
396 " $(document).ready(function () { $.deck('.slide'); });"
397 "</script>"
398 (org-html--build-style info)
399 org-deck-title-page-style
400 "</head>"
401 "<body>"
402 "<header class='deck-status'>"
403 (org-fill-template
404 org-deck-header-template (org-deck-template-alist info))
405 "</header>"
406 "<div class='deck-container'>"
407 ;; title page
408 (org-fill-template
409 org-deck-title-page-template (org-deck-template-alist info))
410 ;; toc page
411 (let ((depth (plist-get info :with-toc)))
412 (when depth (org-deck-toc depth info)))
413 contents
414 (mapconcat
415 (lambda (snippet)
416 (with-temp-buffer (insert-file-contents snippet)
417 (buffer-string)))
418 (plist-get pkg-info :snippets) "\n")
419 "<footer class='deck-status'>"
420 (org-fill-template
421 org-deck-footer-template (org-deck-template-alist info))
422 "</footer>"
423 "</div>"
424 "</body>"
425 "</html>\n") "\n")))
427 (defun org-deck--build-meta-info (info)
428 "Return meta tags for exported document.
429 INFO is a plist used as a communication channel."
430 (let* ((title (org-export-data (plist-get info :title) info))
431 (author (and (plist-get info :with-author)
432 (let ((auth (plist-get info :author)))
433 (and auth (org-export-data auth info)))))
434 (date (and (plist-get info :with-date)
435 (let ((date (plist-get info :date)))
436 (and date (org-export-data date info)))))
437 (description (plist-get info :description))
438 (keywords (plist-get info :keywords)))
439 (mapconcat
440 'identity
441 (list
442 (format "<title>%s</title>" title)
443 (format "<meta charset='%s' />"
444 (or (and org-html-coding-system
445 (fboundp 'coding-system-get)
446 (coding-system-get
447 org-html-coding-system 'mime-charset))
448 "iso-8859-1"))
449 (mapconcat
450 (lambda (attr)
451 (when (< 0 (length (car attr)))
452 (format "<meta name='%s' content='%s'/>\n"
453 (nth 1 attr) (car attr))))
454 (list '("Org-mode" "generator")
455 `(,author "author")
456 `(,description "description")
457 `(,keywords "keywords")) "")) "\n")))
458 (defun org-deck-export-as-html
459 (&optional async subtreep visible-only body-only ext-plist)
460 "Export current buffer to an HTML buffer.
462 If narrowing is active in the current buffer, only export its
463 narrowed part.
465 If a region is active, export that region.
467 A non-nil optional argument ASYNC means the process should happen
468 asynchronously. The resulting buffer should be accessible
469 through the `org-export-stack' interface.
471 When optional argument SUBTREEP is non-nil, export the sub-tree
472 at point, extracting information from the headline properties
473 first.
475 When optional argument VISIBLE-ONLY is non-nil, don't export
476 contents of hidden elements.
478 When optional argument BODY-ONLY is non-nil, only write code
479 between \"<body>\" and \"</body>\" tags.
481 EXT-PLIST, when provided, is a property list with external
482 parameters overriding Org default settings, but still inferior to
483 file-local settings.
485 Export is done in a buffer named \"*Org deck.js Export*\", which
486 will be displayed when `org-export-show-temporary-export-buffer'
487 is non-nil."
488 (interactive)
489 (if async
490 (org-export-async-start
491 (lambda (output)
492 (with-current-buffer (get-buffer-create "*Org deck.js Export*")
493 (erase-buffer)
494 (insert output)
495 (goto-char (point-min))
496 (nxml-mode)
497 (org-export-add-to-stack (current-buffer) 'deck)))
498 `(org-export-as 'deck ,subtreep ,visible-only ,body-only ',ext-plist))
499 (let ((outbuf (org-export-to-buffer
500 'deck "*Org deck.js Export*"
501 subtreep visible-only body-only ext-plist)))
502 ;; Set major mode.
503 (with-current-buffer outbuf (nxml-mode))
504 (when org-export-show-temporary-export-buffer
505 (switch-to-buffer-other-window outbuf)))))
507 (defun org-deck-export-to-html
508 (&optional async subtreep visible-only body-only ext-plist)
509 "Export current buffer to a deck.js HTML file.
511 If narrowing is active in the current buffer, only export its
512 narrowed part.
514 If a region is active, export that region.
516 A non-nil optional argument ASYNC means the process should happen
517 asynchronously. The resulting file should be accessible through
518 the `org-export-stack' interface.
520 When optional argument SUBTREEP is non-nil, export the sub-tree
521 at point, extracting information from the headline properties
522 first.
524 When optional argument VISIBLE-ONLY is non-nil, don't export
525 contents of hidden elements.
527 When optional argument BODY-ONLY is non-nil, only write code
528 between \"<body>\" and \"</body>\" tags.
530 EXT-PLIST, when provided, is a property list with external
531 parameters overriding Org default settings, but still inferior to
532 file-local settings.
534 Return output file's name."
535 (interactive)
536 (let* ((extension (concat "." org-html-extension))
537 (file (org-export-output-file-name extension subtreep))
538 (org-export-coding-system org-html-coding-system))
539 (if async
540 (org-export-async-start
541 (lambda (f) (org-export-add-to-stack f 'deck))
542 (let ((org-export-coding-system org-html-coding-system))
543 `(expand-file-name
544 (org-export-to-file
545 'deck ,file ,subtreep ,visible-only ,body-only ',ext-plist))))
546 (let ((org-export-coding-system org-html-coding-system))
547 (org-export-to-file
548 'deck file subtreep visible-only body-only ext-plist)))))
550 (defun org-deck-publish-to-html (plist filename pub-dir)
551 "Publish an org file to deck.js HTML Presentation.
552 FILENAME is the filename of the Org file to be published. PLIST
553 is the property list for the given project. PUB-DIR is the
554 publishing directory. Returns output file name."
555 (org-publish-org-to 'deck filename ".html" plist pub-dir))
557 (provide 'ox-deck)