Editor Comments

⭐ The code lives at github.com/alhassy/org-special-block-extras —star it, or discuss it there!
“Editor Comments” are intended to be top-level first-class comments in an article that are inline with the surrounding text and are delimited in such a way that they are visible but drawing attention. I first learned about this idea from Wolfram Kahl —who introduced me to Emacs many years ago. We
Example
This
In LaTeX, a =remark= appears inline with the text surrounding it.
#+begin_remark Bobert
org-mode is dope, yo!
#+replacewith:
Org-mode is essentially a path toward enlightenment.
#+end_remark
Unfortunately, in the HTML rendition, the =remark= is its own paragraph and thus
separated by new lines from its surrounding text.
Yields
In LaTeX, an remark appears inline with the text surrounding it.
[Bobert: Replace:
org-mode is dope, yo!
With:Org-mode is essentially a path toward enlightenment.
]
Unfortunately, in the HTML rendition, the remark is its own paragraph and thus
separated by new lines from its surrounding text.
Implementing ‘remark’, from §ection 1, with more bells and whistles
(defvar org-hide-editor-comments nil
"Should editor comments be shown in the output or not.")
(org-defblock remark
(&optional (editor "Editor Remark") &key (color "black") (signoff "") strong &context backend contents)
; :inline-please__see_margin_block_for_a_similar_incantation ; ⇒ crashes!
[:face '(:foreground "red" :weight bold)]
"Format CONTENTS as an first-class editor comment according to BACKEND.
The CONTENTS string has an optional switch: If it contains a line
with having only ‘#+replacewith:’, then the text preceding this
clause should be replaced by the text after it; i.e., this is
what the EDITOR (the person editing) intends and so we fromat the
replacement instruction (to the authour) as such.
In Emacs, as links, editor remarks are shown with a bold red; but
the exported COLOR of a remark is black by default and it is not
STRONG ---i.e., bold---. There is an optional SIGNOFF message
that is appended to the remark.
"
(-let* (;; Are we in the html backend?
(tex? (equal backend 'latex))
;; fancy display style
(boxed (lambda (x)
(if tex?
(concat "\\fbox{\\bf " x "}")
(concat "<span style=\"border-width:1px"
";border-style:solid;padding:5px\">"
"<strong>" x "</strong></span>"))))
;; Is this a replacement clause?
((this that) (s-split "\\#\\+replacewith:" contents))
(replacement-clause? that) ;; There is a ‘that’
(replace-keyword (if tex?
"\\underline{Replace:}" " <u>Replace:</u>"))
(with-keyword (if tex? "\\underline{With:}" "<u>With:</u>"
))
(editor (format "[%s:%s" editor
(if replacement-clause?
replace-keyword
"")))
(contents′ (if replacement-clause?
(format "%s %s %s" this
(org-export (funcall boxed with-keyword))
that)
contents))
;; “[Editor Comment:”
(edcomm-begin (funcall boxed editor))
;; “]”
(edcomm-end (funcall boxed "]")))
(setq org-export-allow-bind-keywords t) ;; So users can use “#+bind” immediately
(if org-hide-editor-comments
""
(format (pcase backend
('latex (format "{\\color{%%s}%s %%s %%s %%s %%s}" (if strong "\\bfseries" "")))
(_ (format "<%s style=\"color: %%s;\">%%s %%s %%s %%s</%s>" (if strong "strong" "p") (if strong "strong" "p"))))
color edcomm-begin contents′ signoff edcomm-end))))
In the HTML export, the edcomm special block is not in-line with the text
surrounding it —ideally, it would be inline so that existing paragraphs are
not split into multiple paragraphs but instead have an editor's comment
indicating suggested alterations.
Let's have some sanity tests…
(deftest "The user's remark is enclosed in the default delimiters"
[remark]
(⇝ (⟰ "#+begin_remark
Here is some meta-commentary...
#+end_remark")
(* anything) "[Editor Remark:"
(* anything) "Here is some meta-commentary"
(* anything) "]"))
;; The other features of remark blocks should be tested;
;; but this is not a pressing, nor interesting, concern.
Example: No optional arguments
[Editor Remark:
Please change this section to be more, ya know, professional.
]Source:
#+begin_remark
/Please/ *change* _this_ section to be more, ya know, professional.
#+end_remark
Example: Only providing a main argument ---i.e., the remark author, the editor
[Bobert:
Please change this section to be more, ya know, professional.
]Source:
#+begin_remark Bobert
/Please/ *change* _this_ section to be more, ya know, professional.
#+end_remark
Example: Possibly with no contents:
[Bobert: ]
Source:
#+begin_remark Bobert
#+end_remark
Example: Empty contents, no authour, nothing
[Editor Remark: ]
Source:
#+begin_remark
#+end_remark
Example: Possibly with an empty new line
[Editor Remark: ]
Source:
#+begin_remark
#+end_remark
Example: With a “#+replacewith:” clause
[Editor Remark: Replace:
The two-dimensional notation; e.g., \(\sum_{i = 0}^n i^2\)
With:A linear one-dimensional notation; e.g., \((\Sigma i : 0..n \;\bullet\; i^2)\)
]Source:
#+begin_remark
The two-dimensional notation; e.g., $\sum_{i = 0}^n i^2$
#+replacewith:
A linear one-dimensional notation; e.g.,
$(\Sigma i : 0..n \;\bullet\; i^2)$
#+end_remark
Example: Possibly “malformed” replacement clauses
Forgot the thing to be replaced…
[Editor Remark: Replace: With:
A linear one-dimensional notation; e.g., \((\Sigma i : 0..n \;\bullet\; i^2)\)
]Forgot the new replacement thing…
[Editor Remark: Replace:
The two-dimensional notation; e.g., \(\sum_{i = 0}^n i^2\)
With: ]Completely lost one's train of thought…
[Editor Remark: Replace: With: ]
Source:
#+begin_remark
#+replacewith:
#+end_remark
A block to make an editorial comment could be overkill in some cases; luckily
defblockDeclare a new special block, and link, in the style of ‘defun’.
To remove support for a block NAME, simply use ‘org-undefblock’.
A full featured example is at the end of this documentation string.
This is an anaphoric macro that provides export support for
special blocks *and* links named NAME. Just as an Org-mode
src-block consumes as main argument the language for the src
block, our special blocks too consume a MAIN-ARG; it may be a
symbol or a cons-list consisting of a symbolic name (with which
to refer to the main argument in the definition of the block)
followed by a default value, then, optionally, any information
for a one-time setup of the associated link type.
The main arg may be a sequence of symbols separated by spaces,
and a few punctuation with the exception of comma ‘,’ since it is
a special Lisp operator. In doubt, enclose the main arg in
quotes.
Then, just as Org-mode src blocks consume key-value pairs, our
special blocks consume a number of keyword arguments.
Together, the main and keyword arguments form KWDS, written as a
‘cl-defun’ arglist; by way of example,
(&optional (title "Details") &key (color "green") style)
declares main argument TITLE, defaulting to "Details", and
keyword arguments COLOR and STYLE. As with ‘cl-defun’, a header
line’s unknown keywords are an export-time error unless KWDS writes
‘&allow-other-keys’; so, () takes no arguments at all.
KWDS may also carry options, which are not arguments:
+ ‘:block nil’ declines block support: #+begin_NAME is left to Org, yet the
link and tag still work, through ‘org-block/NAME’.
+ ‘:link nil’ declines the link type NAME:; ‘:link t’ makes it; and ‘:link
[:face … :display …]’ makes it with that ‘org-deflink’ display, which sees
the link’s arglist (label &optional description backend); e.g.,
[:face =’(:foreground "red")] shows such links in red.
+ ‘:tag nil’ declines the tag: otherwise a section tagged :NAME: exports as a
NAME block around its text; the tag’s underscore-separated items form the
main argument, and the section’s properties supply keyword arguments by
name ---every property bar Org’s own, when the block allows other keys.
E.g., "* Stuck? :details_Hint:" folds its text away under the
title Hint. See ‘org--defblock-section-to-block’.
A block cannot hold a heading, so ‘:tag [:subsections MODE]’ says what becomes
of a tagged section’s subsections: ‘ignore’ leaves them outside the block, as
‘:tag t’ does; ‘error’ refuses a section that has any; and ‘recurse’ wraps
each subsection’s text in a block of its own.
The link and tag default to ‘org-defblock-links-and-tags’; for just one of
them, see ‘org-deflink’ and ‘org-deftag’.
+ ‘:edit-indirect-mode MODE’ makes C-c =’ edit the block in MODE.
+ ‘:latex-head (ITEM …)’ is what a LaTeX document using the block, or its link, needs in its preamble, added once:
a package name, or any preamble line; e.g., unquoted,
:latex-head ("tcolorbox" "\\newtcolorbox{aside}{colback=gray!5}")
Name a package, "tcolorbox", rather than write "\\usepackage{tcolorbox}": a name is left be when the document
loads it already, options and all; a line, only when the document has that very line. See ‘org--load-latex-head’.
+ ‘:html-head (ITEM …)’ is what an HTML page using the block needs in its <head>, added once: a string of HTML, a
symbol whose value is one, or ‘mathjax’ for Org’s own MathJax setup; e.g., unquoted,
:html-head (mathjax my-html-head "<style>.aside {color: gray}</style>")
A symbol shares one head among many blocks, as ‘org--tooltip-html-head’ serves every tooltip. See
‘org--load-html-head’.
+ ‘:fontify [:begin STRING :end STRING :face FACE]’ says how the block looks in Org buffers;
e.g., [:begin "Proof." :end "⏹" :face =’italic] shows “#+begin_proof” as “Proof.”,
“#+end_proof” as “⏹”, and the contents in italics too. Moreover, :keywords =’((REGEXP GROUP FACE) …) shows
each REGEXP match in the contents in FACE; e.g., the ‘math’ block’s “blue:<<Fold>>” shows as a tag, in blue.
And :language "latex" shows the contents as a #+begin_src latex block’s. Each part is optional; see
‘org--fontify-blocks’.
+ ‘:hover STRING’ is prose that eldoc shows, on hovering the block’s #+begin_ line, beneath its docstring’s
first line; e.g., a tip too long for that line. See ‘org--special-block-eldoc’.
+ ‘:manual URL’ is where the block is documented, which eldoc offers as “See the manual”; a URL of #ANCHOR is
that section of ‘org-special-block-extras-manual’; e.g., :manual "#math".
After that is an optional DOCSTRING, a familar feature of DEFUN.
The docstring is displayed as part of the tooltip for the
produced link type.
Finally, the BODY is a (sequence of) Lisp forms ---no progn
needed--- that sees KWDS’ arguments, and whatever else it asks for
after &context in KWDS:
(org-defblock shout (&optional (title "Hey") &context backend contents)
(format (if (eq backend =’html) "<b>%s</b>: %s" "%s: %s") title (upcase contents)))
On offer, as ‘org--block-context-keys’ lists them:
+ backend ⇒ the current export backend; e.g., html, or latex.
+ contents ⇒ the block’s contents, Org-parsed; i.e., Org-markup is
acknowledged ---or the description clause of a link.
+ raw-contents ⇒ the contents unparsed, for the rare body that wants them.
+ args ⇒ the plist of every keyword argument the block was given.
+ link? ⇒ whether we are rendering a link’s description, not a block.
+ anchor ⇒ the block’s #+name:, else nil.
This is dependency injection, by keyword: every block is offered the
whole record, and takes only what it names. Hence BODY sees no name
it did not ask for, so nothing of ours captures a variable of its own;
e.g., a main argument named ‘contents’ is just that. An item we offer
in due time is invisible to the blocks that do not ask for it; and an
item not on offer, such as a misspelt contnets, is an error at once.
An item may be bound under another name, ((:ITEM VAR)), as when KWDS
already uses ITEM’s name; an item named both ways is an error:
(org-defblock quip (&optional contents &context ((:contents text)))
(format "%s, said of %s" contents text)) ;; #+begin_quip Ha! ⇒ “Ha!, said of …”
See “Keyword Parameters” in Practical Common Lisp, a gentle tour of
such renaming: https://gigamonkeys.com/book/functions.html
Finally, this macro exposes two functions:
+ ORG-EXPORT: Wrap the argument in an export block for the current backend.
+ ORG-PARSE: This should ONLY be called within an ORG-EXPORT call,
to escape text to Org, and out of the export block.
⇄ We use “@@html:⋯:@@” when altering CONTENTS, but otherwise use raw HTML *around* CONTENTS.
⇄ For example: (format "<div>%s</div>" (s-replace "#+columnbreak:" "@@html:<hr>@@" contents))
----------------------------------------------------------------------
The relationship between links and special blocks:
[ [type:label][description]]
≈
#+begin_type label
description
#+end_type
----------------------------------------------------------------------
Example declaration, with all possible features shown:
;; We can use variable values when defining new blocks
(setq angry-red ’(:foreground "red" :weight bold))
(org-defblock remark
(&optional (editor "Editor Remark") &key (color "red") (signoff "")
:link [:face angry-red] &context backend contents)
"Top level (HTML & LaTeX) editorial remarks; in Emacs they’re angry red."
(format (if (equal backend ’html)
"<strong style=\"color: %s;\">⟦%s: %s%s⟧</strong>"
"{\color{%s}\bfseries %s: %s%s}")
color editor contents signoff))
;; Personalised defaults, without changing the definition, as Babel’s for a src block of language remark;
;; or, per section, :header-args:remark: :editor "Jasim Jameson" in a heading’s drawer.
(setq org-babel-default-header-args:remark =’((:editor . "Jasim Jameson") (:signoff . "( Aim for success! )")))
Three example uses:
;; ⟨0⟩ As a special blocks with arguments given.
#+begin_remark Bobbert Barakallah :signoff "Thank-you for pointing this out!" :color green
I was trying to explain that ${large (n × (n + 1) over 2}$ is always an integer.
#+end_remark
;; ⟨1⟩ As a terse link, using default values for the args.
;; Notice that Org-mode formatting is recoqgnised even in links.
[ [remark:Jasim Jameson][Why are you taking about "$\mathsf{even}$" here?]]
;; ⟨2⟩ So terse that no editor name is provided.
[ [remark:][Please improve your transition sentences.]]
;; ⟨★⟩ Unlike 0, examples 1 and 2 will have the default SIGNOFF
;; catenated as well as the default red color.
(fn NAME KWDS &optional DOCSTRING &rest BODY) automatically provides an associated link type for the declared
special blocks.
- Syntax:
[[remark:person_name][editorial remark]]. - This link type exports the same as the
remarkblock type; however, in Emacs it is shown with an ‘angry’ —bold— red face.
Example: Terse remarks via links
[[edcomm:Jasim][Hello, where are you?]]
[Jasim: Hello, where are you? ]
The #+replacewith: switch —and usual Org markup— also works with these
links:
[[remark:Qasim][/‘j’/ #+replacewith: /‘q’/]]
[Qasim: Replace: ‘j’ With: ‘q’ ]
All editor comments, remarks, are disabled by declaring, in your Org file:
#+bind: org-hide-editor-comments t
The #+bind: keyword makes Emacs variables buffer-local during export
—it is evaluated after any src blocks. To use it, one must declare in
their Emacs init file the following line, which our mode
ensures is true.
(setq org-export-allow-bind-keywords t)
( Remember to C-c C-c the #+bind to activate it, the first time it is written. ) |
Life & Computing Science by Musa Al-hassy is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License