diff options
| author | Craig Jennings <c@cjennings.net> | 2026-02-14 03:21:13 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-02-14 03:21:13 -0600 |
| commit | 176ea668cdd83beddd54a24334a8a9db3cc87dfb (patch) | |
| tree | e0ee5727213fc9bf46bbf1c99dc574877410ac30 /modules/org-reveal-config.el | |
| parent | ebd8b2ce83941386b196e663d8f8ba83d7ce44c1 (diff) | |
feat(reveal): add org-reveal presentation workflow with ERT tests
Replaced pandoc-based reveal.js export with native ox-reveal integration.
New org-reveal-config.el module provides offline, self-contained HTML export
with keybindings under C-; p. Includes setup script for reveal.js 5.1.0
and 34 ERT tests covering header template and title-to-filename helpers.
Diffstat (limited to 'modules/org-reveal-config.el')
| -rw-r--r-- | modules/org-reveal-config.el | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/modules/org-reveal-config.el b/modules/org-reveal-config.el new file mode 100644 index 00000000..3ab80315 --- /dev/null +++ b/modules/org-reveal-config.el @@ -0,0 +1,162 @@ +;;; org-reveal-config.el --- Reveal.js Presentation Configuration -*- lexical-binding: t; coding: utf-8; -*- +;; author: Craig Jennings <c@cjennings.net> + +;;; Commentary: +;; Integrates ox-reveal for creating reveal.js presentations from Org files. +;; +;; Fully offline workflow using a local reveal.js clone (managed by +;; scripts/setup-reveal.sh) and self-contained HTML export. +;; +;; Keybindings (C-; p prefix): +;; - C-; p e : Export to self-contained HTML and open in browser +;; - C-; p p : Start live preview (re-exports on save) +;; - C-; p s : Stop live preview +;; - C-; p h : Insert #+REVEAL_ header block at top of current buffer +;; - C-; p n : Create new presentation file (prompts for title and location) + +;;; Code: + +;; Forward declarations for byte-compiler (ox-reveal loaded via use-package) +(defvar org-reveal-root) +(defvar org-reveal-single-file) +(defvar org-reveal-plugins) +(defvar org-reveal-highlight-css) +(defvar org-reveal-init-options) +(declare-function org-reveal-export-to-html "ox-reveal") + +;; --------------------------------- Constants --------------------------------- + +(defconst cj/reveal-root + (expand-file-name "reveal.js" user-emacs-directory) + "Local reveal.js installation directory.") + +(defconst cj/reveal-default-theme "black" + "Default reveal.js theme for new presentations.") + +(defconst cj/reveal-default-transition "slide" + "Default reveal.js slide transition for new presentations.") + +;; --------------------------------- ox-reveal --------------------------------- + +(use-package ox-reveal + :after ox + :config + (setq org-reveal-root (concat "file://" cj/reveal-root)) + (setq org-reveal-single-file t) + (setq org-reveal-plugins '(highlight notes search zoom)) + (setq org-reveal-highlight-css "%r/plugin/highlight/monokai.css") + (setq org-reveal-init-options "slideNumber:true, hash:true")) + +;; ----------------------------- Private Helpers ------------------------------- + +(defun cj/--reveal-header-template (title) + "Return the reveal.js header block string for TITLE." + (unless (stringp title) + (user-error "Title must be a string")) + (format "#+TITLE: %s +#+AUTHOR: %s +#+DATE: %s +#+REVEAL_ROOT: %s +#+REVEAL_THEME: %s +#+REVEAL_TRANS: %s +#+REVEAL_INIT_OPTIONS: slideNumber:true, hash:true +#+REVEAL_PLUGINS: (highlight notes search zoom) +#+REVEAL_HIGHLIGHT_CSS: %%r/plugin/highlight/monokai.css +#+OPTIONS: toc:nil num:nil + +" title (user-full-name) (format-time-string "%Y-%m-%d") + (concat "file://" cj/reveal-root) + cj/reveal-default-theme + cj/reveal-default-transition)) + +(defun cj/--reveal-title-to-filename (title) + "Convert TITLE to a slug-based .org filename. +Downcases TITLE, replaces whitespace runs with hyphens, appends .org." + (concat (replace-regexp-in-string "[[:space:]]+" "-" (downcase title)) + ".org")) + +(defun cj/--reveal-preview-export-on-save () + "Export current org buffer to reveal.js HTML silently. +Intended for use as a buffer-local `after-save-hook'." + (when (derived-mode-p 'org-mode) + (let ((inhibit-message t)) + (org-reveal-export-to-html)))) + +;; ----------------------------- Public Functions ------------------------------ + +(defun cj/reveal-export () + "Export current Org buffer to self-contained reveal.js HTML and open in browser." + (interactive) + (unless (derived-mode-p 'org-mode) + (user-error "Not in an Org buffer")) + (let ((html-file (org-reveal-export-to-html))) + (when html-file + (browse-url-of-file html-file) + (message "Opened presentation: %s" html-file)))) + +(defun cj/reveal-preview-start () + "Start live preview: re-export to HTML on every save. +Opens the presentation in a browser on first call. Subsequent saves +re-export silently; refresh the browser to see changes." + (interactive) + (unless (derived-mode-p 'org-mode) + (user-error "Not in an Org buffer")) + (add-hook 'after-save-hook #'cj/--reveal-preview-export-on-save nil t) + (let ((html-file (org-reveal-export-to-html))) + (when html-file + (browse-url-of-file html-file))) + (message "Live preview started — save to re-export, refresh browser to update")) + +(defun cj/reveal-preview-stop () + "Stop live preview by removing the after-save-hook." + (interactive) + (remove-hook 'after-save-hook #'cj/--reveal-preview-export-on-save t) + (message "Live preview stopped")) + +(defun cj/reveal-insert-header () + "Insert a #+REVEAL_ header block at the top of the current Org buffer." + (interactive) + (unless (derived-mode-p 'org-mode) + (user-error "Not in an Org buffer")) + (let ((title (read-from-minibuffer "Presentation title: "))) + (save-excursion + (goto-char (point-min)) + (insert (cj/--reveal-header-template title))) + (message "Inserted reveal.js headers"))) + +(defun cj/reveal-new () + "Create a new reveal.js presentation file. +Prompts for a title and save location, then opens the file with +reveal.js headers pre-filled." + (interactive) + (let* ((title (read-from-minibuffer "Presentation title: ")) + (default-dir (expand-file-name "~/")) + (file (read-file-name "Save presentation to: " default-dir nil nil + (cj/--reveal-title-to-filename title)))) + (when (file-exists-p file) + (user-error "File already exists: %s" file)) + (find-file file) + (insert (cj/--reveal-header-template title)) + (insert "* Slide 1\n\n") + (save-buffer) + (message "New presentation: %s" file))) + +;; -------------------------------- Keybindings -------------------------------- + +(global-set-key (kbd "C-; p e") #'cj/reveal-export) +(global-set-key (kbd "C-; p p") #'cj/reveal-preview-start) +(global-set-key (kbd "C-; p s") #'cj/reveal-preview-stop) +(global-set-key (kbd "C-; p h") #'cj/reveal-insert-header) +(global-set-key (kbd "C-; p n") #'cj/reveal-new) + +(with-eval-after-load 'which-key + (which-key-add-key-based-replacements + "C-; p" "presentations" + "C-; p e" "export & open" + "C-; p p" "start live preview" + "C-; p s" "stop live preview" + "C-; p h" "insert headers" + "C-; p n" "new presentation")) + +(provide 'org-reveal-config) +;;; org-reveal-config.el ends here |
