;;; org-preview-html.el --- Automatically preview org-exported HTML files within Emacs -*- lexical-binding: t; -*- ;; Copyright (C) 2021 Jake B ;; Author: Jake B ;; Original author of org-preview-html (until 2021-09): DarkSun ;; Url: https://github.com/jakebox/org-preview-html ;; Keywords: Org, convenience, outlines ;; Version: 0.3.1 ;; Package-Requires: ((emacs "25.1") (org "8.0")) ;; This file is NOT part of GNU Emacs. ;; 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 this program. If not, see . ;;; Commentary: ;; This minor mode provides a side-by-side preview of your org-exported HTML ;; files using the either the eww or xwidget browsers. The update frequency of ;; the preview can be configured to suit your preference. ;; ;; Quick start: ;; Put this file under your load path. ;; Enable the minor mode in an Org buffer: ;; M-x org-preview-html-mode ;; Configure options with M-x customize-group org-preview-html ;; ;; Source code ;; org-preview-html's code can be found here: ;; http://github.com/jakebox/org-preview-html ;;; Code: ;;;; Requirements (require 'org) (require 'xwidget) (require 'eww) (defgroup org-preview-html nil "Automatically preview org-exported HTML files within Emacs." :group 'org-mode :link '(url-link :tag "Homepage" "https://github.com/jakebox/org-preview-html/")) (defcustom org-preview-html-refresh-configuration 'save "Specifies how often the HTML preview will be refreshed. If `manual', update manually by running `org-preview-html-refresh'. If `save', update on save (default). If `export', update on manual export \(using `org-html-export-to-html'). If `timer', update preview on timer (`org-preview-html-timer-interval')." :type '(choice (symbol :tag "Update preview manually" manual) (symbol :tag "Update preview on save" save) (symbol :tag "Update preview on export" export) (symbol :tag "Update preview on a timer" timer)) :group 'org-preview-html) (defcustom org-preview-html-timer-interval 2 "Integer seconds to wait between exports when in 'timer mode." :type 'integer :group 'org-preview-html) (defcustom org-preview-html-viewer 'eww "Which Emacs browser `org-preview-html-mode' will use. If `eww', use eww browser (default). If `xwidget', use xwidget browser." :type '(choice (symbol :tag "Use eww" eww) (symbol :tag "Use xwidget" xwidget)) :group 'org-preview-html) (define-obsolete-variable-alias 'org-preview-html/body-only 'org-preview-html-subtree-only "Version 0.3") (defcustom org-preview-html-subtree-only nil "If non-nil, scope the preview to the current subtree." :type 'boolean :group 'org-preview-html) (defcustom org-preview-html/body-only nil "Scope the preview to the body or include the entire document. Obselete as of version 0.3, instead use `org-preview-html-subtree-only'." :type 'boolean :group 'org-preview-html) ;; Internal variables (defvar org-preview-html--browser-buffer nil) (defvar org-preview-html--previewed-buffer-name nil) (defvar org-preview-html--refresh-timer nil) (defvar-local org-preview-html--html-file nil) ;; https://emacs.stackexchange.com/questions/7116/pop-a-window-into-a-frame (defun org-preview-html-pop-window-to-frame () "Pop a window to a frame." (interactive) (let ((buffer (current-buffer))) (unless (one-window-p) (delete-window)) (display-buffer-pop-up-frame buffer nil))) ;; Taken from frame.el Emacs 27.1, copied here for better version compatibility. ;; Without this here 27.1 required. With, 25.1. (defun org-preview-html--previous-window-any-frame () (select-window (previous-window (selected-window) (> (minibuffer-depth) 0) 0)) (select-frame-set-input-focus (selected-frame))) (defun org-preview-html-refresh () "Exports the org file to HTML and refreshes the preview." (interactive) (when (eq org-preview-html-refresh-configuration 'manual) (pop-to-buffer org-preview-html--previewed-buffer-name nil t)) (org-preview-html--org-export-html) (org-preview-html--reload-preview)) (defun org-preview-html--org-export-html () "Silently export org to HTML." (let ((standard-output 'ignore)) (org-export-to-file 'html org-preview-html--html-file nil org-preview-html-subtree-only nil nil nil nil))) (defun org-preview-html--reload-preview () "Reload preview." (save-selected-window (pop-to-buffer org-preview-html--browser-buffer) (cond ((eq org-preview-html-viewer 'xwidget) (xwidget-webkit-reload)) ((eq org-preview-html-viewer 'eww) (with-selected-window (selected-window) ;; This stuff is to keep eww window scrolled at same point (let ((eww-point (point)) (eww-window-start (window-start))) (eww-reload) (goto-char eww-point) (set-window-start nil eww-window-start))))))) (defun org-preview-html--kill-preview-buffer () "Kill the preview buffer and pop back to the previewed org buffer." ;; Only do these things if the preview is around (when (bound-and-true-p org-preview-html--browser-buffer) ;; If preview is visible we first delete the window, otherwise ;; just kill the preview buffer (if (get-buffer-window org-preview-html--browser-buffer 'visible) (delete-window (get-buffer-window org-preview-html--browser-buffer))) (let ((kill-buffer-query-functions nil)) (kill-buffer org-preview-html--browser-buffer)) (pop-to-buffer org-preview-html--previewed-buffer-name))) (defun org-preview-html--run-with-timer () "Configure timer to refresh preview for `timer' mode." (setq org-preview-html--refresh-timer (run-at-time 1 org-preview-html-timer-interval #'org-preview-html-refresh))) (defun org-preview-html--config () "Configure buffer for preview: add exit hooks; configure refresh hooks." (setq org-preview-html--previewed-buffer-name (buffer-name)) (dolist (hook '(kill-buffer-hook kill-emacs-hook)) ;; Configure exit hooks (add-hook hook #'org-preview-html--stop-preview nil t)) (let ((conf org-preview-html-refresh-configuration)) (cond ((eq conf 'manual)) ((eq conf 'save) ;; On save (add-hook 'after-save-hook #'org-preview-html-refresh nil t)) ((eq conf 'timer) ;; every X seconds (org-preview-html--run-with-timer)) ((eq conf 'export) ;; On export using org-html-export-html command manually (advice-add 'org-html-export-to-html :after #'org-preview-html--reload-preview))))) (defun org-preview-html--unconfig () "Unconfigure 'org-preview-html-mode' (remove hooks and advice)." (let ((conf org-preview-html-refresh-configuration)) (cond ((eq conf 'save) (remove-hook 'after-save-hook #'org-preview-html-refresh t)) ((eq conf 'timer) (cancel-timer org-preview-html--refresh-timer)) ((eq conf 'export) (advice-remove 'org-html-export-to-html #'org-preview-html--reload-preview)))) (dolist (hook '(kill-buffer-hook kill-emacs-hook)) ;; Remove hooks (remove-hook hook #'org-preview-html--stop-preview t)) ;; Reset variables (dolist (var '(org-preview-html--browser-buffer org-preview-html--previewed-buffer-name)) (set var nil))) (defun org-preview-html--open-browser () "Open a browser to preview the exported HTML file." ;; Store the exported HTML filename (setq-local org-preview-html--html-file (concat (file-name-sans-extension buffer-file-name) ".html")) (org-preview-html--org-export-html) ;; Export the org file to HTML ;; Procedure to open the side-by-side preview (split-window-right) (other-window 1) (let ((file org-preview-html--html-file)) (cond ((eq org-preview-html-viewer 'xwidget) (xwidget-webkit-browse-url (concat "file://" file))) ((eq org-preview-html-viewer 'eww) (eww-open-file file)))) (setq org-preview-html--browser-buffer (get-buffer (buffer-name))) (org-preview-html--previous-window-any-frame)) (defun org-preview-html--start-preview () "Begin the org-preview-html preview." (when buffer-file-name (cond ((derived-mode-p 'org-mode) (message "org-preview-html has recieved a major update - xwidgets support, refresh configurations and more! \n M-x customize-group org-preview-html-mode") (org-preview-html--open-browser) (org-preview-html--config)) (t (org-preview-html-mode -1) (user-error "`%s' not supported by org-preview-html preview, only `org mode'!" major-mode))))) (defun org-preview-html--stop-preview () "Stop the org-preview-html preview." (org-preview-html--kill-preview-buffer) (org-preview-html--unconfig)) ;;;###autoload (define-minor-mode org-preview-html-mode "(Optionally) live preview for Org exports to HTML." :lighter " org-preview-html" (if org-preview-html-mode (org-preview-html--start-preview) (org-preview-html--stop-preview))) (provide 'org-preview-html) ;;; org-preview-html.el ends here