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/>.
23 ;; This library implements a deck.js presentation back-end for the Org
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.)
41 (org-export-define-derived-backend deck html
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"
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
)))))))
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
))
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)
87 (car (remove 'nil components
))
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
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
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."
119 (let ((outbuf (get-buffer-create "*deck.js Extensions*")))
120 (with-current-buffer outbuf
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
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
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
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
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
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; }
191 position: static; padding: 0;
193 -webkit-transform: none;
194 -moz-transform: none;
206 "CSS styles to use for title page"
207 :group
'org-export-deck
210 (defcustom org-deck-title-page-template
211 "<div class='slide' id='title-slide'>
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
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;
231 -webkit-transform: none;
232 -moz-transform: none;
244 "CSS styles to use for title page"
245 :group
'org-export-deck
248 (defun org-deck-toc (depth info
)
250 "<div id=\"table-of-contents\" class=\"slide\">\n"
251 (format "<h2>%s</h2>\n"
252 (org-html--translate "Table of Contents" info
))
256 (let* ((class (org-element-property :HTML_CONTAINER_CLASS headline
))
259 (and (not (org-export-low-level-p headline info
))
260 (org-export-numbered-headline-p headline info
))
264 (org-export-get-headline-number headline info
) ".") ". ")))
268 (replace-regexp-in-string ; remove any links in headline...
271 (org-element-property :title headline
) info
)))))
273 (if (and class
(string-match-p "\\<slide\\>" class
))
275 "<a href='#outline-container-%s'>%s</a>"
276 (or (org-element-property :CUSTOM_ID headline
)
279 (org-export-get-headline-number headline info
) "-"))
282 (org-export-get-relative-level headline info
))))
283 (org-export-collect-headlines info depth
)))
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"))
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))
313 (if (file-name-directory theme
) theme
314 (format "%sthemes/style/%s" prefix theme
)))
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."
327 ;; Table of contents.
328 (let ((depth (plist-get info
:with-toc
)))
329 (when depth
(org-deck-toc depth info
)))
330 ;; Document contents.
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
)
354 (defun org-deck-template-alist (info)
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
)))
371 (let ((lang (plist-get info
:language
)))
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"))
383 (org-deck--build-meta-info info
)
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;}")
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'); });"
405 (org-html--build-style info
)
406 org-deck-title-page-style
409 "<header class='deck-status'>"
411 org-deck-header-template
(org-deck-template-alist info
))
413 "<div class='deck-container'>"
416 org-deck-title-page-template
(org-deck-template-alist info
))
418 (let ((depth (plist-get info
:with-toc
)))
419 (when depth
(org-deck-toc depth info
)))
423 (with-temp-buffer (insert-file-contents snippet
)
425 (plist-get pkg-info
:snippets
) "\n")
426 "<footer class='deck-status'>"
428 org-deck-footer-template
(org-deck-template-alist info
))
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
)))
449 (format "<title>%s</title>" title
)
450 (format "<meta charset='%s' />"
451 (or (and org-html-coding-system
452 (fboundp 'coding-system-get
)
454 org-html-coding-system
'mime-charset
))
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")
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
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
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
492 Export is done in a buffer named \"*Org deck.js Export*\", which
493 will be displayed when `org-export-show-temporary-export-buffer'
497 (org-export-async-start
499 (with-current-buffer (get-buffer-create "*Org deck.js Export*")
502 (goto-char (point-min))
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
)))
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
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
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
541 Return output file's name."
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
))
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
))
552 'deck
,file
,subtreep
,visible-only
,body-only
',ext-plist
))))
553 (let ((org-export-coding-system org-html-coding-system
))
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
))