51 KiB
- one.el
- Install one.el
- Getting started
- How does one.el work?
- one-default render function
- Miscellaneous
- one-ox
- one-ox | headline
- one-ox | src-block
- one-ox | quote-block
- one-ox | fixed-width and example-block
- one-ox | links
- one-ox | plain-list and item
- one-ox | table
one.el
Static Site Generator for Emacs Lisp programmers
Have you ever wanted to write a blog:
- contained in a unique org file,
- rendered with only one Emacs command,
- that can be modified by writing Emacs Lisp code (and CSS too),
- with "html templates" that are plain Emacs Lisp data,
- with no config file,
- and no dependencies on external static site generators?
If so, you might be interested in one.el a simple Static Site
Generator for Emacs Lisp programmers and org-mode users.
To get started right away check Install one.el and Getting started pages.
You can find the code here: https://github.com/tonyaldon/one.el.
Athough one.el uses org-mode not all the org elements are useful to
build technical blog sites (see Why one.el?). So only a few org
elements have an transcoder function implemented in one-ox, the org
backend used by one.el to build the default website (see one-default
render function). Please check Org elements not supported before
relying on one.el.
In one.el, the following org document defines a website with 3 pages
that we build by calling one-build command while we are visiting it:
* My website
:PROPERTIES:
:ONE: one-default-home
:CUSTOM_ID: /
:END:
Welcome to my website!
* Blog post 1
:PROPERTIES:
:ONE: one-default
:CUSTOM_ID: /blog/page-1/
:END:
My first blog post!
* Blog post 2
:PROPERTIES:
:ONE: one-default
:CUSTOM_ID: /blog/page-2/
:END:
My second blog post!
Note that if we want to use the default css style sheet we can add it
by calling one-default-add-css-file before building the website.
The path / in the first CUSTOM_ID org property tells one.el that the
page "My website" is the home page. That page is rendered using
one-default-home render function, value of ONE org property of the
same headline.
The path /blog/page-1/ in the second CUSTOM_ID org property tells
one.el that we want to render "Blog post 1" page in such a way
that when we serve our website locally at http://localhost:3000 for
instance, that page is served at http://localhost:3000/blog/page-1/.
How that page is rendered is determined by the value of ONE org
property of the same headline which is one-default, a render
function.
The same goes for the last page "Blog post 2".
As you might have noticed, a one.el website is an org file where the
pages are the headlines of level 1 with the org properties ONE and
CUSTOM_ID set. Nothing more!
ONE is the only org property added by one.el. Its value, an Emacs Lisp
function which returns an HTML string, for a given page determines how
one.el renders that page.
Paths of pages are set using CUSTOM_ID org property.
With that said, if you want to try it you can check Install one.el and Getting started pages.
Why one.el?
I wrote one.el because I didn't find an existing static site generator
with the following requirements:
- I'm not looking for a solution for every type of websites, only for technical blog sites which are basically chunks of code surrounded by text,
- I want something simple that I understand and that I can modify only by writting some Emacs Lisp,
- I want websites to be written to a single org file,
- I want something with no dependencies other than emacs packages that are not bridges to feed other static site frameworks,
- I want something with no configuration options, if you want to modify something you write Emacs Lisp code and
- Finally, I want an Emacs solution for an Emacs user.
Following those requirements led me to one.el, an opiniated static
site generator for Emacs Lisp programmers and Org mode users that
works well if you want to build websites like
- minibuffer (source): learn Emacs Lisp one sexp at a time,
- Elisp posts: some articles about Emacs Lisp,
- jack: HTML generator library for Emacs Lisp,
- one.el: documentation of one.el package,
- LNROOM: learn how to hack on Core Lightning and
- https://tonyaldon.com.
all built with one.el.
Install one.el
Manually
With package-install
one.el is also available on Melpa so you can install it like this:
M-x package-install <RET> one <RET>
With straight.el
If you're using straight.el, to install one.el you just have to add
this sexp to your init file:
(straight-use-package
'(one :type git :host github :repo "tonyaldon/one.el"
:build (:not compile)))
Note that :build (:not compile) is important. It tells straight.el
not to byte compile one.el. Something happened in straight.el
between commits 3eca39d and b3760f5 which broke byte compilation of
one.el if done by straight.el.
Let's go
Now you can create a new website by calling one-default-new-project
(preferably in an empty directory) and you can build it by calling
one-build command.
If this is the first time you try one.el reading Getting started
page might be helpful.
Getting started
Start a new project
By calling one-default-new-project command (preferably in an empty
directory) we produce a new one.el project with the following
structure:
.
├── assets
│ └── one.css
└── one.org
Once done we can build the website under the directory ./public/
by calling one-build command while we are in the file one.org. Our
project's structure is now:
.
├── assets
│ └── one.css
├── one.org
└── public
├── blog
│ ├── default
│ │ └── index.html
│ ├── default-home-list-pages
│ │ └── index.html
│ ├── one-default-doc
│ │ └── index.html
│ ├── one-default-with-sidebar
│ │ └── index.html
│ └── one-default-with-toc
│ └── index.html
├── index.html
└── one.css
Modify the content with live reloading
To get our website up and running, we serve the files in ./public/
subdirectory using brower-sync (any webserver serving files is OK).
Once we have it installed, to start a webserver with live reloading,
we run the following commands (in a terminal):
$ cd public
$ browser-sync start -s -w --files "*"
Assuming the port 3000 isn't used we have our website served at
http://localhost:3000.
Now we can modify the content of one.org file and see the changes
reflected in the browser after we rebuild/re-render the whole website
or part of it using the following commands one-build, one-render-pages
and one-render-page-at-point or the asynchronous version of those
commands one-build-async, one-render-pages-async and
one-render-page-at-point-async.
CSS style sheet
When we call one-build (or one-build-async) command the pages of the
website are rendered in the directory ./public/ and the files in
./assets/ directory are copied into ./public/ subdirectory.
When we build a one.el website with the default render functions and
the default CSS style sheet (this is the case if we used
one-default-new-project as we did above) the style sheet that applies
is ./public/one.css file which is a copy of ./assets/one.css file.
So in that case, to modify the website's layout we just have to modify
the file ./assets/one.css and copy it in ./public/ directory either
with one-build, one-build-async or one-copy-assets-to-public.
Modify the CSS style sheet with live reloading
To get the file ./assets/one.css copied into ./public/ directory each
time we modify it we can use entr utility like this (being at the root
of our project):
$ ls assets/one.css | entr -s 'cp assets/one.css public/'
Combined with browser-sync live reloading I think we get a decent
programmer experience.
Source blocks
When we use the default render functions and the default CSS style
sheet, the org content is exported into HTML strings using one-ox org
export backend. Consequently, src-block elements are highlighted
using htmlize.
See one-ox | src-block for more information.
How does one.el work?
In an org file containing all the pages of our website we can build
the website under ./public/ subdirectory by calling either one-build
or one-render-pages commands.
The only difference between those two commands is that before
producing the HTML pages calling one-render-pages, one-build command
cleans the subdirectory ./public/ and copies the content of ./assets/
subdirectory into ./public/ subdirectory.
So let's focus on one-render-pages command.
For each page of our website, the function one-render-pages uses
the render function set in ONE org property of the page to produce the
HTML string representing the page and stores it in an index.html file
whom path is determined by CUSTOM_ID org property of the page.
Render functions are at the heart of one.el mechanism. They
determined how pages are rendered. Specifically, render functions are
regular Elisp functions that takes 3 arguments
page-tree: corresponding to the parsed tree of the org entry defining the page,pages: the list of pages,global: a plist of global informations that are computed once inone-render-pages(seeone-add-to-global) before rendering the pages
and return HTML strings.
For instance, the following hello-world function
(defun hello-world (page-tree pages global)
"<h1>Hello world!</h1>")
defines a valid render function. We can use it to build a website
like this. In an empty directory, we create a file named one.org with
the following content:
* The home page
:PROPERTIES:
:ONE: hello-world
:CUSTOM_ID: /
:END:
* Blog post 1
:PROPERTIES:
:ONE: hello-world
:CUSTOM_ID: /blog/page-1/
:END:
We visit that file and call one-build command. It produces the
following files
.
├── one.org (already there)
└── public
├── blog
│ └── page-1
│ └── index.html
└── index.html
and both files ./public/blog/page-1/index.html and
./public/index.html have the same content:
<h1>Hello world!</h1>
Therefore if we serve the website in ./public/ directory at
http://localhost:3000 we can access the two "Hello world!" pages
at http://localhost:3000/blog/page-1/ and http://localhost:3000.
That's it! This is how one.el works under the hood.
one.el comes with predefined render functions, a custom CSS style
sheet and a custom org export backend which are used all together to
build that documentation for instance.
See Getting started to start a new project with those defaults.
See one-default render function to take inspiration and write your own render functions.
one-default render function
In How does one.el work? page we saw that render functions are at
the heart of one.el mechanism. They determine how pages are
rendered.
We saw that
(defun hello-world (page-tree pages global)
"<h1>Hello world!</h1>")
defines a valid render function that can be used to render pages of a
one.el website by setting ONE org property to hello-world like this
for instance:
* The home page
:PROPERTIES:
:ONE: hello-world
:CUSTOM_ID: /
:END:
* Blog post 1
:PROPERTIES:
:ONE: hello-world
:CUSTOM_ID: /blog/page-1/
:END:
one.el comes with several default render functions that can be used
instead of the dummy hello-world function:
one-default-home: org content,one-default-home-list-pages: org content followed by the list in reverse order of the pages of the website,one-default: org content with navigation buttons at the bottom to go to the previous page, the next page or a random one,one-default-with-toc: same asone-defaultbut with a table of content at the top of the page andone-default-with-sidebar: same asone-defaultbut with a sidebar listing all the pages in the website,one-default-doc: same asone-default-with-sidebarbut with a table of content at the top of the page.
Those default render functions use one-ox custom org export backend and
one-default-css custom CSS style sheet.
If we want to start a new project using these defaults, we can use
one-default-new-project command (see Getting started).
If you plan to write your own render functions you may find the following sections interesting.
The org document
Let's consider the following org document in a file named one.org for
instance:
* Home
:PROPERTIES:
:ONE: one-default-home
:CUSTOM_ID: /
:END:
* Page 1
:PROPERTIES:
:ONE: one-default
:CUSTOM_ID: /blog/page-1/
:END:
** Headline foo 1
[[#/blog/page-2/][Link to Page 2]]
** Headline foo 2
*** Headline bar
Some content.
*** Headline baz
:PROPERTIES:
:CUSTOM_ID: /blog/page-1/#baz
:END:
#+BEGIN_SRC emacs-lisp
(message "foo bar baz")
#+END_SRC
* Page 2
:PROPERTIES:
:ONE: one-default
:CUSTOM_ID: /blog/page-2/
:END:
[[#/blog/page-1/#baz][Link to Headline baz in Page 1]]
Let's generate the file ./assets/one.css that contains the content of
one-default-css string by calling one-default-add-css-file command.
Our project structure is now:
.
├── assets
│ └── one.css
└── one.org
Build the website
Now, while vising the file one.org we call one-build which builds
"Home", "Page 1" and "Page 2" pages under the directory ./public/ such
that our project tree is now:
.
├── assets
│ └── one.css
├── one.org
└── public
├── blog
│ ├── page-1
│ │ └── index.html
│ └── page-2
│ └── index.html
├── index.html
└── one.css
Home
The page "Home" has been generated:
- in the file
./public/index.htmlrespecting the path information/inCUSTOM_IDorg property and - its HTML content has been created using
one-default-homerender function specified inONEorg property.
./public/index.html (pretty printed for the demonstration):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" type="text/css" href="/one.css" />
<title>Home</title>
</head>
<body>
<div class="header">Home</div>
<div class="content">
<div id="home"><div></div></div>
</div>
</body>
</html>
Page 1
The page "Page 1" has been generated:
- in the file
./public/blog/page-1/index.htmlrespecting the path information/blog/page-1/inCUSTOM_IDorg property and - its HTML content has been created using
one-defaultrender function specified inONEorg property.
./public/blog/page-1/index.html (pretty printed for the demonstration):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" type="text/css" href="/one.css" />
<title>Page 1</title>
</head>
<body>
<div class="header"><a href="/">Home</a></div>
<div class="content">
<div class="title">
<div class="title"><h1>Page 1</h1></div>
</div>
<div></div>
<div>
<h2 id="one-df8f0f16cc">Headline foo 1</h2>
<div>
<p><a href="/blog/page-2/">Link to Page 2</a></p>
</div>
</div>
<div>
<h2 id="one-9c2f3b8536">Headline foo 2</h2>
<div>
<h3 id="one-fe469dd578">Headline bar</h3>
<div><p>Some content.</p></div>
</div>
<div>
<h3 id="baz">Headline baz</h3>
<div>
<pre><code class="one-hl one-hl-block">(message <span class="one-hl-string">"foo bar baz"</span>)</code></pre>
</div>
</div>
</div>
<div class="nav">
<a href="/">PREV</a><a href="/">RANDOM</a
><a href="/blog/page-2/">NEXT</a>
</div>
</div>
</body>
</html>
Page 2
The page "Page 2" has been generated:
- in the file
./public/blog/page-2/index.htmlrespecting the path information/blog/page-2/inCUSTOM_IDorg property and - its HTML content has been created using
one-defaultrender function specified inONEorg property.
./public/blog/page-2/index.html (pretty printed for the demonstration):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" type="text/css" href="/one.css" />
<title>Page 2</title>
</head>
<body>
<div class="header"><a href="/">Home</a></div>
<div class="content">
<div class="title">
<div class="title"><h1>Page 2</h1></div>
</div>
<div>
<p><a href="/blog/page-1/#baz">Link to Headline baz in Page 1</a></p>
</div>
<div class="nav">
<a href="/blog/page-1/">PREV</a><a href="/">RANDOM</a>
</div>
</div>
</body>
</html>
How was "Page 1" built?
When we called one-build in one.org buffer, the whole buffer was
parsed with the function one-parse-buffer and a list of pages was
built from that parsed tree and looked like this:
((:one-title "Home"
:one-path "/"
:one-render-page-function one-default-home
:one-page-tree (headline (:raw-value "Home" ...) ...))
(:one-title "Page 1"
:one-path "/blog/page-1/"
:one-render-page-function one-default
:one-page-tree (headline (:raw-value "Page 1" ...) ...))
(:one-title "Page 2"
:one-path "/blog/page-2/"
:one-render-page-function one-default
:one-page-tree (headline (:raw-value "Page 2" ...) ...)))
Let's call pages that list of pages.
Then for each page in pages the function one-render-page was called
with page, pages and global (see one-add-to-global variable) as
arguments.
Finally, in one-render-page the function one-default or
one-default-home was called with the arguments page-tree, pages and
global to create the HTML content of each page whom path under the
directory ./public/ was determined by the value of :one-path property
in page and page-tree was the value of :one-page-tree property in
page.
Focusing on "Page 1", the function one-default was called with the
arguments page-tree, page and global with page-tree being the
following parsed tree of the headline defining "Page 1":
(headline
(:raw-value "Page 1"
:CUSTOM_ID "/blog/page-1/"
:ONE "one-default"
:parent (org-data ...)
:one-internal-id "one-9c81c230b6"
...)
(section (...) (property-drawer ...))
(headline
(:raw-value "Headline foo 1"
:one-internal-id "one-4df8d962d9"
...)
(section (...) (paragraph ...)))
(headline
(:raw-value "Headline foo 2"
:one-internal-id "one-9d89da8271"
...)
(headline
(:raw-value "Headline bar"
:one-internal-id "one-95fa001487"
...)
(section
(...)
(paragraph (...) #("Some content. " 0 14 (:parent #4)))))
(headline
(:raw-value "Headline baz"
:CUSTOM_ID "/blog/page-1/#baz"
:one-internal-id "baz"
...)
(section
(...)
(property-drawer ...)
(src-block
(:language "emacs-lisp"
:value "(message \"foo bar baz\")"
...))))))
In one-default the org content of "Page 1" was exported into a HTML
string using org-export-data-with-backend and one-ox custom org export
backend. Then this HTML string was used in a data structure
representing the HTML page. Finally, jack-html (see jack) transformed
that data structure into a HTML string which was written on the file
./public/blog/page-1/index.html:
(defun one-default (page-tree pages _global)
"Default render function.
See `one-is-page', `one-render-pages' and `one-default-css'."
(let* ((title (org-element-property :raw-value page-tree))
(path (org-element-property :CUSTOM_ID page-tree))
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
(website-name (one-default-website-name pages))
(nav (one-default-nav path pages)))
(jack-html
"<!DOCTYPE html>"
`(:html
(:head
(:meta (@ :name "viewport" :content "width=device-width,initial-scale=1"))
(:link (@ :rel "stylesheet" :type "text/css" :href "/one.css"))
(:title ,title))
(:body
(:div.header (:a (@ :href "/") ,website-name))
(:div.content
(:div.title
,(if (not (string= path "/"))
`(:div.title (:h1 ,title))
'(:div.title-empty)))
,content
,nav))))))
Miscellaneous
Page at point
If we need to render only the page at point, meaning the headline of
level 1 with ONE and CUSTOM_ID org properties set, we can use the
commands one-render-page-at-point and one-render-page-at-point-async.
onerc.el file
We can use an Emacs Lisp file called onerc.el to customize our
website. It must be in the same directory of the org file containing
the content of our website.
This file is loaded first in one-render-pages before rendering the
webpages.
This is a good place to set one-add-to-global and one-hook variables
or to define our own render functions.
one-add-to-global
Render functions takes 3 arguments:
page-tree: the parsed tree of the page being rendered,pages: the list of pages,global: a plist of global informations that are computed once inone-render-pagesbefore rendering the pages usingone-add-to-globalvariable.
That means that if a render function needs extra informations, we can
use one-add-to-global variable to pass those informations to the
render function.
Specifically, elements in one-add-to-global list are plist with the
following properties:
:one-global-property: a keyword that is used as proprety in theglobalargument passed to the render functions,:one-global-function: a function that takes two argumentspages(list of pages, seeone-list-pages) andtree(seeone-parse-buffer). That function is called once inone-render-pagesand its result is used as the value of the property:one-global-propertyin theglobalargument passed to the render functions.
For instance, if one-add-to-global is set to
((:one-global-property :one-tree
:one-global-function (lambda (pages tree) tree)))
then global local variable will be set to
((:one-tree tree))
where tree is the value returned by one-parse-buffer function.
one-hook
Each function in one-hook is called once in one-render-pages.
Those functions take three arguments:
pages: list of pages (seeone-list-pages),tree: seeone-parse-buffer,global: see one-add-to-global.
As those functions take global argument they are called after
that argument has been let binded using one-add-to-global.
feed.xml example
This hook is used to build feed.xml file of minibuffer.tonyaldon.com
website. You can check onerc.el file of
tonyaldon/minibuffer.tonyaldon.com repository to see how it is done.
robot.txt and sitemap.txt
If we want to add a sitemap.txt file to our website we can do so using
one-hook.
robot.txt
First we need to indicate in a robots.txt where our sitemap.txt is
located.
Assuming our website is https://example.com and our sitemap.txt file
is at the root of it, we can add the following robots.txt file in the
assets directory (./assets/robots.txt):
User-Agent: *
Allow: /
Sitemap: https://domain.com/sitemap.txt
sitemap.txt
Now in onerc.el file:
- we set our domain with protocol in the variable
domain, - then we define
make-sitemapfunction which will create the filesitemap.txtin thepublicdirectory (./public/sitemap.txt) each time be build our website, - Finally, to tell
one.elto actually createsitemap.txtfile usingmake-sitemapfunction each time be build our website, we add it toone-hook:
(defvar domain "https://example.com"
"Domain with protocol to be used to produce sitemap file.
See `make-sitemap'.")
(defun make-sitemap (pages tree global)
"Produce file ./public/sitemap.txt
Global variable `domain' is used as domain with protocol.
This function is meant to be added to `one-hook'."
(with-temp-file "./public/sitemap.txt"
(insert
(mapconcat 'identity
(mapcar
(lambda (page)
(let* ((path (plist-get page :one-path))
(link (concat domain path)))
link))
pages)
"\n"))))
(add-hook 'one-hook 'make-sitemap)
Async commands
The function one-render-pages-async and one-build-async spawn an
emacs subprocess in order to build html pages asynchronously. The
arguments passed to emacs depends on one-emacs-cmd-line-args-async value.
By default, when one-emacs-cmd-line-args-async is nil, we run emacs
in "batch mode", we load the user's initialization file and we
evaluate a specific sexp that builds html pages. Specifically, we
pass the following command (emacs file name followed by command line
arguments) to make-process function like this:
(let* ((emacs (file-truename
(expand-file-name invocation-name invocation-directory)))
(command `(,emacs "--batch"
"-l" ,user-init-file
"--eval" ,sexp))
(sexp ...))
(make-process
:name ...
:buffer ...
:command command))
If one-emacs-cmd-line-args-async is non-nil, we no longer load the user's
initialization file and replace "-l" ,user-init-file in command above
by the elements of one-emacs-cmd-line-args-async. For instance, if
one-emacs-cmd-line-args-async is equal to
'("-l" "/path/to/some-elisp-file.el")
then command becomes
(let* (...
(command `(,emacs "--batch"
"-l" "/path/to/some-elisp-file.el"
"--eval" ,sexp))
...)
...)
Extend one-ox org backend
When we use the default render functions, the org content of the webpages is exported using one-ox org backend like this
(org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil)
where page-tree is the parsed tree of the headline containing the page
being rendered (see one-default render function).
While one-ox exports enough org elements for my use cases (see Why
one.el?) this might not be the case for you.
I think this is not a big problem because we can extend one-ox
(precisely we can derive a new org backend from one-ox org backend)
with other transcoder functions for the org elements that miss
transcoder functions.
Let's see how we can do that with an example.
Extend one-ox with horizontal-rule org elements
Lines consisting of only dashes (at least 5) are parsed by the org
parser as horizontal-rule org elements. one-ox doesn't provide a
transcoder function for horizontal-rule so we can't use it directly if
we want to have them exported as <hr> tags in our website.
In that section we see how to derived an org backend one-ox-with-hr
from one-ox org backend that exports horizontal-rule org elements
with <hr> tags.
To do that we define a transcoder function my-horizontal-rule which
takes 3 arguments (not used) and return the string "<hr>":
(defun my-horizontal-rule (_ _ _) "<hr>")
Then we use that function in the :translate-alist alist in the body of
the function org-export-define-derived-backend to define one-ox-with-hr
org backend:
(org-export-define-derived-backend 'one-ox-with-hr 'one-ox
:translate-alist
'((horizontal-rule . my-horizontal-rule)))
Then we can export the org content of the webpages (including the
horizontal-rule) using one-ox-with-hr org backend like this
(org-export-data-with-backend
(org-element-contents page-tree)
'one-ox-with-hr nil)
where page-tree is the parsed tree of the headline containing the page
being rendered.
Now that we saw how to derive one-ox-with-hr org backend and use it,
let's build a website with only a home page with two horizontal-rule.
In an empty directory let's add the following files:
-
one.org:* Home page :PROPERTIES: :ONE: my-render-function :CUSTOM_ID: / :END: foo ----- bar ----- baz -
onerc.el:(defun my-horizontal-rule (_ _ _) "<hr>") (org-export-define-derived-backend 'one-ox-with-hr 'one :translate-alist '((horizontal-rule . my-horizontal-rule))) (defun my-render-function (page-tree pages _global) "" (let* ((title (org-element-property :raw-value page-tree)) (content (org-export-data-with-backend (org-element-contents page-tree) 'one-ox-with-hr nil))) (jack-html "<!DOCTYPE html>" `(:html (:head (:title ,title)) (:body (:h1 ,title) ,content)))))
Now while visiting one.org file we call one-build to build our website
with <hr> tags.
one-ox
Org export backend used by the default render functions
one.el (specifically the default render functions) uses its own org
export backend called one-ox to export the org content of the pages
into HTML strings.
For instance, the render function one-default takes as first argument
page-tree which is the current page being rendered (page-tree is the
org parsed data structure representing the page) and exports it as an
HTML string using org-export-data-with-backend function and one-ox
export backend and uses it to render the HTML page:
(defun one-default (page-tree pages _global)
"..."
(let* (...
(content (org-export-data-with-backend
(org-element-contents page-tree)
'one-ox nil))
...)
(jack-html
"<!DOCTYPE html>"
`(:html
(:head ...)
(:body ... (:div.content ... ,content ,nav))))))
This org backend is taylor for one.el usage. So it doesn't try to
export all the org elements unlike html backend and when the org
elements are exported they differ from what we can expect from html
backend.
For instance headline elements don't take into account markups
neither links.
Another example are the link elements. They don't support org fuzzy
links and links to local files that are not in the subdirectories
./public/ or ./assets/ raise errors.
You can read how the supported org elements are exported by one-ox org
backend in the following page:
Org elements not supported
The org elements that are not supported are the following:
center-block, clock, drawer, dynamic-block, entity, export-block,
export-snippet, footnote-reference, horizontal-rule, inline-src-block,
inlinetask, keyword, latex-environment, latex-fragment, line-break,
node-property, planning, property-drawer, radio-target, special-block,
statistics-cookie, target, timestamp, verse-block.
Note that "not supported" means they are not rendered by default by
one.el but we can still use them or even extend one-ox org export
backend to take some of them into account.
Why doesn't one.el support all org elements?
-
I don't need those org elements to write my technical blogs:
- I don't do math. No support for Latex,
- etc.
one-oxorg backend is used only by the default render functions, so if you need more org elements you can either use another org backend or extendone-oxorg backend and use this other org backend in your own render functions (See Extend one-ox org backend).
one-ox | headline
Note that markups and links are not exported if used in headlines, only the raw value string.
So don't use them in headlines.
one-ox | src-block
Code highlighting with htmlize
Description
one-ox highlights code via the function one-ox-htmlize that uses
htmlize to do the work.
For a given piece of code X in a certain language Y, X will be
highlighted as it would be in the emacs mode Z used to edit Y code.
For instance, clojure-mode is used to highlight Clojure code and
sh-mode is used to highlight Bash code.
Attributes of a face (like background-color or foreground-color)
are not taken directly. A generated name for the face is produced and
used as the CSS class for the parts of the code X that are highlighted
with that face.
For instance, in sh-mode, the word echo is highlighted with the face
font-lock-builtin-face. So, the word echo in a piece of Shell (or
Bash) code will be transformed into:
<span class="one-hl-builtin">echo</span>
The whole piece of code X, once the previously described operations
have been done, is wrapped:
-
for a normal block with the component:
<pre><code class="one-hl one-hl-block">...</code></pre> -
for a result block with the component:
<pre><code class="one-hl one-hl-results">...</code></pre>See section org keyword RESULTS.
Example with Bash code
For instance, the following org src-block, containing some bash code:
#+BEGIN_SRC bash
echo "list file's extensions in current dir:"
for f in `ls`; do
echo ${f##*.}
done
#+END_SRC
is exported as follow:
<pre><code class="one-hl one-hl-block"><span class="one-hl-builtin">echo</span> <span class="one-hl-string">"list file's extensions in current dir:"</span>
<span class="one-hl-keyword">for</span> f<span class="one-hl-keyword"> in</span> <span class="one-hl-sh-quoted-exec">`ls`</span>; <span class="one-hl-keyword">do</span>
<span class="one-hl-builtin">echo</span> ${<span class="one-hl-variable-name">f</span>##*.}
<span class="one-hl-keyword">done</span></code></pre>
</div>
and rendered like this:
echo "list file's extensions in current dir:"
for f in `ls`; do
echo ${f##*.}
done
Note that one-ox-htmlize has produced and used the following CSS
classes (listed with their corresponding emacs faces):
# from font-lock
one-hl-builtin --> font-lock-builtin-face
one-hl-keyword --> font-lock-keyword-face
one-hl-string --> font-lock-string-face
one-hl-variable-name --> font-lock-variable-name-face
# specific to sh-mode
one-hl-sh-quoted-exec --> sh-quoted-exec
You might have notice the pattern used for font-lock faces and the one
used for mode specific faces.
one.el provides a default style sheet (one-default-css) that has the
CSS classes defined for all the font-lock faces (faces starting by
font-lock-) but not the specific faces used by each prog mode.
You can add the CSS classes specific to the prog modes you use as you go and need them.
Org keyword RESULTS
Result blocks are preceded by a line starting with #+RESULTS:. Blocks
that are not result blocks are normal blocks.
When exported, normal blocks and result blocks differ only by their CSS classes:
one-hl one-hl-blockfor normal blocks,one-hl one-hl-resultsfor result blocks.
This way result blocks can be rendered with a different style than normal blocks as we can see in the following example.
Example using org keyword 'RESULTS'
The following org snippet:
#+BEGIN_SRC bash :results output
ls
#+END_SRC
#+RESULTS:
: assets
: docs.org
: public
is exported by one-ox as follow:
<pre><code class="one-hl one-hl-block">ls</code></pre>
<pre><code class="one-hl one-hl-results">assets
docs.org
public</code></pre>
and is rendered by one-ox with the first block (normal block) having a
different style from second block (result block):
ls
assets docs.org public
Code blocks inside list
Lists can contain source blocks as we can see in the following org snippet
1. item 1
#+BEGIN_SRC emacs-lisp
(message "src-block in item 1")
#+END_SRC
2. item 2
3. item 3
which is exported by one as follow
<ol>
<li>
<p>item 1</p>
<pre><code class="one-hl one-hl-block">(message <span class="one-hl-string">"src-block in item 1"</span>)</code></pre>
</li>
<li><p>item 2</p></li>
<li><p>item 3</p></li>
</ol>
and is rendered by one-ox like this:
-
item 1
(message "src-block in item 1") - item 2
- item 3
one-ox | quote-block
Blocks defined with #+BEGIN_QUOTE ... #+END_QUOTE pattern are
quote-block.
They are exported by one-ox in a <blockquote>...</blockquote>
component with the CSS class one-blockquote.
The following org snippet:
#+BEGIN_QUOTE
A quitter never wins and a winner never quits. —Napoleon Hill
#+END_QUOTE
defines a quote and is exported by one-ox as follow
<blockquote class="one-blockquote"><p>A quitter never wins and a winner never quits. —Napoleon Hill</p></blockquote>
and looks like this
A quitter never wins and a winner never quits. —Napoleon Hill
one-ox | fixed-width and example-block
Description
A line starting with a colon : followed by a space defines a
fixed-width element. A fixed-width element can span several
lines.
Blocks defined with #+BEGIN_EXAMPLE ... #+END_EXAMPLE pattern are
example-block elements.
Both fixed-width and example-block blocks are treated as src-block in
text-mode. So:
- they are highlighted as
text-modewould do, - they are exported in
<pre><code>...</code></pre>components (indentation and newlines are respected) and -
the CSS classes used depend on the block's type:
- normal blocks use
one-hl one-hl-blockCSS classes and - result blocks use
one-hl one-hl-resultsCSS classes (see org keyword RESULTS).
- normal blocks use
Example
The following org snippet
Here is a ~fixed-width~ element (one line):
: I'm a fixed-width element
~fixed-width~ elements can also be used within lists:
- item 1
: fixed-width element
- item 2
#+BEGIN_SRC bash :results output
printf 'multiline fixed-width element\nthat is also a result block,\nso has a different style.'
#+END_SRC
#+RESULTS:
: multiline fixed-width element
: that is also a result block,
: so has a different style.
Although I don't often use ~example-block~ elements, here is one:
#+BEGIN_EXAMPLE
This is
an example!
#+END_EXAMPLE
is exported by one as follow
<p>Here is a <code class="one-hl one-hl-inline">fixed-width</code> element (one line):
</p>
<pre><code class="one-hl one-hl-block">I'm a fixed-width element</code></pre>
<p><code class="one-hl one-hl-inline">fixed-width</code> elements can also be used within lists:
</p>
<ul><li><p>item 1
</p>
<pre><code class="one-hl one-hl-block">fixed-width element</code></pre>
</li>
<li><p>item 2
</p>
<pre><code class="one-hl one-hl-block"><span class="one-hl-builtin">printf</span> <span class="one-hl-string">'multiline fixed-width element\nthat is also a result block,\nso has a different style.'</span></code></pre>
<pre><code class="one-hl one-hl-results">multiline fixed-width element
that is also a result block,
so has a different style.</code></pre>
</li>
</ul>
<p>Although I don't often use <code class="one-hl one-hl-inline">example-block</code> elements, here is one:
</p>
<pre><code class="one-hl one-hl-block">This is
an example!</code></pre>
and looks like this:
Here is a fixed-width element (one line):
I'm a fixed-width element
fixed-width elements can also be used within lists:
-
item 1
fixed-width element
-
item 2
printf 'multiline fixed-width element\nthat is also a result block,\nso has a different style.'multiline fixed-width element that is also a result block, so has a different style.
Although I don't often use example-block elements, here is one:
This is
an example!
one-ox | links
http, https, mailto links
Web links (starting by http or https) and links to message
composition (starting by mailto) are exported as we expect.
For instance the following link
http://tonyaldon.com
is exported as follow
<a href="http://tonyaldon.com">http://tonyaldon.com</a>
and rendered like this: http://tonyaldon.com.
This following link with a description
[[https://tonyaldon.com][Tony Aldon (https)]]
is exported as follow
<a href="https://tonyaldon.com">Tony Aldon (https)</a>
and rendered like this: Tony Aldon (https).
This mailto link
[[mailto:tony@tonyaldon.com][send me an email]]
is exported as follow
<a href="mailto:tony@tonyaldon.com">send me an email</a>
and rendered like this: send me an email.
Custom ID links
In one.el, CUSTOM_ID org property is used to defined the path of pages
or the path to specific heading in pages.
Considering the following org document
* Home Page
:PROPERTIES:
:ONE: one-default-home
:CUSTOM_ID: /
:END:
- [[#/blog/page-1/]]
- [[#/blog/page-1/#headline-1]]
* Page 1
:PROPERTIES:
:ONE: one-default
:CUSTOM_ID: /blog/page-1/
:END:
** headline 1 in Page 1
:PROPERTIES:
:CUSTOM_ID: /blog/page-1/#headline-1
:END:
the link [[#/blog/page-1/]] in "Home Page" targets "Page 1" page
and the link [[#/blog/page-1/#headline-1]] in "Home Page" targets the
heading "headline 1 in page Page 1" in the "Page 1" page.
Those paths define valid web urls starting at the root of the website
if we respect the following rules for CUSTOM_ID values:
- we use only url-encoded characters,
- we start them with a
/and end them with/excepted for the home page which is a single/, - we use
#character to start the last part of the path when we are targeting a heading tag with itsidbeing the last part after the#character.
The benefits of these "rules/conventions" are:
- when we export
custom-idlinks usingone-oxorg backend we can leave them as they are and - the navigation between pages inside emacs using
custom-idlinks works out-of-the-box.
Example of a link to a page
The following link
[[#/docs/one-ox-plain-list/][one-ox | plain-list]]
is exported to this anchor tag that links to the page /docs/one-ox-plain-list/:
<a href="/docs/one-ox-plain-list/">one-ox | plain-list</a>
and is rendered like this one-ox | plain-list.
Example of a link to a heading in a page
The following link
[[#/docs/one-ox-plain-list/#unordered-lists][unordered lists heading in the page about plain-list]]
is exported to this anchor tag that links to the heading with the id
set to unordered-lists on the page /docs/one-ox-plain-list/:
<a href="/docs/one-ox-plain-list/#unordered-lists">unordered lists heading in the page about plain-list</a>
and is rendered like this unordered lists heading in the page about plain-list.
Fuzzy links
I don't use fuzzy links. So, if there is a fuzzy link
in the document, that means I wrote the link wrong.
Broken links are bad user experience. I don't like them.
So I decided that one-ox raises an error (hard-coded) when we try to
export a fuzzy link to HTML.
For instance, the following fuzzy link:
[[fuzzy search]]
raise an error like the following:
(one-link-broken "fuzzy search" "fuzzy links not supported" "goto-char: 5523")
File links
Links to local files in assets and public directories
Links to local files in ./assets/ and ./public/ directories like
[[./assets/foo/bar.txt][Bar file]]
[[./public/foo/baz.txt][Baz file]]
are exported with the prefixes ./assets and ./public of the path
removed like this:
<a href="/foo/bar.txt">Bar file</a>
<a href="/foo/baz.txt">Baz file</a>
Local file links that raise one-link-broken error
Any file link that doesn't point to a file in ./assets/ or ./public/
subdirectories raises an one-link-broken error when we try to
export it with one-ox org backend
For instance if we try to export using one-ox org backend the
following link to the file foo.txt in the directory /tmp/
[[/tmp/foo.txt]]
which is not in ./public/ subdirectory nor in ./assets/ subdirectory
we will get an error like the following:
(one-link-broken "/tmp/" "goto-char: 26308")
Links to images
Links to local files in ./assets/ and ./public/ directories whom path
matches one-ox-link-image-extensions regexp are exported with an img
tag.
For instance the following link to an image in ./assets/img/ directory
[[./assets/img/keep-learning.png][Keep Learning]]
is exported as follow
<img href="/img/keep-learning.png" alt="Keep Learning"></a>
and rendered like this
one-ox | plain-list and item
Only unordered and ordered lists are supported.
Unordered lists
The following org snippet (unordered list):
- a thing,
- another thing,
- and the last one.
is exported by one-ox as follow
<ul>
<li>
<p>a thing,</p>
</li>
<li>
<p>another thing,</p>
</li>
<li>
<p>and the last one.</p>
</li>
</ul>
and is rendered like this:
- a thing,
- another thing,
- and the last one.
Ordered list
The following org snippet (unordered list):
1. first,
2. second,
3. third.
is exported by one-ox as follow
<ol>
<li>
<p>a thing,</p>
</li>
<li>
<p>another thing,</p>
</li>
<li>
<p>and the last one.</p>
</li>
</ol>
and is rendered like this:
- first,
- second,
- third.
one-ox | table
Tables with header rows are supported.
The following org snippet:
| Name | Age | City |
|-------+-----+----------|
| Alice | 30 | New York |
| Bob | 25 | London |
is exported by one-ox as follow
<table>
<tr>
<th>Name</th>
<th>Age</th>
<th>City</th>
</tr>
<tr>
<td>Alice</td>
<td>30</td>
<td>New York</td>
</tr>
<tr>
<td>Bob</td>
<td>25</td>
<td>London</td>
</tr>
</table>
The first row is automatically exported as <th> (header) tags when
separated by a horizontal rule (|---+---|), and subsequent rows use
<td> (data) tags.