203ca8c1d3315d567cbd6cd89aa7cce80ee7ecdc
[org-mode.git] / contrib / lisp / ox-deck.el
blob203ca8c1d3315d567cbd6cd89aa7cce80ee7ecdc
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)
41 (org-export-define-derived-backend deck html
42 :menu-entry
43 (?d "Export to deck.js HTML Presentation"
44 ((?H "To temporary buffer" org-deck-export-as-html)
45 (?h "To file" org-deck-export-to-html)
46 (?o "To file and open"
47 (lambda (a s v b)
48 (if a (org-deck-export-to-html t s v b)
49 (org-open-file (org-deck-export-to-html nil s v b)))))))
50 :options-alist
51 ((:html-link-home "HTML_LINK_HOME" nil nil)
52 (:html-link-up "HTML_LINK_UP" nil nil)
53 (:html-mathjax "HTML_MATHJAX" nil "" space)
54 (:html-postamble nil "html-postamble" nil t)
55 (:html-preamble nil "html-preamble" nil t)
56 (:html-style-extra "HTML_STYLE" nil org-html-style-extra newline)
57 (:html-style-include-default "HTML_INCLUDE_DEFAULT" nil nil)
58 (:html-style-include-scripts "HTML_INCLUDE_SCRIPTS" nil nil)
59 (:deck-base-url "DECK_BASE_URL" nil org-deck-base-url)
60 (:deck-theme "DECK_THEME" nil org-deck-theme)
61 (:deck-transition "DECK_TRANSITION" nil org-deck-transition)
62 (:deck-include-extensions "DECK_INCLUDE_EXTENSIONS" nil
63 org-deck-include-extensions split)
64 (:deck-exclude-extensions "DECK_EXCLUDE_EXTENSIONS" nil
65 org-deck-exclude-extensions split)
66 (:deck-directories "DECK_DIRECTORIES" nil
67 org-deck-directories split))
68 :translate-alist
69 ((headline . org-deck-headline)
70 (inner-template . org-deck-inner-template)
71 (item . org-deck-item)
72 (template . org-deck-template)))
74 (defgroup org-export-deck nil
75 "Options for exporting Org mode files to deck.js HTML Presentations."
76 :tag "Org Export DECK"
77 :group 'org-export-html)
79 (defcustom org-deck-directories nil
80 "Directories to search for deck.js components (jquery,
81 modernizr; core, extensions and themes directories.)"
82 :group 'org-export-deck
83 :type '(repeat (string :tag "Directory")))
85 (defun org-deck--cleanup-components (components)
86 (remove-duplicates
87 (car (remove 'nil components))
88 :test (lambda (x y)
89 (string= (file-name-nondirectory x)
90 (file-name-nondirectory y)))))
92 (defun org-deck--find-extensions ()
93 "Returns a unique list of all extensions found in
94 in the extensions directories under `org-deck-directories'"
95 (org-deck--cleanup-components
96 (mapcar ; extensions under existing dirs
97 (lambda (dir)
98 (when (file-directory-p dir) (directory-files dir t "^[^.]")))
99 (mapcar ; possible extension directories
100 (lambda (x) (expand-file-name "extensions" x))
101 org-deck-directories))))
103 (defun org-deck--find-css (type)
104 "Return a unique list of all the css stylesheets in the themes/TYPE
105 directories under `org-deck-directories'."
106 (org-deck--cleanup-components
107 (mapcar
108 (lambda (dir)
109 (let ((css-dir (expand-file-name
110 (concat (file-name-as-directory "themes") type) dir)))
111 (when (file-directory-p css-dir)
112 (directory-files css-dir t "\\.css$"))))
113 org-deck-directories)))
115 (defun org-deck-list-components ()
116 "List all available deck extensions, styles and
117 transitions (with full paths) to a temporary buffer."
118 (interactive)
119 (let ((outbuf (get-buffer-create "*deck.js Extensions*")))
120 (with-current-buffer outbuf
121 (erase-buffer)
122 (insert "Extensions\n----------\n")
123 (insert (mapconcat 'identity (org-deck--find-extensions) "\n"))
124 (insert "\n\nStyles\n------\n")
125 (insert (mapconcat 'identity (org-deck--find-css "style") "\n"))
126 (insert "\n\nTransitions\n----------\n")
127 (insert (mapconcat 'identity (org-deck--find-css "transition") "\n")))
128 (switch-to-buffer-other-window outbuf)))
130 (defcustom org-deck-include-extensions nil
131 "If non-nil, list of extensions to include instead of all available.
132 Can be overriden or set with the DECK_INCLUDE_EXTENSIONS property.
133 During output generation, the extensions found by
134 `org-deck--find-extensions' are searched for the appropriate
135 files (scripts and/or stylesheets) to include in the generated
136 html. The href/src attributes are created relative to `org-deck-base-url'."
137 :group 'org-export-deck
138 :type '(repeat (string :tag "Extension")))
140 (defcustom org-deck-exclude-extensions nil
141 "If non-nil, list of extensions to exclude.
142 Can be overriden or set with the DECK_EXCLUDE_EXTENSIONS property."
143 :group 'org-export-deck
144 :type '(repeat (string :tag "Extension")))
146 (defcustom org-deck-theme "swiss.css"
147 "deck.js theme. Can be overriden with the DECK_THEME property.
148 If this value contains a path component (\"/\"), it is used as a
149 literal path (url). Otherwise it is prepended with
150 `org-deck-base-url'/themes/style/."
151 :group 'org-export-deck
152 :type 'string)
154 (defcustom org-deck-transition "fade.css"
155 "deck.js transition theme. Can be overriden with the
156 DECK_TRANSITION property.
157 If this value contains a path component (\"/\"), it is used as a
158 literal path (url). Otherwise it is prepended with
159 `org-deck-base-url'/themes/transition/."
160 :group 'org-export-deck
161 :type 'string)
163 (defcustom org-deck-base-url "deck.js"
164 "Url prefix to deck.js base directory containing the core, extensions
165 and themes directories.
166 Can be overriden with the DECK_BASE_URL property."
167 :group 'org-export-deck
168 :type 'string)
170 (defcustom org-deck-footer-template
171 "<h1>%author - %title</h1>"
172 "Format template to specify footer div.
173 Completed using `org-fill-template'.
174 Optional keys include %author, %email, %file, %title and %date.
175 This is included in a <footer> section."
176 :group 'org-export-deck
177 :type 'string)
179 (defcustom org-deck-header-template ""
180 "Format template to specify page. Completed using `org-fill-template'.
181 Optional keys include %author, %email, %file, %title and %date.
182 This is included in a <header> section."
183 :group 'org-export-deck
184 :type 'string)
186 (defcustom org-deck-title-page-style
187 "<style type='text/css'>
188 header, footer { left: 5px; width: 100% }
189 header { position: absolute; top: 10px; }
190 #title-slide h1 {
191 position: static; padding: 0;
192 margin-top: 10%;
193 -webkit-transform: none;
194 -moz-transform: none;
195 -ms-transform: none;
196 -o-transform: none;
197 transform: none;
199 #title-slide h2 {
200 text-align: center;
201 border:none;
202 padding: 0;
203 margin: 0;
205 </style>"
206 "CSS styles to use for title page"
207 :group 'org-export-deck
208 :type 'string)
210 (defcustom org-deck-title-page-template
211 "<div class='slide' id='title-slide'>
212 <h1>%title</h1>
213 <h2>%author</h2>
214 <h2>%email</h2>
215 <h2>%date</h2>
216 </div>"
217 "Format template to specify title page div.
218 Completed using `org-fill-template'.
219 Optional keys include %author, %email, %file, %title and %date.
220 Note that the wrapper div must include the class \"slide\"."
221 :group 'org-export-deck
222 :type 'string)
224 (defcustom org-deck-toc-style
225 "<style type='text/css'>
226 header, footer { left: 5px; width: 100% }
227 header { position: absolute; top: 10px; }
228 #table-of-contents h1 {
229 position: static; padding: 0;
230 margin-top: 10%;
231 -webkit-transform: none;
232 -moz-transform: none;
233 -ms-transform: none;
234 -o-transform: none;
235 Transform: none;
237 #title-slide h2 {
238 text-align: center;
239 border:none;
240 padding: 0;
241 margin: 0;
243 </style>"
244 "CSS styles to use for title page"
245 :group 'org-export-deck
246 :type 'string)
248 (defun org-deck-toc (depth info)
249 (concat
250 "<div id=\"table-of-contents\" class=\"slide\">\n"
251 (format "<h2>%s</h2>\n"
252 (org-html--translate "Table of Contents" info))
253 (org-html-toc-text
254 (mapcar
255 (lambda (headline)
256 (let* ((class (org-element-property :HTML_CONTAINER_CLASS headline))
257 (section-number
258 (when
259 (and (not (org-export-low-level-p headline info))
260 (org-export-numbered-headline-p headline info))
261 (concat
262 (mapconcat
263 'number-to-string
264 (org-export-get-headline-number headline info) ".") ". ")))
265 (title
266 (concat
267 section-number
268 (replace-regexp-in-string ; remove any links in headline...
269 "</?a[^>]*>" ""
270 (org-export-data
271 (org-element-property :title headline) info)))))
272 (list
273 (if (and class (string-match-p "\\<slide\\>" class))
274 (format
275 "<a href='#outline-container-%s'>%s</a>"
276 (or (org-element-property :CUSTOM_ID headline)
277 (mapconcat
278 'number-to-string
279 (org-export-get-headline-number headline info) "-"))
280 title)
281 title)
282 (org-export-get-relative-level headline info))))
283 (org-export-collect-headlines info depth)))
284 "</div>\n" ))
286 (defun org-deck--get-packages (info)
287 (let ((prefix (concat (plist-get info :deck-base-url) "/"))
288 (theme (plist-get info :deck-theme))
289 (transition (plist-get info :deck-transition))
290 (include (plist-get info :deck-include-extensions))
291 (exclude (plist-get info :deck-exclude-extensions))
292 (scripts '()) (sheets '()) (snippets '()))
293 (add-to-list 'scripts (concat prefix "jquery-1.7.2.min.js"))
294 (add-to-list 'scripts (concat prefix "core/deck.core.js"))
295 (add-to-list 'scripts (concat prefix "modernizr.custom.js"))
296 (add-to-list 'sheets (concat prefix "core/deck.core.css"))
297 (mapc
298 (lambda (extdir)
299 (let* ((name (file-name-nondirectory extdir))
300 (dir (file-name-as-directory extdir))
301 (path (concat prefix "extensions/" name "/"))
302 (base (format "deck.%s." name)))
303 (when (and (or (eq nil include) (member name include))
304 (not (member name exclude)))
305 (when (file-exists-p (concat dir base "js"))
306 (add-to-list 'scripts (concat path base "js")))
307 (when (file-exists-p (concat dir base "css"))
308 (add-to-list 'sheets (concat path base "css")))
309 (when (file-exists-p (concat dir base "html"))
310 (add-to-list 'snippets (concat dir base "html"))))))
311 (org-deck--find-extensions))
312 (add-to-list 'sheets
313 (if (file-name-directory theme) theme
314 (format "%sthemes/style/%s" prefix theme)))
315 (add-to-list
316 'sheets
317 (if (file-name-directory transition) transition
318 (format "%sthemes/transition/%s" prefix transition)))
319 (list :scripts (nreverse scripts) :sheets (nreverse sheets)
320 :snippets snippets)))
322 (defun org-html-inner-template (contents info)
323 "Return body of document string after HTML conversion.
324 CONTENTS is the transcoded contents string. INFO is a plist
325 holding export options."
326 (concat
327 ;; Table of contents.
328 (let ((depth (plist-get info :with-toc)))
329 (when depth (org-deck-toc depth info)))
330 ;; Document contents.
331 contents
332 "\n"))
334 (defun org-deck-headline (headline contents info)
335 (let ((org-html-toplevel-hlevel 2)
336 (class (or (org-element-property :HTML_CONTAINER_CLASS headline) ""))
337 (level (org-export-get-relative-level headline info)))
338 (when (and (= 1 level) (not (string-match-p "\\<slide\\>" class)))
339 (org-element-put-property headline :HTML_CONTAINER_CLASS (concat class " slide")))
340 (org-html-headline headline contents info)))
342 (defun org-deck-item (item contents info)
343 "Transcode an ITEM element from Org to HTML.
344 CONTENTS holds the contents of the item. INFO is a plist holding
345 contextual information.
346 If the containing headline has the property :slide, then
347 the \"slide\" class will be added to the to the list element,
348 which will make the list into a \"build\"."
349 (let ((text (org-html-item item contents info)))
350 (if (org-export-get-node-property :STEP item t)
351 (replace-regexp-in-string "^<li>" "<li class='slide'>" text)
352 text)))
354 (defun org-deck-template-alist (info)
355 (list
356 `("title" . ,(car (plist-get info :title)))
357 `("author" . ,(car (plist-get info :author)))
358 `("email" . ,(plist-get info :email))
359 `("date" . ,(nth 0 (plist-get info :date)))
360 `("file" . ,(plist-get info :input-file))))
362 (defun org-deck-template (contents info)
363 "Return complete document string after HTML conversion.
364 CONTENTS is the transcoded contents string. INFO is a plist
365 holding export options."
366 (let ((pkg-info (org-deck--get-packages info)))
367 (mapconcat
368 'identity
369 (list
370 "<!DOCTYPE html>"
371 (let ((lang (plist-get info :language)))
372 (mapconcat
373 (lambda (x)
374 (apply
375 'format
376 "<!--%s <html class='no-js %s' lang='%s'> %s<![endif]-->"
378 (list `("[if lt IE 7]>" "ie6" ,lang "")
379 `("[if IE 7]>" "ie7" ,lang "")
380 `("[if IE 8]>" "ie8" ,lang "")
381 `("[if gt IE 8]><!-->" "" ,lang "<!--")) "\n"))
382 "<head>"
383 (org-deck--build-meta-info info)
384 (mapconcat
385 (lambda (sheet)
386 (format
387 "<link rel='stylesheet' href='%s' type='text/css' />" sheet))
388 (plist-get pkg-info :sheets) "\n")
389 "<style type='text/css'>"
390 "#table-of-contents a {color: inherit;}"
391 "#table-of-contents ul {margin-bottom: 0;}"
392 (when (plist-get info :section-numbers)
393 "#table-of-contents ul li {list-style-type: none;}")
394 "</style>"
396 (mapconcat
397 (lambda (script)
398 (format
399 "<script src='%s' type='text/javascript'></script>" script))
400 (plist-get pkg-info :scripts) "\n")
401 (org-html--build-mathjax-config info)
402 "<script type='text/javascript'>"
403 " $(document).ready(function () { $.deck('.slide'); });"
404 "</script>"
405 (org-html--build-style info)
406 org-deck-title-page-style
407 "</head>"
408 "<body>"
409 "<header class='deck-status'>"
410 (org-fill-template
411 org-deck-header-template (org-deck-template-alist info))
412 "</header>"
413 "<div class='deck-container'>"
414 ;; title page
415 (org-fill-template
416 org-deck-title-page-template (org-deck-template-alist info))
417 ;; toc page
418 (let ((depth (plist-get info :with-toc)))
419 (when depth (org-deck-toc depth info)))
420 contents
421 (mapconcat
422 (lambda (snippet)
423 (with-temp-buffer (insert-file-contents snippet)
424 (buffer-string)))
425 (plist-get pkg-info :snippets) "\n")
426 "<footer class='deck-status'>"
427 (org-fill-template
428 org-deck-footer-template (org-deck-template-alist info))
429 "</footer>"
430 "</div>"
431 "</body>"
432 "</html>\n") "\n")))
434 (defun org-deck--build-meta-info (info)
435 "Return meta tags for exported document.
436 INFO is a plist used as a communication channel."
437 (let* ((title (org-export-data (plist-get info :title) info))
438 (author (and (plist-get info :with-author)
439 (let ((auth (plist-get info :author)))
440 (and auth (org-export-data auth info)))))
441 (date (and (plist-get info :with-date)
442 (let ((date (plist-get info :date)))
443 (and date (org-export-data date info)))))
444 (description (plist-get info :description))
445 (keywords (plist-get info :keywords)))
446 (mapconcat
447 'identity
448 (list
449 (format "<title>%s</title>" title)
450 (format "<meta charset='%s' />"
451 (or (and org-html-coding-system
452 (fboundp 'coding-system-get)
453 (coding-system-get
454 org-html-coding-system 'mime-charset))
455 "iso-8859-1"))
456 (mapconcat
457 (lambda (attr)
458 (when (< 0 (length (car attr)))
459 (format "<meta name='%s' content='%s'/>\n"
460 (nth 1 attr) (car attr))))
461 (list '("Org-mode" "generator")
462 `(,author "author")
463 `(,description "description")
464 `(,keywords "keywords")) "")) "\n")))
465 (defun org-deck-export-as-html
466 (&optional async subtreep visible-only body-only ext-plist)
467 "Export current buffer to an HTML buffer.
469 If narrowing is active in the current buffer, only export its
470 narrowed part.
472 If a region is active, export that region.
474 A non-nil optional argument ASYNC means the process should happen
475 asynchronously. The resulting buffer should be accessible
476 through the `org-export-stack' interface.
478 When optional argument SUBTREEP is non-nil, export the sub-tree
479 at point, extracting information from the headline properties
480 first.
482 When optional argument VISIBLE-ONLY is non-nil, don't export
483 contents of hidden elements.
485 When optional argument BODY-ONLY is non-nil, only write code
486 between \"<body>\" and \"</body>\" tags.
488 EXT-PLIST, when provided, is a property list with external
489 parameters overriding Org default settings, but still inferior to
490 file-local settings.
492 Export is done in a buffer named \"*Org deck.js Export*\", which
493 will be displayed when `org-export-show-temporary-export-buffer'
494 is non-nil."
495 (interactive)
496 (if async
497 (org-export-async-start
498 (lambda (output)
499 (with-current-buffer (get-buffer-create "*Org deck.js Export*")
500 (erase-buffer)
501 (insert output)
502 (goto-char (point-min))
503 (nxml-mode)
504 (org-export-add-to-stack (current-buffer) 'deck)))
505 `(org-export-as 'deck ,subtreep ,visible-only ,body-only ',ext-plist))
506 (let ((outbuf (org-export-to-buffer
507 'deck "*Org deck.js Export*"
508 subtreep visible-only body-only ext-plist)))
509 ;; Set major mode.
510 (with-current-buffer outbuf (nxml-mode))
511 (when org-export-show-temporary-export-buffer
512 (switch-to-buffer-other-window outbuf)))))
514 (defun org-deck-export-to-html
515 (&optional async subtreep visible-only body-only ext-plist)
516 "Export current buffer to a deck.js HTML file.
518 If narrowing is active in the current buffer, only export its
519 narrowed part.
521 If a region is active, export that region.
523 A non-nil optional argument ASYNC means the process should happen
524 asynchronously. The resulting file should be accessible through
525 the `org-export-stack' interface.
527 When optional argument SUBTREEP is non-nil, export the sub-tree
528 at point, extracting information from the headline properties
529 first.
531 When optional argument VISIBLE-ONLY is non-nil, don't export
532 contents of hidden elements.
534 When optional argument BODY-ONLY is non-nil, only write code
535 between \"<body>\" and \"</body>\" tags.
537 EXT-PLIST, when provided, is a property list with external
538 parameters overriding Org default settings, but still inferior to
539 file-local settings.
541 Return output file's name."
542 (interactive)
543 (let* ((extension (concat "." org-html-extension))
544 (file (org-export-output-file-name extension subtreep))
545 (org-export-coding-system org-html-coding-system))
546 (if async
547 (org-export-async-start
548 (lambda (f) (org-export-add-to-stack f 'deck))
549 (let ((org-export-coding-system org-html-coding-system))
550 `(expand-file-name
551 (org-export-to-file
552 'deck ,file ,subtreep ,visible-only ,body-only ',ext-plist))))
553 (let ((org-export-coding-system org-html-coding-system))
554 (org-export-to-file
555 'deck file subtreep visible-only body-only ext-plist)))))
557 (defun org-deck-publish-to-html (plist filename pub-dir)
558 "Publish an org file to deck.js HTML Presentation.
559 FILENAME is the filename of the Org file to be published. PLIST
560 is the property list for the given project. PUB-DIR is the
561 publishing directory. Returns output file name."
562 (org-publish-org-to 'deck filename ".html" plist pub-dir))
564 (provide 'ox-deck)