239 lines
9.2 KiB
EmacsLisp
239 lines
9.2 KiB
EmacsLisp
;;; org-preview-html.el --- Automatically preview org-exported HTML files within Emacs -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2021 Jake B <jakebox0@protonmail.com>
|
|
|
|
;; Author: Jake B <jakebox0@protonmail.com>
|
|
;; Original author of org-preview-html (until 2021-09): DarkSun <lujun9972@gmail.com>
|
|
;; 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 <https://www.gnu.org/licenses/>.
|
|
|
|
;;; 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
|