Commit f30a74ae5139

Vincent Demeester <vincent@sbr.pm>
2023-11-20 12:51:18
tools/emacs: some org-mode and denote configuration
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent ee865fb
tools/emacs/config/config-org.el
@@ -9,7 +9,7 @@
 (defconst org-directory "~/desktop/org/"
   "org-mode directory, where most of the org-mode file lives")
 ;; P.A.R.A. setup
-(defconst org-inbox-file (expand-file-name "inbox.org" org-directory)
+(defconst org-inbox-file (expand-file-name "20231120T124316--inbox__inbox.org" org-directory)
   "New stuff collected in this file.")
 
 (defconst org-archive-dir (expand-file-name "archive" org-directory)
@@ -19,7 +19,7 @@
   "Project files directory.")
 (defconst org-projects-completed-dir (expand-file-name "projects" org-archive-dir)
   "Directory of completed project files.")
-(defconst org-projects-future-file (expand-file-name "future.org" org-projects-dir)
+(defconst org-projects-future-file (expand-file-name "20231120T124316--future-projects-incubation__project_future.org" org-projects-dir)
   "Future projects are collected in this file.")
 
 (defconst org-areas-dir (expand-file-name "areas" org-directory)
@@ -48,6 +48,16 @@
 (set-register ?a `(file . ,org-areas-dir))
 (set-register ?r `(file . ,org-resources-dir))
 
+(defun vde/org-mode-hook ()
+  "Org-mode hook"
+  (setq show-trailing-whitespace t)
+  (when (not (eq major-mode 'org-agenda-mode))
+    (setq fill-column 90)
+    (auto-revert-mode)
+    (auto-fill-mode)
+    (org-indent-mode)
+    (add-hook 'before-save-hook #'save-and-update-includes nil 'make-it-local)))
+
 (use-package org
   :mode (("\\.org$" . org-mode)
          ("\\.org.draft$" . org-mode))
@@ -57,8 +67,9 @@
          ("C-c o a a" . org-agenda)
          ("C-c o s" . org-sort)
          ("<f12>" . org-agenda))
+  :hook (org-mode . vde/org-mode-hook)
   :custom
-  (org-reverse-note-order '(("inbox.org" . t) ;; Insert items on top of inbox
+  (org-reverse-note-order '((org-inbox-file . t) ;; Insert items on top of inbox
                             (".*" . nil)))    ;; On any other file, insert at the bottom
   (org-archive-location (concat org-archive-dir "/%s::datetree/"))
   (org-agenda-file-regexp "^[a-zA-Z0-9-_]+.org$")
@@ -90,7 +101,24 @@
   :after org)
 
 (use-package org-tempo
-  :after (org))
+  :after (org)
+  :custom
+  (org-structure-template-alist '(("a" . "aside")
+				  ("c" . "center")
+				  ("C" . "comment")
+				  ("e" . "example")
+				  ("E" . "export")
+				  ("Ea" . "export ascii")
+				  ("Eh" . "export html")
+				  ("El" . "export latex")
+				  ("q" . "quote")
+				  ("s" . "src")
+				  ("se" . "src emacs-lisp")
+				  ("sE" . "src emacs-lisp :results value code :lexical t")
+				  ("sg" . "src go")
+				  ("sr" . "src rust")
+				  ("sp" . "src python")
+				  ("v" . "verse"))))
 
 ;; (use-package org-bullets
 ;;   :if (not window-system)
@@ -137,8 +165,15 @@
 
 ;; Using denote as the "source" of my second brain *in* org-mode.
 (use-package denote
-  :commands (denote denote-date denote-link-or-create denote-open-or-create denote-signature)
-  :after org)
+  :after org
+  :custom
+  (denote-directory org-directory)
+  (denote-rename-buffer-format "๐Ÿ“ %t")
+  :config
+  (denote-rename-buffer-mode 1)
+  (require 'denote-journal-extras)
+  (setq denote-journal-extras-directory (expand-file-name "journal" org-directory)
+	denote-journal-extras-title-format 'day-date-month-year))
 
 ;; (use-package org
 ;;   ;; :ensure org-plus-contrib ;; load from the package instead of internal
tools/emacs/lisp/denote-journal-extras.el
@@ -0,0 +1,213 @@
+;;; denote-journal-extras.el --- Convenience functions for daily journaling  -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2023  Free Software Foundation, Inc.
+
+;; Author: Protesilaos Stavrou <info@protesilaos.com>
+;; Maintainer: Denote Development <~protesilaos/denote@lists.sr.ht>
+;; URL: https://git.sr.ht/~protesilaos/denote
+;; Mailing-List: https://lists.sr.ht/~protesilaos/denote
+
+;; 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 is a set of optional convenience functions that used to be
+;; provided in the Denote manual.  They facilitate the use of Denote
+;; for daily journaling.
+
+;;; Code:
+
+(require 'denote)
+
+(defgroup denote-journal-extras nil
+  "Denote for daily journaling."
+  :group 'denote
+  :link '(info-link "(denote) Top")
+  :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote"))
+
+(defcustom denote-journal-extras-directory
+  (expand-file-name "journal" denote-directory)
+  "Directory for storing daily journal entries.
+This can either be the same as the variable `denote-directory' or
+a subdirectory of it.
+
+A value of nil means to use the variable `denote-directory'.
+Journal entries will thus be in a flat listing together with all
+other notes.  They can still be retrieved easily by searching for
+the `denote-journal-extras-keyword'."
+  :group 'denote-journal-extras
+  :type '(choice (directory :tag "Provide directory path (is created if missing)")
+                 (const :tag "Use the `denote-directory'" nil)))
+
+(defcustom denote-journal-extras-keyword "journal"
+  "Single word keyword to tag journal entries.
+It is used by `denote-journal-extras-new-entry' to add a keyword
+to the newly created file."
+  :group 'denote-journal-extras
+  :type 'string)
+
+(defcustom denote-journal-extras-title-format 'day-date-month-year-24h
+  "Date format to construct the title with `denote-journal-extras-new-entry'.
+The value is either a symbol or an arbitrary string that is
+passed to `format-time-string' (consult its documentation for the
+technicalities).
+
+Acceptable symbols and their corresponding styles are:
+
+| Symbol                  | Style                             |
+|-------------------------+-----------------------------------|
+| day                     | Monday                            |
+| day-date-month-year     | Monday 19 September 2023          |
+| day-date-month-year-24h | Monday 19 September 2023 20:49    |
+| day-date-month-year-12h | Monday 19 September 2023 08:49 PM |
+
+With a nil value, make `denote-journal-extras-new-entry' prompt
+for a title."
+  :group 'denote-journal-extras
+  :type '(choice
+          (const :tag "Prompt for title with `denote-journal-extras-new-entry'" nil)
+          (const :tag "Monday"
+                 :doc "The `format-time-string' is: %A"
+                 day)
+          (const :tag "Monday 19 September 2023"
+                 :doc "The `format-time-string' is: %A %e %B %Y"
+                 day-date-month-year)
+          (const :tag "Monday 19 September 2023 20:49"
+                 :doc "The `format-time-string' is: %A %e %B %Y %H:%M"
+                 day-date-month-year-24h)
+          (const :tag "Monday 19 September 2023 08:49 PM"
+                 :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p"
+                 day-date-month-year-12h)
+          (string :tag "Custom string with `format-time-string' specifiers")))
+
+(defcustom denote-journal-extras-hook nil
+  "Normal hook called after `denote-journal-extras-new-entry'.
+Use this to, for example, set a timer after starting a new
+journal entry (refer to the `tmr' package on GNU ELPA)."
+  :group 'denote-journal-extras
+  :type 'hook)
+
+(defun denote-journal-extras-directory ()
+  "Make the variable `denote-journal-extras-directory' and its parents."
+  (when-let (((stringp denote-journal-extras-directory))
+             (directory (file-name-as-directory (expand-file-name denote-journal-extras-directory))))
+    (when (not (file-directory-p denote-journal-extras-directory))
+      (make-directory directory :parents))
+    directory))
+
+(defun denote-journal-extras-daily--title-format (&optional date)
+  "Return present date in `denote-journal-extras-title-format' or prompt for title.
+With optional DATE, use it instead of the present date.  DATE has
+the same format as that returned by `current-time'."
+  (format-time-string
+   (if (and denote-journal-extras-title-format
+            (stringp denote-journal-extras-title-format))
+       denote-journal-extras-title-format
+     (pcase denote-journal-extras-title-format
+       ('day "%A")
+       ('day-date-month-year "%A %e %B %Y")
+       ('day-date-month-year-24h "%A %e %B %Y %H:%M")
+       ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p")
+       (_ (denote-title-prompt (format-time-string "%F" date)))))
+   date))
+
+(defun denote-journal-extras--get-template ()
+  "Return template that has `journal' key in `denote-templates'.
+If no template with `journal' key exists but `denote-templates'
+is non-nil, prompt the user for a template among
+`denote-templates'.  Else return nil.
+
+Also see `denote-journal-extras-new-entry'."
+  (if-let ((template (alist-get 'journal denote-templates)))
+      template
+    (when denote-templates
+      (denote-template-prompt))))
+
+(defun denote-journal-extras--get-date (date)
+  "Return a valid DATE for `format-time-string'.
+If DATE is a list, return it as-is.  If it is a string, parse it
+with `denote--valid-date'.  Else return the `current-time'."
+  (cond
+   ((listp date) date)
+   ((stringp date) (denote--valid-date date))
+   (t (current-time))))
+
+;;;###autoload
+(defun denote-journal-extras-new-entry (&optional date)
+  "Create a new journal entry in variable `denote-journal-extras-directory'.
+Use `denote-journal-extras-keyword' as a keyword for the newly
+created file.  Set the title of the new entry according to the
+value of the user option `denote-journal-extras-title-format'.
+
+With optional DATE as a prefix argument, prompt for a date.  If
+`denote-date-prompt-use-org-read-date' is non-nil, use the Org
+date selection module.
+
+When called from Lisp DATE is a string and has the same format as
+that covered in the documentation of the `denote' function.  It
+is internally processed by `denote-journal-extras--get-date'."
+  (interactive (list (when current-prefix-arg (denote-date-prompt))))
+  (let ((internal-date (denote-journal-extras--get-date date))
+        (denote-user-enforced-denote-directory (denote-journal-extras-directory)))
+    (denote
+     (denote-journal-extras-daily--title-format internal-date)
+     `(,denote-journal-extras-keyword)
+     nil nil date
+     (denote-journal-extras--get-template))
+    (run-hooks 'denote-journal-extras-hook)))
+
+(defun denote-journal-extras--entry-today (&optional date)
+  "Return list of files matching a journal for today or optional DATE.
+DATE has the same format as that returned by `denote-journal-extras--get-date'."
+  (denote-directory-files-matching-regexp
+   (format "%sT[0-9]\\{6\\}.*_%s"
+           (format-time-string "%Y%m%d" date)
+           denote-journal-extras-keyword)))
+
+;;;###autoload
+(defun denote-journal-extras-new-or-existing-entry (&optional date)
+  "Locate an existing journal entry or create a new one.
+A journal entry is one that has `denote-journal-extras-keyword' as
+part of its file name.
+
+If there are multiple journal entries for the current date,
+prompt for one using minibuffer completion.  If there is only
+one, visit it outright.  If there is no journal entry, create one
+by calling `denote-journal-extra-new-entry'.
+
+With optional DATE as a prefix argument, prompt for a date.  If
+`denote-date-prompt-use-org-read-date' is non-nil, use the Org
+date selection module.
+
+When called from Lisp, DATE is a string and has the same format
+as that covered in the documentation of the `denote' function.
+It is internally processed by `denote-journal-extras--get-date'."
+  (interactive
+   (list
+    (when current-prefix-arg
+      (denote-date-prompt))))
+  (let* ((internal-date (denote-journal-extras--get-date date))
+         (files (denote-journal-extras--entry-today internal-date)))
+    (cond
+     ((length> files 1)
+      (find-file (completing-read "Select journal entry: " files nil :require-match)))
+     (files
+      (find-file (car files)))
+     (t
+      (denote-journal-extras-new-entry date)))))
+
+(provide 'denote-journal-extras)
+;;; denote-journal-extras.el ends here
tools/emacs/lisp/org-extra-emphasis.el
@@ -0,0 +1,805 @@
+;;; org-extra-emphasis.el --- Extra Emphasis markers for Org -*- lexical-binding: t; coding: utf-8-emacs; -*-
+
+;; Copyright (C) 2022 Jambunathan K <kjambunathan at gmail dot com>
+;; Copyright (C) 2004-2022 Free Software Foundation, Inc.
+
+;; Author: Jambunathan K <kjambunathan at gmail dot com>
+;; Keywords: org
+;; Homepage: https://github.com/kjambunathan/org-extra-emphasis
+;; Version: 1.0
+;; Package-Requires: ((ox-odt "9.5.3.467"))
+
+;; 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Overview
+;; ========
+;;
+;; This library provides two additional markers `!!' and `!@' over
+;; and above those in `org-emphasis-alist'.'
+;;
+;; - Text enclosed in `!!' is highlighted in yellow, and exported likewise
+;; - Text enclosed in `!@' is displayed in red, and exported likewise
+;;
+;; Following backends are supported: HTML and ODT. For export of extra
+;; emphasis markers to the ODT side, you need
+;; [[https://github.com/kjambunathan/org-mode-ox-odt][Enhanced ODT]]
+;; exporter with version >= 9.5.3.467 (dtd. June 14, 2022 IST).  This
+;; is the first version of the exporter that defines the user option
+;; `org-odt-extra-styles'.
+;;
+;; Example
+;; =======
+;;
+;; Setup
+;; =====
+;;
+;; Add the following to your `user-init-file' and restart Emacs.
+;;
+;;    (requrie 'org-extra-emphasis)
+;;
+;; Test Run
+;; ========
+;;
+;; 1. Create an `org' file, say `org-export-emphasis.org' and fill it
+;;    with following content or you can download the file from
+;;    https://raw.githubusercontent.com/kjambunathan/org-extra-emphasis/main/org-extra-emphasis.org
+
+	  ;; #+TITLE: Test file for ==org-extra-emphasis== library
+
+	  ;; * Demo of extra emphasis markers ==!!== and ==!@==
+
+	  ;; !!Ea consectetur laboris adipiscing et ipsum labore esse qui minim
+	  ;; pariatur et sunt sunt nostrud anim laborum culpa.!!
+
+	  ;; !@Minim reprehenderit excepteur elit, dolore elit, veniam, eu.
+	  ;; Ullamco dolore elit, cupidatat sed labore ea aute.!@
+
+	  ;; Pariatur !!et lorem cupidatat !@minim irure!@ proident, ad.!!  Eiusmod
+	  ;; sunt et lorem labore ex aliqua aute esse.
+
+	  ;; Ut mollit !@duis velit est est magna in quis ipsum.  !!Aliqua aliqua
+	  ;; non laboris exercitation cupidatat aliqua incididunt.!!  Qui voluptate
+	  ;; irure aute occaecat laborum cillum est.!@  Quis magna dolor ullamco
+	  ;; magna do consectetur est laborum enim ut.
+
+	  ;; * !!Demo of extra emphasis markers in a styled paragraph!!
+
+	  ;; #+ATTR_ODT: :target "extra_styles"
+	  ;; #+begin_src nxml
+	  ;; <style:style style:name="Warn"
+	  ;;	     style:parent-style-name="Text_20_body"
+	  ;;	     style:family="paragraph">
+	  ;;   <style:paragraph-properties>
+	  ;;     <style:tab-stops />
+	  ;;   </style:paragraph-properties>
+	  ;;   <style:text-properties fo:background-color="#ff0000"
+	  ;;		       fo:color="#ffffff"
+	  ;;		       fo:font-size="20pt"
+	  ;;		       fo:font-style="italic"
+	  ;;		       fo:font-weight="bold" />
+	  ;; </style:style>
+	  ;; #+end_src
+
+	  ;; #+ATTR_ODT: :style "Warn"
+	  ;; Proident, duis dolore consectetur sed nisi ea pariatur.  Esse
+	  ;; proident, cillum duis qui ullamco sint cillum magna.  !!Eiusmod
+	  ;; veniam, !@sint officia!@ non consectetur laboris cillum.!!  Cillum
+	  ;; mollit consequat eu dolore ullamco qui reprehenderit anim cillum
+	  ;; in consectetur consequat sunt dolore aliquip voluptate
+	  ;; consectetur anim ea.  Voluptate nisi est incididunt aliquip
+	  ;; excepteur aliqua id do enim ut non consequat.
+;;
+;; 2. Note that portions of text marked with `!!' and `!@' are fontified as described above.
+;;
+;; 3. Export the file to HTML with `C-c C-e h O'.
+;;
+;;    Note that the text enclosed in the above emphasis markers are
+;;    colorized in HTML file.
+;;
+;; 4. Export the file to ODT with `C-c C-e o O'.
+;;
+;;    Note that the text enclosed in the above emphasis markers are
+;;    colorized in ODT file.
+;;
+;; The HTML, ODT, PDF generated in steps (3) and (4) above are
+;; available at https://github.com/kjambunathan/org-extra-emphasis and
+;; the screenshots can be seen in https://github.com/kjambunathan/org-extra-emphasis/tree/main/screenshots
+;;
+
+;; Default Settings
+;; ================
+;;
+;; 16 Emphasis Markers
+;; ===================
+;;
+;; This library defines the following 16 emphasis markers,
+;;
+;; |----+----+----+----|
+;; | !! | !@ | !% | !& |
+;; |----+----+----+----|
+;; | @! | @@ | @% | @& |
+;; |----+----+----+----|
+;; | %! | %@ | %% | %& |
+;; |----+----+----+----|
+;; | &! | &@ | &% | && |
+;; |----+----+----+----|
+;;
+;; The above markers are all pairings of the following four characters:
+;;     ! @ % &
+;;
+;; It is hoped that these set of emphasis markers don't pose issues
+;; while exporting.
+;;
+;; 17 Extra Emphasis Faces
+;; =======================
+;;
+;; This library defines 17 faces:
+;;
+;; - one base face `org-extra-emphasis'
+;; - 16 more faces `org-extra-emphasis-01',`org-extra-emphasis-02',
+;;  ..., `org-extra-emphasis-16'.
+;;
+;; The later 16 faces derive from `org-extra-emphasis' face.  Of
+;; these, only the first two faces `org-extra-emphasis-01' and
+;; `org-extra-emphasis-02' are explicitly configured.  If you are
+;; using more than 2 emphasis markers, you may want to configure the
+;; other 14 faces.
+;;
+;; `org-extra-emphasis-alist' already associated 16 emphasis markers
+;; with 16 different faces.
+;;
+;; Customization
+;; =============
+;;
+;; Configuring your own Emphasis Markers
+;; =====================================
+;;
+;; 16 numbers of emphasis markers should suffice in practice.
+;; However, if none of the above emphasis markers resonate with you,
+;; you can customize `org-extra-emphasis-alist', and plug in your own
+;; markers.  When choosing your own marker, ensure that you exercise
+;; some care.  For example, if you choose `#' as a marker you are
+;; likely to get malformed `html' and `odt' files.
+;;
+;; Configuring Extra Emphasis Faces
+;; ===============================
+;;
+;; You can use `M-x customize-group RET org-extra-emphasis-faces RET'
+;; to configure the extra emphasis faces.
+;;
+;; Disabling the Extra Emphasis
+;; =============================
+;;
+;; You can use `M-x org-extra-emphasis-mode' to toggle this feature.
+;;
+;; Adding additional export backends
+;; =================================
+;;
+;; To add additional backends, modify `org-extra-emphasis-formatter'
+;; and `org-extra-emphasis-build-backend-regexp'.
+
+;;; Code:
+
+(require 'org)
+(require 'ox-odt)
+(require 'rx)
+(require 'htmlfontify)
+
+;;; PART-1: `org-extra-emphasis-mode'
+
+;;;; Internal Variables
+
+(defvar org-extra-emphasis-backends
+  '(html odt ods))
+
+(defvar org-extra-emphasis-info
+  (list :enabled nil))
+
+;; Helper snippets to convert a Emacs Face to Inine CSS and ODT Text Properties
+;;
+;; (defun org-extra-emphasis-emacs-face->inline-css (face)
+;;   (let ((s (cdr (hfy-face-to-css-default face))))
+;;     (when (string-match (rx-to-string '(and "{" (group (zero-or-more any)) "}")) s)
+;;       (format "<span style=\"%s\">%%s</span>" (match-string 1 s)))))
+;;
+;; (org-extra-emphasis-emacs-face->inline-css 'hi-yellow)
+;; (org-extra-emphasis-emacs-face->inline-css 'hi-red-b)
+;;
+;; (defun org-extra-emphasis-emacs-face->odt-text-properties (face)
+;;   (org-odt--lisp-to-xml
+;;    (assoc 'style:text-properties
+;;	  (org-odt--xml-to-lisp
+;;	   (cdr (org-odt-hfy-face-to-css face))))))
+;;
+;; (org-extra-emphasis-emacs-face->odt-text-properties 'hi-yellow)
+;; (org-extra-emphasis-emacs-face->odt-text-properties 'hi-red-b)
+
+(defun org-extra-emphasis-update (&rest _ignored)
+  "Workhorse function that responds to configuration changes.
+
+Current state is maintined in `org-extra-emphasis-info', a plist."
+  ;; When `org-extra-emaphasis' is ON, override use
+  ;; `org-extra-emphasis-org-do-emphasis-faces'.
+  ;; Otherwise, use `org-do-emphasis-faces'.
+  (cond
+   ((plist-get org-extra-emphasis-info :enabled)
+    (advice-add 'org-do-emphasis-faces :override
+		'org-extra-emphasis-org-do-emphasis-faces))
+   (t
+    (advice-remove 'org-do-emphasis-faces
+		   'org-extra-emphasis-org-do-emphasis-faces)))
+  ;; `org-extra-emphasis-alist' is effective only if
+  ;; `org-extra-emphasis' is enabled.
+  (plist-put org-extra-emphasis-info :work-alist
+	     (when (plist-get org-extra-emphasis-info :enabled)
+	       (plist-get org-extra-emphasis-info :alist)))
+  ;; Set properties that control fontification.
+  ;; The property names and their values mimics the corresponding
+  ;; variables in `org-set-emph-re'.
+  (plist-put org-extra-emphasis-info :org-emphasis-alist
+	     (when (and (boundp 'org-emphasis-regexp-components)
+			org-emphasis-alist org-emphasis-regexp-components)
+	       (append (plist-get org-extra-emphasis-info :work-alist)
+		       org-emphasis-alist)))
+  (plist-put org-extra-emphasis-info :org-emph-re-template
+	     (when (and (boundp 'org-emphasis-regexp-components)
+			org-emphasis-alist org-emphasis-regexp-components)
+	       (pcase-let*
+		   ((`(,pre ,post ,border ,body ,nl) org-emphasis-regexp-components)
+		    (body (if (<= nl 0) body
+			    (format "%s*?\\(?:\n%s*?\\)\\{0,%d\\}" body body nl))))
+		 (format (concat "\\([%s]\\|^\\)" ;before markers
+				 "\\(\\(%%s\\)\\([^%s]\\|[^%s]%s[^%s]\\)\\3\\)"
+				 "\\([%s]\\|$\\)") ;after markers
+			 pre border border body border post))))
+  (plist-put org-extra-emphasis-info :org-emph-re
+	     (format (plist-get org-extra-emphasis-info :org-emph-re-template)
+		     (rx-to-string
+		      `(or ,@(mapcar #'car
+				     (cl-remove-if (lambda (l)
+						     (eq 'verbatim (nth 2 l)))
+						   (plist-get org-extra-emphasis-info :org-emphasis-alist)))))))
+  (plist-put org-extra-emphasis-info :org-verbatim-re
+	     (format (plist-get org-extra-emphasis-info :org-emph-re-template)
+		     (rx-to-string
+		      `(or ,@(mapcar #'car
+				     (cl-remove-if-not (lambda (l)
+							 (eq 'verbatim (nth 2 l)))
+						       (plist-get org-extra-emphasis-info :org-emphasis-alist)))))
+		     (rx-to-string
+		      `(or ,@(mapcar #'car
+				     (cl-remove-if-not (lambda (l)
+							 (eq 'verbatim (nth 2 l)))
+						       (plist-get org-extra-emphasis-info :org-emphasis-alist)))))))
+  ;; Set properties that control Export backends
+  ;; - Regexp to search for in the final exported document
+  (plist-put org-extra-emphasis-info :export-alist
+	     (org-extra-emphasis-build-backend-regexp))
+
+  ;; - Generate ODT character styles for the extra emphasis faces and
+  ;;   dump those in `org-odt-extra-styles' and `org-ods-automatic-styles'.
+  (plist-put org-extra-emphasis-info :odt-extra-styles
+	     (let* ((odt-styles
+		     (concat (mapconcat #'identity
+					(cl-loop for (_marker face) in (plist-get org-extra-emphasis-info :alist)
+						 collect (cdr (org-odt-hfy-face-to-css face)))
+					"\n\n"))))
+	       (with-no-warnings
+		 (unless (boundp 'org-odt-extra-styles)
+		   (message "`org-odt-extra-styles' not found.  Upgrade to `ox-odt-9.5.3.467' or later.")
+		   ;; (sleep-for 2)
+		   (setq org-odt-extra-styles nil))
+		 (setq org-odt-extra-styles
+		       (concat (or (when (boundp 'org-odt-extra-styles)
+				     (get 'org-odt-extra-styles 'saved-value))
+				   "")
+			       "\n\n"
+			       odt-styles))
+		 (setq org-ods-automatic-styles
+		       (concat (or (when (boundp 'org-ods-automatic-styles)
+				     (get 'org-ods-automatic-styles 'saved-value))
+				   "")
+			       "\n\n"
+			       odt-styles))
+		 (message "`org-odt-extra-styles' and `org-ods-automatic-styles' is updated for this session")
+		 ;; (sleep-for 1)
+		 )
+	       odt-styles))
+  ;; Re-fontify all Org buffers based on current configuration.
+  (dolist (buffer (buffer-list))
+    (with-current-buffer buffer
+      (when (derived-mode-p 'org-mode)
+	(font-lock-flush)))))
+
+;;;; Fontify Extra Emphasis Markers
+
+(defun org-extra-emphasis-org-do-emphasis-faces (limit)
+  "Workhorse function that does fontification This function is
+based on `org-do-emphasis-faces'.  The property names and values
+correspond to the variables used in `org-do-emphasis-faces'.  Key
+differences are:
+
+    - `:org-emphasis-alist' includes entries for both standard
+      emphasis markers and extra emphasis markers.
+
+    - The regexes used for search-based fontification allow for
+      the possibility that the emphasis markers _in all
+      likelihood_ are multi-char strings, as opposed to single
+      chars."
+  (let* ((quick-re (format "\\([%s]\\|^\\)\\(%s\\)"
+			   (car org-emphasis-regexp-components)
+			   (rx-to-string
+			    `(or ,@(mapcar #'car (plist-get org-extra-emphasis-info :org-emphasis-alist)))))))
+    (catch :exit
+      (while (re-search-forward quick-re limit t)
+	(let* ((marker (match-string 2))
+	       (verbatim? (member marker '("~" "="))))
+	  (when (save-excursion
+		  (goto-char (match-beginning 0))
+		  (and
+		   ;; Do not match table hlines.
+		   (not (and (equal marker "+")
+			     (org-match-line
+			      "[ \t]*\\(|[-+]+|?\\|\\+[-+]+\\+\\)[ \t]*$")))
+		   ;; Do not match headline stars.  Do not consider
+		   ;; stars of a headline as closing marker for bold
+		   ;; markup either.
+		   (not (and (equal marker "*")
+			     (save-excursion
+			       (forward-char)
+			       (skip-chars-backward "*")
+			       (looking-at-p org-outline-regexp-bol))))
+		   ;; Match full emphasis markup regexp.
+		   (looking-at (if verbatim? (plist-get org-extra-emphasis-info :org-verbatim-re)
+				 (plist-get org-extra-emphasis-info :org-emph-re)))
+		   ;; Do not span over paragraph boundaries.
+		   (not (string-match-p org-element-paragraph-separate
+					(match-string 2)))
+		   ;; Do not span over cells in table rows.
+		   (not (and (save-match-data (org-match-line "[ \t]*|"))
+			     (string-match-p "|" (match-string 4))))))
+	    (pcase-let ((`(,_ ,face ,_) (assoc marker (plist-get org-extra-emphasis-info :org-emphasis-alist)))
+			(m (if org-hide-emphasis-markers 4 2)))
+	      (font-lock-prepend-text-property
+	       (match-beginning m) (match-end m) 'face face)
+	      (when verbatim?
+		(org-remove-flyspell-overlays-in
+		 (match-beginning 0) (match-end 0))
+		(remove-text-properties (match-beginning 2) (match-end 2)
+					'(display t invisible t intangible t)))
+	      (add-text-properties (match-beginning 2) (match-end 2)
+				   '(font-lock-multiline t org-emphasis t))
+	      (when (and org-hide-emphasis-markers
+			 (not (org-at-comment-p)))
+		(add-text-properties (match-end 4) (match-beginning 5)
+				     '(invisible t))
+		(add-text-properties (match-beginning 3) (match-end 3)
+				     '(invisible t)))
+	      (throw :exit t))))))))
+
+;; There is no `:set' function for `deffaces'.  So, when the extra
+;; faces `org-extra-emphasis-01', `org-extra-emphasis-02' reconfigured,
+;; we don't get a notification.  The following export hook ensures
+;; that `org-extra-emphasis-info' is in sync with user configuration.
+(add-hook 'org-export-before-processing-hook 'org-extra-emphasis-update)
+
+;;;; Export Extra Emphasis Markers
+
+(defun org-extra-emphasis-formatter (marker text backend)
+  "Style TEXT in the same font face as the face MARKER is mapped to.
+Note that TEXT is in BACKEND format.
+
+This currently supports HTML and ODT backends.
+
+See `org-extra-emphasis-alist' for MARKER to face mappings."
+  (let* ((face (car (assoc-default marker (plist-get org-extra-emphasis-info :work-alist))))
+         (encode-attribute-value
+	  (lambda (text)
+	    (dolist (pair '(("&" . "&amp;")
+			    ("<" . "&lt;")
+			    (">" . "&gt;")
+			    ("'" . "&apos;")
+			    ("\"" . "&quot;")))
+	      (setq text (replace-regexp-in-string (car pair) (cdr pair) text t t)))
+	    text)))
+    (cl-case backend
+      ((odt ods)
+       (format "<text:span text:style-name=\"%s\">%s</text:span>"
+	       (car (org-odt-hfy-face-to-css face)) text))
+      (html
+       (format "<span class=\"%s\" style=\"%s\">%s</span>"
+	       face
+	       ;; An alternate implementation of
+	       ;; `hfy-face-to-css-default' which performs correctly
+	       ;; when a face specifies a `:family', and/or inherits
+	       ;; some attributes from other faces.  Note that the
+	       ;; flattening (or non-duplication) of face attributes
+	       ;; here is done by Emacs itself.
+	       (mapconcat (lambda (x)
+			    (when (cdr x)
+			      (format "%s: %s;" (car x)
+                                      (funcall encode-attribute-value (cdr x)))))
+			  (hfy-face-to-style-i
+			   (cl-loop with props = (mapcar #'car face-attribute-name-alist)
+				    for prop in props
+				    for value = (face-attribute face prop nil 'default)
+				    unless (eq prop :inherit)
+				    append (list prop value)))
+			  " ")
+	       text))
+      (_ text))))
+
+(defun org-extra-emphasis-build-backend-regexp ()
+  "Regexp to search for emphasized text in exported file.
+This function transcode an emphasis MARKER which is in plain text
+format, to the BACKEND format.  That is, if you use `<<' as an
+emphasis marker, you need to search for `&lt;&lt;' in the
+exported HTML file.
+
+See `org-extra-emphasis-alist' for more information"
+  (cl-loop for (marker . spec) in (plist-get org-extra-emphasis-info :work-alist) collect
+	   (cons marker
+		 (cl-loop for backend in org-extra-emphasis-backends collect
+			  (cons backend
+				(rx-to-string `(and ,(org-export-data-with-backend marker backend nil)
+						    (group (minimal-match
+							    (zero-or-more (or any "\n"))))
+						    ,(org-export-data-with-backend marker backend nil))))))))
+
+(defun org-extra-emphasis-plain-text-filter (text backend _info)
+  "Transcode TEXT in to BACKEND format.
+Uses `org-extra-emphasis-formatter' to do the transcoding.
+
+Search TEXT for one or more transcoded MARKERs, and mark it up as
+specified in `org-extra-emphasis-alist'."
+  (with-temp-buffer
+    (insert text)
+    (cl-loop for (marker . spec) in (plist-get org-extra-emphasis-info :export-alist)
+	     for regex = (assoc-default backend spec)
+	     do (goto-char (point-min))
+	     (if (not regex) text
+	       (while (re-search-forward regex nil t)
+		 (let* ((contents (match-string 1))
+			(emphasized-contents (save-match-data
+					       (org-extra-emphasis-formatter
+						marker contents backend))))
+		   (replace-match emphasized-contents t t)))))
+    (buffer-substring-no-properties (point-min) (point-max))))
+
+;; Install export filter for transcoding extra emphasis markers.
+(defun org-extra-emphasis-update-filter-functions (&optional export-filter-functions)
+  (let* ((all-filter-functions (thread-last org-export-filters-alist
+					    (seq-map #'cdr)
+					    (seq-sort #'string<))))
+    (dolist (filter-fn '(org-extra-emphasis-plain-text-filter org-extra-emphasis-strip-zws-maybe))
+      (dolist (it all-filter-functions)
+        (set it (delq filter-fn (symbol-value it))))
+      (dolist (it export-filter-functions)
+        (add-to-list it filter-fn)))))
+
+;;;; User Options & Commands
+
+;;;;; Custom Groups
+
+(defgroup org-extra-emphasis nil
+  "Options for highlighting and exporting extra emphasis markers in Org files."
+  :tag "Org Extra Emphasis"
+  :group 'org)
+
+(defgroup org-extra-emphasis-faces nil
+  "Faces for Org Extra Emphasis."
+  :group 'org-extra-emphasis
+  :group 'faces)
+
+;;;; Custom Faces
+
+(defface org-extra-emphasis nil
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-01
+  '((t (:inherit org-extra-emphasis :background "yellow")))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-02
+  '((t (:inherit org-extra-emphasis :foreground "red")))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-03
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-04
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-05
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-06
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-07
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-08
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-09
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-10
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-11
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-12
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-13
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-14
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-15
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+(defface org-extra-emphasis-16
+  '((t (:inherit org-extra-emphasis)))
+  "A face for Org Extra Emphasis."
+  :group 'org-extra-emphasis-faces)
+
+;;;;; Useful Org Setting
+
+(setcar (last org-emphasis-regexp-components) 5)
+
+(defcustom org-extra-emphasis-alist
+  '(("!!" org-extra-emphasis-01)
+    ("!@" org-extra-emphasis-02)
+    ("!%" org-extra-emphasis-03)
+    ("!&" org-extra-emphasis-04)
+    ("@!" org-extra-emphasis-05)
+    ("@@" org-extra-emphasis-06)
+    ("@%" org-extra-emphasis-07)
+    ("@&" org-extra-emphasis-08)
+    ("%!" org-extra-emphasis-09)
+    ("%@" org-extra-emphasis-10)
+    ("%%" org-extra-emphasis-11)
+    ("%&" org-extra-emphasis-12)
+    ("&!" org-extra-emphasis-13)
+    ("&@" org-extra-emphasis-14)
+    ("&%" org-extra-emphasis-15)
+    ("&&" org-extra-emphasis-16))
+  "Alist of emphasis marker and its associated face."
+  :group 'org-extra-emphasis
+  :type '(repeat
+	  (list
+	   (string :tag "Emphasis Marker")
+	   (face :tag "Face")))
+  :set (lambda (var val)
+	 (set var val)
+	 (plist-put org-extra-emphasis-info :alist val)
+	 (org-extra-emphasis-update)))
+
+(defcustom org-extra-emphasis t
+  "When non-nil, enable Org Extra Emphasis."
+  :group 'org-extra-emphasis
+  :type '(boolean "Org Extra Emphasis")
+  :set (lambda (var val)
+	 (set var val)
+	 (plist-put org-extra-emphasis-info :enabled val)
+	 (org-extra-emphasis-update)))
+
+(defcustom org-extra-emphasis-filter-functions
+  '(
+    org-export-filter-headline-functions
+    org-export-filter-paragraph-functions
+    org-export-filter-table-cell-functions
+    )
+  "List of places to which `org-extra-emphasis-plain-text-filter'
+and `org-extra-emphasis-strip-zws-maybe' hooks itself.
+
+The places should be one among the values that occur in
+`org-export-filters-alist'.
+
+By default, the list includes
+    - `org-export-filter-headline-functions'
+    - `org-export-filter-paragraph-functions'
+    - `org-export-filter-table-cell-functions',
+
+This means that text with extra emphasis which appears as plain
+text, or within headlines and table cells will be, fontified."
+  :group 'org-extra-emphasis
+  :type `(set
+          ,@(thread-last org-export-filters-alist
+			 (seq-map #'cdr)
+                         (seq-sort #'string<)
+			 (seq-map (lambda (it)
+			            (list 'const it)))))
+  :set (lambda (var value)
+         (set-default var value)
+         (org-extra-emphasis-update-filter-functions value)))
+
+;;;;; `M-x org-extra-emphasis-mode'
+
+(defun org-extra-emphasis-mode (&optional arg)
+  "Enable / Disable Org Extra Emphasis.
+
+If called interactively, toggle Extra Emphasis.
+
+When called non-interactively, enable Extra Emphasis if ARG is
+positive; disable otherwise."
+  (interactive "p")
+  (cond
+   ;; Called interactively; Toggle
+   ((called-interactively-p 'any)
+    (setq org-extra-emphasis (not org-extra-emphasis)))
+   ;; Called programatically; enable if arg >= 1
+   ((and (numberp arg)
+	 (>= arg 1))
+    (setq org-extra-emphasis t))
+   ;; Otherwise, disable
+   (t
+    (setq org-extra-emphasis nil)))
+  (plist-put org-extra-emphasis-info :enabled org-extra-emphasis)
+  (org-extra-emphasis-update))
+
+;;; PART-2: `org-extra-emphasis-intraword-emphasis-mode'
+
+;;;; User options
+
+(defface org-extra-emphasis-zws-face
+  '((t (:inherit org-extra-emphasis :foreground "red")))
+  "Use this face to highlight the ZERO WIDTH SPACE character."
+  :group 'org-extra-emphasis-faces)
+
+(defcustom org-extra-emphasis-zws-display-char ?\N{SPACING UNDERSCORE}
+  "Use the glyph of this character to display ZERO WIDTH SPACE.
+
+Set this to nil, if you want the ZERO WIDTH SPACE to remain
+inconspicuous in the buffer.  Note that even if ZERO WIDTH SPACE
+is inconspicuos in the buffer, the ZERO WIDTH SPACE will be
+stripped from the export output accoding to the value of
+`org-extra-emphasis-intraword-emphasis-mode'."
+  :type '(choice (const :tag "Disabled" nil)
+		 (character :tag "Display ZERO WIDTH SPACE as "))
+  :group 'org-extra-emphasis)
+
+;;;; Internal Variables
+
+(defvar-local org-extra-emphasis-stashed-display-table nil
+  "Stashed value of `buffer-display-table'.
+
+This is the value of `buffer-display-table' before
+`org-extra-emphasis-intraword-emphasis-mode' is turned on in the
+buffer.
+
+Use this value to restore a buffer's `buffer-display-table' when
+`org-extra-emphasis-intraword-emphasis-mode' is turned off in the
+buffer.")
+
+;;;;  `M-x org-extra-emphasis-intraword-emphasis-mode'
+
+;;;###autoload
+(define-minor-mode org-extra-emphasis-intraword-emphasis-mode
+  "Toggle intra word emphasis in `org-mode' export.
+
+When `org-extra-emphasis-intraword-emphasis-mode' is enabled:
+
+- ZERO WIDTH SPACE characters are stripped from export backends.
+- ZERO WIDTH SPACE characters are displayed using
+  `org-extra-emphasis-zws-display-char' and highlighted with
+  `org-extra-emphasis-zws-face' space.
+
+TIPS for the user:
+
+1. You can insert ZERO WIDTH SPACE using
+
+       `M-x insert-char RET ZERO WIDTH SPACE RET'
+
+   One another way is to store that the ZERO WIDTH SPACE in a
+   register, say SPC, and
+
+       (set-register ?\N{SPACE} \"\N{ZERO WIDTH SPACE}\")
+       
+   and use the \\[insert-register] command on that register to insert
+   the ZERO WIDTH SPACE character.
+
+2. You can examine the presence of ZERO WIDTH SPACE character in the
+   export output by turning on the `glyphless-display-mode'."
+  :lighter " ZWS"
+  :init-value nil
+  :global t
+  :group 'org-extra-emphasis
+  (cond
+   ;; Turn ON `org-extra-emphasis-intraword-emphasis-mode'
+   (org-extra-emphasis-intraword-emphasis-mode
+    (when org-extra-emphasis-zws-display-char
+	    ;; Display ZERO WIDTH CHAR in a conspicuous way.
+	    (setq org-extra-emphasis-stashed-display-table (copy-sequence buffer-display-table))
+	    (unless buffer-display-table
+	      (setq buffer-display-table (make-display-table)))
+	    (aset buffer-display-table
+		  ?\N{ZERO WIDTH SPACE}
+		  (vector (make-glyph-code org-extra-emphasis-zws-display-char
+					   'org-extra-emphasis-zws-face)))))
+   (t
+    ;; Turn OFF `org-extra-emphasis-intraword-emphasis-mode'
+    (when org-extra-emphasis-zws-display-char
+      ;; Restore the buffer's original `buffer-display-table'.
+      (setq buffer-display-table org-extra-emphasis-stashed-display-table)))))
+
+;; Adjust `buffer-display-table' so that ZERO WIDTH SPACE characters
+;; are displayed.
+(add-hook 'org-mode-hook 'org-extra-emphasis-intraword-emphasis-mode t)
+
+;;;; Export hook to strip ZERO WIDTH SPACE
+
+(defun org-extra-emphasis-strip-zws-maybe (text _backend _info)
+  "Strip ZERO WIDTH SPACE from TEXT.
+
+If `org-extra-emphasis-intraword-emphasis-mode' is enabled, strip
+ZERO WIDTH SPACE from TEXT.  Otherwise, return TEXT unmodified."
+  (cond
+   ;; `org-extra-emphasis-intraword-emphasis-mode' is ON
+   (org-extra-emphasis-intraword-emphasis-mode
+    ;; Strip ZERO WIDTH SPACE.
+    (replace-regexp-in-string
+     (rx-to-string `(one-or-more ,(char-to-string ?\N{ZERO WIDTH SPACE})))
+     "" text t t))
+   ;; `org-extra-emphasis-intraword-emphasis-mode' is OFF.
+   (t
+    ;; Nothing to do.
+    text)))
+
+;; Configure Org Export Engine to strip ZERO WIDTH SPACE, if needed.
+;; (dolist (it '(org-export-filter-table-cell-functions
+;; 	      org-export-filter-paragraph-functions))
+;;   (add-to-list it 'org-extra-emphasis-strip-zws-maybe it))
+
+(provide 'org-extra-emphasis)
+
+;;; org-extra-emphasis.el ends here
tools/emacs/lisp/org-protocol-capture-html.el
@@ -0,0 +1,280 @@
+;;; org-protocol-capture-html.el --- Capture HTML with org-protocol
+
+;; URL: https://github.com/alphapapa/org-protocol-capture-html
+;; Version: 0.1-pre
+;; Package-Requires: ((emacs "24.4"))
+
+;;; Commentary:
+
+;; This package captures Web pages into Org-mode using Pandoc to
+;; process HTML.  It can also use eww's eww-readable functionality to
+;; get the main content of a page.
+
+;; These are the helper functions that run in Emacs.  To capture pages
+;; into Emacs, you can use either a browser bookmarklet or the
+;; org-protocol-capture-html.sh shell script.  See the README.org file
+;; for instructions.
+
+;;; License:
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Code:
+
+;;;; Require
+
+(require 'org-protocol)
+(require 'cl-lib)
+(require 'subr-x)
+(require 's)
+
+;;;; Vars
+
+(defcustom org-protocol-capture-html-demote-times 1
+  "How many times to demote headings in captured pages.
+You may want to increase this if you use a sub-heading in your capture template."
+  :group 'org-protocol-capture-html :type 'integer)
+
+;;;; Test Pandoc
+
+(defconst org-protocol-capture-html-pandoc-no-wrap-option nil
+  ;; Set this so it won't be unbound
+  "Option to pass to Pandoc to disable wrapping.
+Pandoc >= 1.16 deprecates `--no-wrap' in favor of
+`--wrap=none'.")
+
+(defun org-protocol-capture-html--define-pandoc-wrap-const ()
+  "Set `org-protocol-capture-html-pandoc-no-wrap-option'."
+  (setq org-protocol-capture-html-pandoc-no-wrap-option
+        ;; Pandoc >= 1.16 deprecates the --no-wrap option, replacing it with
+        ;; --wrap=none.  Sending the wrong option causes output to STDERR,
+        ;; which `call-process-region' doesn't like.  So we test Pandoc to see
+        ;; which option to use.
+        (with-temp-buffer
+          (let* ((process (start-process "test-pandoc" (current-buffer) "pandoc" "--dump-args" "--no-wrap"))
+                 (limit 3)
+                 (checked 0))
+            (while (process-live-p process)
+              (if (= checked limit)
+                  (progn
+                    ;; Pandoc didn't exit in time.  Kill it and raise
+                    ;; an error.  This function will return `nil' and
+                    ;; `org-protocol-capture-html-pandoc-no-wrap-option'
+                    ;; will remain `nil', which will cause this
+                    ;; function to run again and set the const when a
+                    ;; capture is run.
+                    (set-process-query-on-exit-flag process nil)
+                    (error "Unable to test Pandoc!  Please report this bug! (include the output of \"pandoc --dump-args --no-wrap\")"))
+                (sleep-for 0.2)
+                (cl-incf checked)))
+            (if (and (zerop (process-exit-status process))
+                     (not (string-match "--no-wrap is deprecated" (buffer-string))))
+                "--no-wrap"
+              "--wrap=none")))))
+
+;;;; Direct-to-Pandoc
+
+(defun org-protocol-capture-html--with-pandoc (data)
+  "Process an org-protocol://capture-html:// URL using DATA.
+
+This function is basically a copy of `org-protocol-do-capture', but
+it passes the captured content (not the URL or title) through
+Pandoc, converting HTML to Org-mode."
+
+  ;; It would be nice to not basically duplicate
+  ;; `org-protocol-do-capture', but passing the data back to that
+  ;; function would require re-encoding the data into a URL string
+  ;; with Emacs after Pandoc converts it.  Since we've already split
+  ;; it up, we might as well go ahead and run the capture directly.
+
+  (unless org-protocol-capture-html-pandoc-no-wrap-option
+    (org-protocol-capture-html--define-pandoc-wrap-const))
+
+  (let* ((template (or (plist-get data :template)
+                       org-protocol-default-template-key))
+         (url (org-protocol-sanitize-uri (plist-get data :url)))
+         (type (if (string-match "^\\([a-z]+\\):" url)
+                   (match-string 1 url)))
+         (title (or (org-protocol-capture-html--nbsp-to-space (string-trim (plist-get data :title))) ""))
+         (content (or (org-protocol-capture-html--nbsp-to-space (string-trim (plist-get data :body))) ""))
+         (orglink (org-make-link-string
+                   url (if (string-match "[^[:space:]]" title) title url)))
+         (org-capture-link-is-already-stored t)) ; avoid call to org-store-link
+
+    (setq org-stored-links
+          (cons (list url title) org-stored-links))
+    (kill-new orglink)
+
+    (with-temp-buffer
+      (insert content)
+      (if (not (zerop (call-process-region
+                       (point-min) (point-max)
+                       "pandoc" t t nil "-f" "html" "-t" "org" org-protocol-capture-html-pandoc-no-wrap-option)))
+          (message "Pandoc failed: %s" (buffer-string))
+        (progn
+          ;; Pandoc succeeded
+          (org-store-link-props :type type
+                                :annotation orglink
+                                :link url
+                                :description title
+                                :orglink orglink
+                                :initial (buffer-string)))))
+    (org-protocol-capture-html--do-capture)
+    nil))
+
+(add-to-list 'org-protocol-protocol-alist
+             '("capture-html"
+               :protocol "capture-html"
+               :function org-protocol-capture-html--with-pandoc
+               :kill-client t))
+
+;;;; eww-readable
+
+(defvar url-http-end-of-headers)
+
+(eval-when-compile
+  ;; eww-readable only works on Emacs >=25.1, but I think it's better
+  ;; to check for the actual symbols.  I think using
+  ;; `eval-when-compile' is the right way to do this, but I'm not
+  ;; sure.
+  (when (and (require 'eww nil t)
+             (require 'dom nil t)
+             (fboundp 'eww-score-readability))
+
+    (defun org-protocol-capture-html--capture-eww-readable (data)
+      "Capture content of URL with eww-readable.."
+
+      (unless org-protocol-capture-html-pandoc-no-wrap-option
+        (org-protocol-capture-html--define-pandoc-wrap-const))
+
+      (let* ((template (or (plist-get data :template)
+                           org-protocol-default-template-key))
+             (url (org-protocol-sanitize-uri (plist-get data :url)))
+             (type (if (string-match "^\\([a-z]+\\):" url)
+                       (match-string 1 url)))
+             (html (org-protocol-capture-html--url-html url))
+             (result (org-protocol-capture-html--eww-readable html))
+             (title (cdr result))
+             (content (with-temp-buffer
+                        (insert (org-protocol-capture-html--nbsp-to-space (car result)))
+                        ;; Convert to Org with Pandoc
+                        (unless (= 0 (call-process-region (point-min) (point-max)
+                                                          "pandoc" t t nil "-f" "html" "-t" "org"
+                                                          org-protocol-capture-html-pandoc-no-wrap-option))
+                          (error "Pandoc failed"))
+                        (save-excursion
+                          ;; Remove DOS CR/LF line endings
+                          (goto-char (point-min))
+                          (while (search-forward (string ?\C-m) nil t)
+                            (replace-match "")))
+                        ;; Demote page headings in capture buffer to below the
+                        ;; top-level Org heading and "Article" 2nd-level heading
+                        (save-excursion
+                          (goto-char (point-min))
+                          (while (re-search-forward (rx bol (1+ "*") (1+ space)) nil t)
+                            (beginning-of-line)
+                            (insert "**")
+                            (end-of-line)))
+                        (buffer-string)))
+             (orglink (org-make-link-string
+                       url (if (s-present? title) title url)))
+             ;; Avoid call to org-store-link
+             (org-capture-link-is-already-stored t))
+
+        (setq org-stored-links
+              (cons (list url title) org-stored-links))
+        (kill-new orglink)
+
+        (org-store-link-props :type type
+                              :annotation orglink
+                              :link url
+                              :description title
+                              :orglink orglink
+                              :initial content)
+        (org-protocol-capture-html--do-capture)
+        nil))
+
+    (add-to-list 'org-protocol-protocol-alist
+                 '("capture-eww-readable"
+                   :protocol "capture-eww-readable"
+                   :function org-protocol-capture-html--capture-eww-readable
+                   :kill-client t))
+
+    (defun org-protocol-capture-html--url-html (url)
+      "Return HTML from URL as string."
+      (let* ((response-buffer (url-retrieve-synchronously url nil t))
+             (encoded-html (with-current-buffer response-buffer
+                             (pop-to-buffer response-buffer)
+                             ;; Skip HTTP headers, using marker provided by url-http
+                             (delete-region (point-min) (1+ url-http-end-of-headers))
+                             (buffer-string))))
+        (kill-buffer response-buffer)     ; Not sure if necessary to avoid leaking buffer
+        (with-temp-buffer
+          ;; For some reason, running `decode-coding-region' in the
+          ;; response buffer has no effect, so we have to do it in a
+          ;; temp buffer.
+          (insert encoded-html)
+          (condition-case nil
+              ;; Fix undecoded text
+              (decode-coding-region (point-min) (point-max) 'utf-8)
+            (coding-system-error nil))
+          (buffer-string))))
+
+    (defun org-protocol-capture-html--eww-readable (html)
+      "Return `eww-readable' part of HTML with title.
+Returns list (HTML . TITLE)."
+      ;; Based on `eww-readable'
+      (let* ((html
+              ;; Convert "&nbsp;" in HTML to plain spaces.
+              ;; `libxml-parse-html-region' turns them into
+              ;; underlines.  The closest I can find to an explanation
+              ;; is at <http://www.perlmonks.org/?node_id=825188>.
+              (org-protocol-capture-html--nbsp-to-space html))
+             (dom (with-temp-buffer
+                    (insert html)
+                    (libxml-parse-html-region (point-min) (point-max))))
+             (title (cl-caddr (car (dom-by-tag dom 'title)))))
+        (eww-score-readability dom)
+        (cons (with-temp-buffer
+                (shr-dom-print (eww-highest-readability dom))
+                (buffer-string))
+              title)))))
+
+;;;; Helper functions
+
+(defun org-protocol-capture-html--nbsp-to-space (s)
+  "Convert HTML non-breaking spaces to plain spaces in S."
+  ;; Not sure why sometimes these are in the HTML and Pandoc converts
+  ;; them to underlines instead of spaces, but this fixes it.
+  (replace-regexp-in-string (rx "&nbsp;") " " s t t))
+
+(with-no-warnings
+  ;; Ignore warning about the dynamically scoped `template' variable.
+  (defun org-protocol-capture-html--do-capture ()
+    "Call `org-capture' and demote page headings in capture buffer."
+    (raise-frame)
+    (funcall 'org-capture nil template)
+
+    ;; Demote page headings in capture buffer to below the
+    ;; top-level Org heading
+    (save-excursion
+      (goto-char (point-min))
+      (re-search-forward (rx bol "*" (1+ space)) nil t) ; Skip 1st heading
+      (while (re-search-forward (rx bol "*" (1+ space)) nil t)
+        (dotimes (n org-protocol-capture-html-demote-times)
+          (org-demote-subtree))))))
+
+(provide 'org-protocol-capture-html)
+
+;;; org-protocol-capture-html.el ends here