;;; ox-reveal.el --- reveal.js Presentation Back-End for Org Export Engine ;; Copyright (C) 2013 Yujie Wen ;; Author: Yujie Wen ;; Created: 2013-04-27 ;; Version: 1.0 ;; Package-Requires: ((org "8.0")) ;; Keywords: outlines, hypermedia, slideshow, presentation ;; This file is not part of GNU Emacs. ;;; Copyright Notice: ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;; Please see "Readme.org" for detail introductions. ;; Pull request: Multiplex Support - Stephen Barrett %t

%a

%e

%d

" "Format template to specify title page slide. The format string can contain the following escaping elements: %s stands for the title. %a stands for the author's name. %e stands for the author's email. %d stands for the date. %% stands for a literal %. " :group 'org-export-reveal :type 'string) (defcustom org-reveal-transition "default" "Reveal transistion style." :group 'org-export-reveal :type 'string) (defcustom org-reveal-transition-speed "default" "Reveal transistion speed." :group 'org-export-reveal :type 'string) (defcustom org-reveal-theme "moon" "Reveal theme." :group 'org-export-reveal :type 'string) (defcustom org-reveal-extra-js "" "URL to extra JS file." :group 'org-export-reveal :type 'string) (defcustom org-reveal-extra-css "" "URL to extra css file." :group 'org-export-reveal :type 'string) (defcustom org-reveal-multiplex-id "" "The ID to use for multiplexing." :group 'org-export-reveal :type 'string) (defcustom org-reveal-multiplex-secret "" "The secret to use for master slide." :group 'org-export-reveal :type 'string) (defcustom org-reveal-multiplex-url "" "The url of the socketio server." :group 'org-export-reveal :type 'string) (defcustom org-reveal-multiplex-socketio-url "" "the url of the socketio.js library" :group 'org-export-reveal :type 'string) (defcustom org-reveal-control t "Reveal control applet." :group 'org-export-reveal :type 'string) (defcustom org-reveal-progress t "Reveal progress applet." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-history nil "Reveal history applet." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-center t "Reveal center applet." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-rolling-links nil "Reveal use rolling links." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-slide-number "c" "Reveal showing slide numbers." :group 'org-export-reveal :type 'string) (defcustom org-reveal-keyboard t "Reveal use keyboard navigation." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-overview t "Reveal show overview." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-width -1 "Slide width" :group 'org-export-reveal :type 'integer) (defcustom org-reveal-height -1 "Slide height" :group 'org-export-reveal :type 'integer) (defcustom org-reveal-margin "-1" "Slide margin" :group 'org-export-reveal :type 'string) (defcustom org-reveal-min-scale "-1" "Minimum bound for scaling slide." :group 'org-export-reveal :type 'string) (defcustom org-reveal-max-scale "-1" "Maximum bound for scaling slide." :group 'org-export-reveal :type 'string) (defcustom org-reveal-mathjax nil "Obsolete. Org-reveal enable mathjax when it find latex content." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-mathjax-url "https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" "Default MathJax URL." :group 'org-export-reveal :type 'string) (defcustom org-reveal-preamble nil "Preamble contents." :group 'org-export-reveal :type 'string) (defcustom org-reveal-head-preamble nil "Preamble contents for head part." :group 'org-export-reveal :type 'string) (defcustom org-reveal-postamble nil "Postamble contents." :group 'org-export-reveal :type 'string) (defcustom org-reveal-slide-header nil "HTML content used as Reveal.js slide header" :group 'org-export-reveal :type 'string) (defcustom org-reveal-slide-footer nil "HTML content used as Reveal.js slide footer" :group 'org-export-reveal :type 'string) (defcustom org-reveal-default-frag-style nil "Default fragment style." :group 'org-export-reveal :type 'string) (defcustom org-reveal-plugins '(classList markdown zoom notes) "Default builtin plugins" :group 'org-export-reveal :type '(set (const classList) (const markdown) (const highlight) (const zoom) (const notes) (const search) (const remotes) (const multiplex))) (defcustom org-reveal-single-file nil "Export presentation into one single HTML file, which embedded JS scripts and pictures." :group 'org-export-reveal :type 'boolean) (defcustom org-reveal-init-script nil "Custom script that will be passed to Reveal.initialize." :group 'org-export-reveal :type 'string) (defcustom org-reveal-highlight-css "%r/lib/css/zenburn.css" "Hightlight.js CSS file." :group 'org-export-reveal :type 'string) (defcustom org-reveal-note-key-char "n" "If not nil, org-reveal-note-key-char's value is registered as the key character to Org-mode's structure completion for Reveal.js notes. When `<' followed by the key character are typed and then the completion key is pressed, which is usually `TAB', \"#+BEGIN_NOTES\" and \"#+END_NOTES\" is inserted. The default value is \"n\". Set the variable to nil to disable registering the completion" :group 'org-export-reveal :type 'string) (defun if-format (fmt val) (if val (format fmt val) "")) (defun frag-style (frag info) "Return proper fragment string according to FRAG and the default fragment style. FRAG is the fragment style set on element, INFO is a plist holding contextual information." (cond ((string= frag t) (let ((default-frag-style (plist-get info :reveal-default-frag-style))) (if default-frag-style (format "fragment %s" default-frag-style) "fragment"))) (t (format "fragment %s" frag)))) (defun frag-class (frag info) "Return proper HTML string description of fragment style. FRAG is the fragment style set on element, INFO is a plist holding contextual information." (and frag (format " class=\"%s\"" (frag-style frag info)))) (defun org-reveal-export-block (export-block contents info) "Transocde a EXPORT-BLOCK element from Org to Reveal. CONTENTS is nil. INFO is a plist holding contextual information." (let ((block-type (org-element-property :type export-block)) (block-string (org-element-property :value export-block))) (cond ((string= block-type "NOTES") (format "\n" (org-export-string-as block-string 'html 'body-only))) ((string= block-type "HTML") (org-remove-indentation block-string))))) ;; Copied from org-html-headline and modified to embed org-reveal ;; specific attributes. (defun org-reveal-headline (headline contents info) "Transcode a HEADLINE element from Org to HTML. CONTENTS holds the contents of the headline. INFO is a plist holding contextual information." (unless (org-element-property :footnote-section-p headline) (if (org-export-low-level-p headline info) ;; This is a deep sub-tree: export it as in ox-html. (org-html-headline headline contents info) ;; Standard headline. Export it as a slide (let* ((level (org-export-get-relative-level headline info)) (preferred-id (or (org-element-property :CUSTOM_ID headline) (org-export-get-reference headline info) (org-element-property :ID headline))) (hlevel (org-reveal--get-hlevel info)) (header (plist-get info :reveal-slide-header)) (header-div (when header (format "
%s
\n" header))) (footer (plist-get info :reveal-slide-footer)) (footer-div (when footer (format "
%s
\n" footer))) (first-sibling (org-export-first-sibling-p headline info)) (last-sibling (org-export-last-sibling-p headline info))) (concat (if (or (/= level 1) (not first-sibling)) ;; Not the first heading. Close previou slide. (concat ;; Slide footer if any footer-div ;; Close previous slide "\n" (if (<= level hlevel) ;; Close previous vertical slide group. "\n"))) (if (<= level hlevel) ;; Add an extra "
" to group following slides ;; into vertical slide group. "
\n") ;; Start a new slide. (format "
\n" (org-html--make-attribute-string `(:id ,(format "slide-%s" preferred-id) :data-state ,(org-element-property :REVEAL_DATA_STATE headline) :data-transition ,(org-element-property :REVEAL_DATA_TRANSITION headline) :data-background ,(org-element-property :REVEAL_BACKGROUND headline) :data-background-size ,(org-element-property :REVEAL_BACKGROUND_SIZE headline) :data-background-repeat ,(org-element-property :REVEAL_BACKGROUND_REPEAT headline) :data-background-transition ,(org-element-property :REVEAL_BACKGROUND_TRANS headline))) (let ((extra-attrs (org-element-property :REVEAL_EXTRA_ATTR headline))) (if extra-attrs (format " %s" extra-attrs) ""))) ;; Slide header if any. header-div ;; The HTML content of the headline ;; Strip the
tags, if any (let ((html (org-html-headline headline contents info))) (if (string-prefix-p " and the last
tags from html (concat "<" (mapconcat 'identity (butlast (cdr (split-string html "<" t))) "<")) ;; Return the HTML content unchanged html)) (if (and (= level 1) (org-export-last-sibling-p headline info)) ;; Last head 1. Close all slides. (concat ;; Slide footer if any footer-div "
\n
\n"))))))) (defgroup org-export-reveal nil "Options for exporting Orgmode files to reveal.js HTML pressentations." :tag "Org Export Reveal" :group 'org-export) (defun org-reveal--read-file (file) "Return the content of file" (with-temp-buffer (insert-file-contents-literally file) (buffer-string))) (defun org-reveal--file-url-to-path (url) "Convert URL that points to local files to file path." (replace-regexp-in-string (if (string-equal system-type "windows-nt") "^file:///" "^file://") "" url)) (defun org-reveal-stylesheets (info) "Return the HTML contents for declaring reveal stylesheets using custom variable `org-reveal-root'." (let* ((root-path (file-name-as-directory (plist-get info :reveal-root))) (reveal-css (concat root-path "css/reveal.css")) (theme (plist-get info :reveal-theme)) (theme-css (concat root-path "css/theme/" theme ".css")) ;; Local file names. (local-root (org-reveal--file-url-to-path root-path)) (local-reveal-css (concat local-root "css/reveal.css")) (local-theme-css (concat local-root "css/theme/" theme ".css")) (in-single-file (plist-get info :reveal-single-file))) (concat ;; stylesheets (if (and in-single-file (file-readable-p local-reveal-css) (file-readable-p local-theme-css)) ;; CSS files exist and are readable. Embed them. (concat "\n") ;; Fall-back to external CSS links. (if in-single-file ;; Tried to embed CSS files but failed. Print a message about possible errors. (error (concat "Cannot read " (mapconcat 'identity (delq nil (mapcar (lambda (file) (if (not (file-readable-p file)) file)) (list local-reveal-css local-theme-css))) ", ")))) ;; Create links to CSS files. (concat "\n" "\n")) ;; extra css (let ((extra-css (plist-get info :reveal-extra-css))) (if (string= extra-css "") "" (format "\n" extra-css))) ;; Include CSS for highlight.js if necessary (if (org-reveal--using-highlight.js info) (format "" (format-spec (plist-get info :reveal-highlight-css) `((?r . ,(directory-file-name root-path)))))) ;; print-pdf (if in-single-file "" (format " " root-path))))) (defun org-reveal-mathjax-scripts (info) "Return the HTML contents for declaring MathJax scripts" (if (plist-get info :reveal-mathjax) ;; MathJax enabled. (format "\n" (plist-get info :reveal-mathjax-url)))) (defun org-reveal-scripts (info) "Return the necessary scripts for initializing reveal.js using custom variable `org-reveal-root'." (let* ((root-path (file-name-as-directory (plist-get info :reveal-root))) (head-min-js (concat root-path "lib/js/head.min.js")) (reveal-js (concat root-path "js/reveal.js")) ;; Local files (local-root-path (org-reveal--file-url-to-path root-path)) (local-head-min-js (concat local-root-path "lib/js/head.min.js")) (local-reveal-js (concat local-root-path "js/reveal.js")) (in-single-file (plist-get info :reveal-single-file))) (concat ;; reveal.js/lib/js/head.min.js ;; reveal.js/js/reveal.js (if (and in-single-file (file-readable-p local-head-min-js) (file-readable-p local-reveal-js)) ;; Embed scripts into HTML (concat "") ;; Fall-back to extern script links (if in-single-file ;; Tried to embed scripts but failed. Print a message about possible errors. (error (concat "Cannot read " (mapconcat 'identity (delq nil (mapcar (lambda (file) (if (not (file-readable-p file)) file)) (list local-head-min-js local-reveal-js))) ", ")))) (concat "\n" "\n")) ;; plugin headings " \n"))) (defun org-reveal-toc (depth info) "Build a slide of table of contents." (let ((toc (org-html-toc depth info))) (if toc (format "
\n%s
\n" (replace-regexp-in-string "\n
"))) (defun org-reveal-parse-keyword-value (value) "According to the value content, return HTML tags to split slides." (let ((tokens (mapcar (lambda (x) (split-string x ":")) (split-string value)))) (mapconcat (lambda (x) (apply 'org-reveal-parse-token x)) tokens ""))) ;; Copied from org-html-format-list-item. Overwrite HTML class ;; attribute when there is attr_html attributes. (defun org-reveal-format-list-item (contents type checkbox attributes info &optional term-counter-id headline) "Format a list item into HTML." (let ((attr-html (cond (attributes (format " %s" (org-html--make-attribute-string attributes))) (checkbox (format " class=\"%s\"" (symbol-name checkbox))) (t ""))) (checkbox (concat (org-html-checkbox checkbox info) (and checkbox " "))) (br (org-html-close-tag "br" nil info))) (concat (case type (ordered (let* ((counter term-counter-id) (extra (if counter (format " value=\"%s\"" counter) ""))) (concat (format "" attr-html extra) (when headline (concat headline br))))) (unordered (let* ((id term-counter-id) (extra (if id (format " id=\"%s\"" id) ""))) (concat (format "" attr-html extra) (when headline (concat headline br))))) (descriptive (let* ((term term-counter-id)) (setq term (or term "(no term)")) ;; Check-boxes in descriptive lists are associated to tag. (concat (format "%s" attr-html (concat checkbox term)) "
")))) (unless (eq type 'descriptive) checkbox) (and contents (org-trim contents)) (case type (ordered "") (unordered "") (descriptive "
"))))) ;; Copied from org-html-item, changed to call ;; org-reveal-format-list-item. (defun org-reveal-item (item contents info) "Transcode an ITEM element from Org to Reveal. CONTENTS holds the contents of the item. INFO is a plist holding contextual information." (let* ((plain-list (org-export-get-parent item)) (type (org-element-property :type plain-list)) (counter (org-element-property :counter item)) (attributes (org-export-read-attribute :attr_html item)) ; (attributes (org-html--make-attribute-string (org-export-read-attribute :attr_html item))) (checkbox (org-element-property :checkbox item)) (tag (let ((tag (org-element-property :tag item))) (and tag (org-export-data tag info))))) (org-reveal-format-list-item contents type checkbox attributes info (or tag counter)))) (defun org-reveal-keyword (keyword contents info) "Transcode a KEYWORD element from Org to Reveal, and may change custom variables as SIDE EFFECT. CONTENTS is nil. INFO is a plist holding contextual information." (let ((key (org-element-property :key keyword)) (value (org-element-property :value keyword))) (case (intern key) (REVEAL (org-reveal-parse-keyword-value value)) (REVEAL_HTML value)))) (defun org-reveal-embedded-svg (path) "Embed the SVG content into Reveal HTML." (with-temp-buffer (insert-file-contents-literally path) (let ((start (re-search-forward "<[ \t\n]*svg[ \t\n]")) (end (re-search-forward "<[ \t\n]*/svg[ \t\n]*>"))) (concat "\n%s\n\n" tag (if attrs (concat " " (org-html--make-attribute-string attrs)) "") contents tag))) (defun org-reveal--build-pre/postamble (type info) "Return document preamble or postamble as a string, or nil." (let ((section (plist-get info (intern (format ":reveal-%s" type)))) (spec (org-html-format-spec info))) (when section (let ((section-contents (if (functionp (intern section)) (funcall (intern section) info) ;; else section is a string. (format-spec section spec)))) (when (org-string-nw-p section-contents) (org-element-normalize-string section-contents)))))) (defun org-reveal-section (section contents info) "Transcode a SECTION element from Org to Reveal. CONTENTS holds the contents of the section. INFO is a plist holding contextual information." ;; Just return the contents. No "
" tags. contents) (defun org-reveal--using-highlight.js (info) "Check whether highlight.js plugin is enabled." (memq 'highlight (or (car (read-from-string (plist-get info :reveal-plugins))) org-reveal-plugins))) (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)) (label (let ((lbl (org-element-property :name src-block))) (if (not lbl) "" (format " id=\"%s\"" lbl))))) (if (not lang) (format "
\n%s
" (or (frag-class frag info) " class=\"example\"") label code) (format "
\n%s%s\n
" (if (not caption) "" (format "" (org-export-data caption info))) (if use-highlight (format "\n%s" (or (frag-class frag info) "") label lang code) (format "\n
%s
" (or (frag-class frag info) (format " class=\"src src-%s\"" lang)) label code))))))) (defun org-reveal-quote-block (quote-block contents info) "Transcode a QUOTE-BLOCK element from Org to Reveal. CONTENTS holds the contents of the block INFO is a plist holding contextual information." (format "
\n%s
" (frag-class (org-export-read-attribute :attr_reveal quote-block :frag) info) contents)) (defun org-reveal-template (contents info) "Return complete document string after HTML conversion. contents is the transcoded contents string. info is a plist holding export options." (concat (format "\n\n\n" (if-format " lang=\"%s\"" (plist-get info :language))) "\n" (if-format "%s\n" (org-export-data (plist-get info :title) info)) (if-format "\n" (plist-get info :author)) (if-format "\n" (plist-get info :description)) (if-format "\n" (plist-get info :keywords)) (org-reveal-stylesheets info) (org-reveal-mathjax-scripts info) (org-reveal--build-pre/postamble 'head-preamble info) " \n" (org-reveal--build-pre/postamble 'preamble info) "
\n" (if (and (plist-get info :reveal-title-slide) (not (plist-get info :reveal-subtree))) (concat (format "
\n" (if-format " data-background=\"%s\"" (plist-get info :reveal-title-slide-background)) (if-format " data-background-size=\"%s\"" (plist-get info :reveal-title-slide-background-size)) (if-format " data-background-repeat=\"%s\"" (plist-get info :reveal-title-slide-background-repeat)) (if-format " data-background-transition=\"%s\"" (plist-get info :reveal-title-slide-background-transition))) (format-spec (plist-get info :reveal-title-slide-template) (org-html-format-spec info)) "\n
\n") "") contents "
\n" (org-reveal--build-pre/postamble 'postamble info) (org-reveal-scripts info) " \n")) (defun org-reveal-filter-parse-tree (tree backend info) "Do filtering before parsing TREE. Tree is the parse tree being exported. BACKEND is the export back-end used. INFO is a plist-used as a communication channel. Assuming BACKEND is `reveal'. Each `attr_reveal' attribute is mapped to corresponding `attr_html' attributes." (let ((default-frag-style (plist-get info :reveal-default-frag-style))) (org-element-map tree (remq 'item org-element-all-elements) (lambda (elem) (org-reveal-append-frag elem default-frag-style)))) ;; Return the updated tree. tree) (defun org-reveal--update-attr-html (elem frag &optional frag-index) "Update ELEM's attr_html atrribute with reveal's fragment attributes." (let ((attr-html (org-element-property :attr_html elem))) (when (and frag (not (string= frag "none"))) (push (cond ((string= frag t) ":class fragment") (t (format ":class fragment %s" frag))) attr-html) (when frag-index (push (format ":data-fragment-index %s" frag-index) attr-html))) (org-element-put-property elem :attr_html attr-html))) (defun org-reveal-append-frag (elem default-style) "Read org-reveal's fragment attribute from ELEM and append transformed fragment attribute to ELEM's attr_html plist." (let ((frag (org-export-read-attribute :attr_reveal elem :frag)) (frag-index (org-export-read-attribute :attr_reveal elem :frag_idx))) (if frag (cond ((and (string= (org-element-type elem) 'plain-list) (char-equal (string-to-char frag) ?\()) (let* ((frag-list (car (read-from-string frag))) (frag-list (if default-style (mapcar (lambda (s) "Replace t with default-style" (if (string= s t) default-style s)) frag-list) frag-list)) (items (org-element-contents elem))) (if frag-index (mapcar* 'org-reveal--update-attr-html items frag-list (car (read-from-string frag-index))) ;; Make frag-list tail circular (nconc frag-list (last frag-list)) (mapcar* 'org-reveal--update-attr-html items frag-list)))) (t (org-reveal--update-attr-html elem frag frag-index)))) elem)) (defvar client-multiplex nil "used to cause generation of client html file for multiplex") (defun org-reveal-export-to-html (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to a reveal.js HTML file." (interactive) (let* ((extension (concat "." org-html-extension)) (file (org-export-output-file-name extension subtreep)) (clientfile (org-export-output-file-name (concat "_client" extension) subtreep))) ; export filename_client HTML file if multiplexing (setq client-multiplex nil) (setq retfile (org-export-to-file 'reveal file async subtreep visible-only body-only ext-plist)) ; export the client HTML file if client-multiplex is set true ; by previous call to org-export-to-file (if (eq client-multiplex t) (org-export-to-file 'reveal clientfile async subtreep visible-only body-only ext-plist)) (cond (t retfile)))) (defun org-reveal-export-to-html-and-browse (&optional async subtreep visible-only body-only ext-plist) "Export current buffer to a reveal.js and browse HTML file." (interactive) (browse-url-of-file (expand-file-name (org-reveal-export-to-html async subtreep visible-only body-only ext-plist)))) (defun org-reveal-export-current-subtree (&optional async subtreep visible-only body-only ext-plist) "Export current subtree to a Reveal.js HTML file." (interactive) (org-narrow-to-subtree) (let ((ret (org-reveal-export-to-html async subtreep visible-only body-only (plist-put ext-plist :reveal-subtree t)))) (widen) ret)) ;;;###autoload (defun org-reveal-publish-to-reveal (plist filename pub-dir) "Publish an org file to Html. 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. Return output file name." (org-publish-org-to 'reveal filename ".html" plist pub-dir)) ;; Register auto-completion for speaker notes. (when org-reveal-note-key-char (add-to-list 'org-structure-template-alist (list org-reveal-note-key-char "#+BEGIN_NOTES\n\?\n#+END_NOTES"))) (provide 'ox-reveal) ;;; ox-reveal.el ends here