From 6e790cb1d15e7072817ff8646b4fc165b4408332 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 14 Aug 2026 19:48:51 +0200 Subject: md: 2026-08-14T19:48:51+02:00 --- lisp/ox-reveal.el | 808 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 808 insertions(+) create mode 100644 lisp/ox-reveal.el (limited to 'lisp') diff --git a/lisp/ox-reveal.el b/lisp/ox-reveal.el new file mode 100644 index 0000000..5c8539e --- /dev/null +++ b/lisp/ox-reveal.el @@ -0,0 +1,808 @@ +;;; 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. +See `org-html-postamble-format' for the valid elements which +can be include." + :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-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 t + "Reveal showing slide numbers." + :group 'org-export-reveal + :type 'boolean) + +(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 + "Enable MathJax script." + :group 'org-export-reveal + :type 'boolean) + +(defcustom org-reveal-mathjax-url + "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" + "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-plugins + '(markdown highlight zoom notes) + "Reveal.js plugins." + :group 'org-export-reveal + :type '(set + (const markdown) + (const highlight) + (const zoom) + (const notes) + (const search) + (const math))) + +(defconst org-reveal-plugin-definitions + '((markdown "dist/plugin/markdown.js" "RevealMarkdown") + (highlight "dist/plugin/highlight.js" "RevealHighlight") + (zoom "dist/plugin/zoom.js" "RevealZoom") + (notes "dist/plugin/notes.js" "RevealNotes") + (search "dist/plugin/search.js" "RevealSearch") + (math "dist/plugin/math.js" "RevealMath"))) + +(defun if-format (fmt val) + (if val (format fmt val) "")) + +(defun org-reveal-attrs-list (attrs) + "Generate an HTML attribute list string from the list +ATTRS. ATTRS is expected to be a list of key-value pairs, ((key0 +. value0) (key1 . value1) ...), and for each pair of none-nil +value, an HTML attribute statement key=\"value\" is inserted into +the result string." + (mapconcat + (lambda (elem) + (let ((key (car elem)) + (value (car (cdr elem)))) + (if value (format " %s=\"%s\"" key value)))) + attrs "")) +(defun org-reveal-tag (tagname attrs content &optional sep) + "Generate an HTML tag of form CONTENT. If +SEP is given, then the CONTENT is enclosed by SEP, otherwise it is enclosed by +a '\\n'" + (let ((sep_ (or sep "\n"))) + (format "<%s %s>%s%s%s" + tagname ; The leading tagname. + (if attrs (org-html--make-attribute-string attrs) "") + sep_ content sep_ tagname))) + +(defun frag-class (frag) + ;; Return proper HTML string description of fragment style. + (and frag + (format " class=\"%s\"" (frag-style frag)))) + +(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") + (org-reveal-tag 'aside '(('class . 'notes)) + (org-export-string-as block-string 'html 'body-only))) + ((string= block-type "HTML") + (org-remove-indentation block-string))))) + +(defun org-reveal-headline (headline contents info) + "Transcode a HEADLINE element from Org to Reveal. +CONTENTS holds the contents of the headline. INFO is a plist +holding contextual information." + ;; First call org-html-headline to get the formatted HTML contents. + ;; Then add enclosing
tags to mark slides. + (setq contents (or contents "")) + (let* ((numberedp (org-export-numbered-headline-p headline info)) + (level (org-export-get-relative-level headline info)) + (text (org-export-data (org-element-property :title headline) info)) + (todo (and (plist-get info :with-todo-keywords) + (let ((todo (org-element-property :todo-keyword headline))) + (and todo (org-export-data todo info))))) + (todo-type (and todo (org-element-property :todo-type headline))) + (tags (and (plist-get info :with-tags) + (org-export-get-tags headline info))) + (priority (and (plist-get info :with-priority) + (org-element-property :priority headline))) + ;; Create the headline text. + (full-text (funcall (or (plist-get info :html-format-headline-function) + ;; nil function, return a suggestive error + (error "Export failed. It seems you are using a stable release of Org-mode. Please use Org-reveal `stable' branch for Org-mode stable releases.")) + todo todo-type priority text tags info))) + (cond + ;; Case 1: This is a footnote section: ignore it. + ((org-element-property :footnote-section-p headline) nil) + ;; Case 2. This is a deep sub-tree: export it as a list item. + ;; Also export as items headlines for which no section + ;; format has been found. + ((org-export-low-level-p headline info) + ;; Build the real contents of the sub-tree. + (let* ((type (if numberedp 'ordered 'unordered)) + (itemized-body (org-reveal-format-list-item + contents type nil info nil 'none full-text))) + (concat + (and (org-export-first-sibling-p headline info) + (org-html-begin-plain-list type)) + itemized-body + (and (org-export-last-sibling-p headline info) + (org-html-end-plain-list type))))) + ;; Case 3. Standard headline. Export it as a section. + (t + (let* ((level1 (format "h%d" (+ level (1- org-html-toplevel-hlevel)))) + (hlevel (org-reveal--get-hlevel info)) + (first-content (car (org-element-contents headline)))) + (concat + (if (or (/= level 1) + (not (org-export-first-sibling-p headline info))) + ;; Stop previous slide. + "
\n") + (if (eq level hlevel) + ;; Add an extra "
" to group following slides + ;; into vertical ones. + "
\n") + ;; Start a new slide. + (format "\n" + (org-reveal-attrs-list + `(("id" ,(or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info))) + ("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) ""))) + "\n" + ;; The HTML content of this headline. + (org-reveal-tag level1 ;;"\n%s\n" + (let ((fragment (org-element-property :REVEAL-FRAG headline))) + (if fragment `(("class" ,(concat "fragment " fragment))))) + full-text + "") + "\n" + ;; When there is no section, pretend there is an empty + ;; one to get the correct
" to stop vertical slide + ;; grouping. + "
\n") + (if (and (= level 1) + (org-export-last-sibling-p headline info)) + ;; Last head 1. Stop all slides. + "
"))))))) + +(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-stylesheets (info) + (let ((root-path (file-name-as-directory (plist-get info :reveal-root)))) + (concat + (format " + + + +" + root-path root-path root-path + (plist-get info :reveal-theme)) + (let ((extra-css (plist-get info :reveal-extra-css))) + (if extra-css (format "" extra-css) ""))))) + +(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-plugin-spec (plugin) + (cdr (assq plugin org-reveal-plugin-definitions))) + +(defun org-reveal-plugin-list (info) + (let ((configured (plist-get info :reveal-plugins))) + (cond + ((null configured) org-reveal-plugins) + ((string= configured "") nil) + (t (car (read-from-string configured)))))) + +(defun org-reveal-scripts (info) + (let* ((root-path (file-name-as-directory (plist-get info :reveal-root))) + (plugin-specs + (delq nil + (mapcar #'org-reveal-plugin-spec + (org-reveal-plugin-list info)))) + (extra-js (plist-get info :reveal-extra-js))) + (concat + (format "\n" root-path) + (mapconcat + (lambda (spec) + (format "" + root-path (nth 0 spec))) + plugin-specs + "\n") + (if (string= extra-js "") + "" + (format "\n" extra-js)) + "\n\n"))) + +(defun org-reveal-toc-headlines-r (headlines info prev_level hlevel prev_x prev_y) + "Generate toc headline text recursively." + (let* ((headline (car headlines)) + (text (org-export-data (org-element-property :title headline) info)) + (level (org-export-get-relative-level headline info)) + (x (if (<= level hlevel) (+ prev_x 1) prev_x)) + (y (if (<= level hlevel) 0 (+ prev_y 1))) + (remains (cdr headlines)) + (remain-text + (if remains + ;; Generate text for remain headlines + (org-reveal-toc-headlines-r remains info level hlevel x y) + ""))) + (concat + (cond + ((> level prev_level) + ;; Need to start a new level of unordered list + "
    \n") + ((< level prev_level) + ;; Need to end previous list item and the whole list. + "\n
\n") + (t + ;; level == prev_level, Need to end previous list item. + "\n")) + (format "
  • \n%s\n%s" + (or (org-element-property :CUSTOM_ID headline) + (org-export-get-reference headline info)) + text remain-text)))) +(defun org-reveal-toc-headlines (headlines info) + "Generate the Reveal.js contents for headlines in table of contents. +Add proper internal link to each headline." + (let ((level (org-export-get-relative-level (car headlines) info)) + (hlevel (org-reveal--get-hlevel info))) + (concat + (format "

    %s

    " + (org-export-translate "Table of Contents" :html info)) + (org-reveal-toc-headlines-r headlines info 0 hlevel 1 1) + (if headlines "
  • \n\n" "")))) + + +(defun org-reveal-toc (depth info) + "Build a slide of table of contents." + (let ((headlines (org-export-collect-headlines info depth))) + (and headlines + (format "
    \n%s
    \n" + (org-reveal-toc-headlines headlines info))))) + +(defun org-reveal-inner-template (contents info) + "Return body of document string after HTML conversion. +CONTENTS is the transcoded contents string. INFO is a plist +holding export options." + (concat + ;; Table of contents. + (let ((depth (plist-get info :with-toc))) + (when depth (org-reveal-toc depth info))) + ;; Document contents. + contents)) + +(defun org-reveal-parse-token (key &optional value) + "Return HTML tags or perform SIDE EFFECT according to key" + (case (intern key) + (split "\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 + ""))) + + +(defun org-reveal-keyword (keyword contents info) + "Transcode a KEYWORD element from Org to HTML, +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-plain-list (plain-list contents info) + "Transcode a PLAIN-LIST element from Org to Reveal. + +CONTENTS is the contents of the list. INFO is a plist holding +contextual information. + +Extract and set `attr_html' to plain-list tag attributes." + (org-reveal-tag (case (org-element-property :type plain-list) + (ordered "ol") + (unordered "ul") + (descriptive "dl")) + (org-export-read-attribute :attr_html plain-list) + contents)) + +(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-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 ((lang (org-element-property :language src-block)) + (caption (org-export-get-caption src-block)) + (code (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\"" + (org-export-solidify-link-text lbl)))))) + (if (not lang) + (format "
    \n%s
    " + (or (frag-class frag) " class=\"example\"") + label + code) + (format + "
    \n%s%s\n
    " + (if (not caption) "" + (format "" + (org-export-data caption info))) + (format "\n
    %s
    " + (or (frag-class frag) + (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)) + 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" + "\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 (plist-get info :reveal-title-slide) + (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-scripts info) + (org-reveal--build-pre/postamble 'postamble 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." + (org-element-map tree (remq 'item org-element-all-elements) + 'org-reveal-append-frag) + ;; Return the updated tree. + tree) + +(defun org-reveal-append-frag (elem) + "Read org-reveal's fragment attribute from ELEM and append +transformed fragment attribute to ELEM's attr_html plist." + (let ((frag-attr (org-export-read-attribute :attr_reveal elem :frag))) + (if frag-attr + (let ((attr-html (org-element-property :attr_html elem)) + (elem-type (org-element-type elem))) + (cond + ((and (string= elem-type 'plain-list) + (char-equal (string-to-char frag-attr) ?\()) + (mapcar* + (lambda (item frag) + "Overwrite item's `:checkbox' property with + reveal's fragment attribute." + (and (not (string= frag "none")) + (org-element-put-property + item :checkbox + (intern (cond ((string= frag t) "fragment") + (t (format "fragment %s" frag))))))) + (org-element-contents elem) + (car (read-from-string frag-attr)))) + (t + ;; Convert reveal's fragment attribute to HTML attribute. + (let ((attr-html (org-element-property :attr_html elem))) + (push + (cond ((string= frag-attr t) ":class fragment") + (t (concat ":class fragment " frag-attr))) + attr-html) + (org-element-put-property elem :attr_html attr-html)))))) + 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)))) + +;;;###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 'reval filename ".html" plist pub-dir)) + + +(provide 'ox-reveal) + +;;; ox-reveal.el ends here -- cgit v1.3.1