Ox-Tailwind Export Back-End

Check the output in ox-tailwind GitHub docs.

This back-end has the purpose of allowing easy customization of the HTML output. Although it is called Tailwind, the only thing that it does is allowing you to customize the classes of the HTML and exporting a more barebones HTML (It does not create as many divs and sections as the normal HTML export back-end). Instead of using Tailwind.css you can just name the classes of the elements and import your own css (or edit ./css/style.css).

Note: Although you can export any .org file as html by using C-c C-e x h (through org-export-dispatcher), this method won't create the file inside the dist folder where you have all the required js and css files. If you open this file on a browser and open the console you will notice there are import errors. You should instead setup your notes directory with org-publish-project-alist and then publish the project using (org-publish-all) or (org-publish-all t) to forcefully publish files.

Installation

To install this back-end, clone this repo into your packages or private folder in your ~/.emacs.d directory:

Console

git clone https://github.com/vascoferreira25/ox-tailwind
(out)Cloning into '~/.emacs.d/private'...
(out)remote: Enumerating objects: 49, done.
(out)remote: Counting objects: 100% (49/49), done.
(out)remote: Compressing objects: 100% (40/40), done.
(out)remote: Total 49 (delta 20), reused 28 (delta 8), pack-reused 0
(out)Unpacking objects: 100% (49/49), done.

And add the following to your config file:

your_config.el

;; ox-tailwind export back-end
(load "~/.emacs.d/private-packages/ox-tailwind/ox-tailwind.el")

Afterwards, copy the notes_example folder to get an already made directory structure for your notes including all the required CSS and JS files. You just have to setup org-publish-project-alist and then use the (org-publish-all) command and you are done. Check the /notes_example/index.org file.

Snippets!

If you want snippets so you don't need to remember all the custom attributes, just copy the snippets in the snippets folder into your snippets/org folder.

You can trigger all the snippets by starting with <ox.

Available snippets:

<ox-details
insert details block
<ox-tip
insert tip block
<ox-warning
insert warning block
<ox-danger
insert danger block
<ox-mermaid
insert mermaid diagram block
<ox-img-video
insert image or video
<ox-table
insert a table
<ox-src
insert a source code block

Straight Package Management

If you use straight as your package management, add this to your config file:

(straight-use-package
 '(ox-tailwind :type git :host github :repo "vascoferreira25/ox-tailwind"))

Doom

To use this package with Doom Emacs, add the package to packages.el:

~/.doom.d/packages.el

(package! ox-tailwind
  :recipe (:host github :repo "vascoferreira25/ox-tailwind"))

And then, load it after ox-html

~/.doom.d/config.el

(after! ox-html (require 'ox-tailwind))

Org-Roam

If you use org-roam and want to show the backlinks of the current file, add this function to your config:

~/config.el or ~/config.org

;; Add backlinks to the export
(defun collect-backlinks-string (backend)
  (when (org-roam-node-at-point)
    (let* ((source-node (org-roam-node-at-point))
           (source-file (org-roam-node-file source-node))
           ;; Sort the nodes by the point to avoid errors when inserting the
           ;; references
           (nodes-in-file (--sort (< (org-roam-node-point it)
                                     (org-roam-node-point other))
                                  (-filter (lambda (node)
                                             (s-equals?
                                              (org-roam-node-file node)
                                              source-file))
                                           (org-roam-node-list))))
           ;; Nodes don't store the last position so, get the next node position
           ;; and subtract one character
           (nodes-start-position (-map (lambda (node) (org-roam-node-point node))
                                       nodes-in-file))
           (nodes-end-position (-concat (-map (lambda (next-node-position)
                                                (- next-node-position 1))
                                              (-drop 1 nodes-start-position))
                                        (list (point-max))))
           ;; Keep track of the current-node index
           (current-node 0)
           ;; Keep track of the amount of text added
           (character-count 0))
      (when (and nodes-in-file (s-equals? backend "latex"))
        (insert "\n\n\\clearpage\n\n"))
      (dolist (node nodes-in-file)
        (when (org-roam-backlinks-get node)
          ;; Go to the end of the node and don't forget about previously inserted
          ;; text
          (goto-char (+ (nth current-node nodes-end-position) character-count))
          ;; Add the references as a subtree of the node
          (setq heading (format "\n\n%s Backlinks\n"
                                (s-repeat (+ (org-roam-node-level node) 1) "*")))
          ;; Count the characters and count the new lines (4)
          (setq character-count (+ 3 character-count (string-width heading)))
          (insert heading)
          ;; Insert properties drawer
          (setq properties-drawer ":PROPERTIES:\n:HTML_CONTAINER_CLASS: references\n:END:\n")
          ;; Count the characters and count the new lines (3)
          (setq character-count (+ 3 character-count (string-width properties-drawer)))
          (insert properties-drawer)
          (dolist (backlink (org-roam-backlinks-get node))
            (let* ((source-node (org-roam-backlink-source-node backlink))
                   (point (org-roam-backlink-point backlink))
                   (text (s-replace "\n" " " (org-roam-preview-get-contents
                                              (org-roam-node-file source-node)
                                              point)))
                   (references (format "- [[./%s][%s]]: %s\n\n"
                                       (file-relative-name (org-roam-node-file source-node))
                                       (org-roam-node-title source-node)
                                       text)))
              ;; Also count the new lines (2)
              (setq character-count (+ 2 character-count (string-width references)))
              (insert references))))
        (setq current-node (+ current-node 1))))))

(add-hook 'org-export-before-processing-hook 'collect-backlinks-string)

Notes structure

Org-Roam

You can also use Org-Roam to handle all your notes. In that case, don't follow the following folder structure.

In order for the search bar to work, this export backend will search all .org files in the path of your project and store all the headings in a .js file.

The folder structure for your notes should be like this:

C:/notes

tree /F /a
(out)C:/path/to/your_notes/
(out)|   index.org
(out)|   ... other org files
(out)|   
(out)+---files
(out)|       # Your attachments
(out)|
(out)+---img
(out)|       # Your image files
(out)|       
(out)+---dist
(out)        # Org Publish Files

The home page for your notes should be index.org.

For easier search and note management, the notes should be named as <subject>_<notes_name>_<guide|snippets|templates|...>.org.

Examples:

NotesFile Name
Programming Guides Indexprogramming.org
Python notes Indexprogramming_python.org
Python guideprogramming_python_guide.org
Python snippetsprogramming_python_snippets.org
Guides Indexguides.org
How to manage Ebooks guideguides_manage_ebooks.org
Gaming Indexgaming.org
Skyrim guidegaming_skyrim.org
Subjects Indexsubjects.org
Mathematicssubjects_mathematics.org
Economicssubjects_economics.org

Index files should have the following template

subjects.org

* Subjects
** Pages
   
[[./subjects_accounting_and_finance.org][Accounting and Finance]]

[[./subjects_computer_science.org][Computer Science]]

[[./subjects_economics.org][Economics]]

[[./subjects_elo_rating.org][Elo Rating System]]

[[./subjects_mathematics.org][Mathematics]]

[[./subjects_statistics.org][Statistics]]

** References

Publish settings

To setup automatic export of all my org files I use the following settings:

your_config.el

(setq org-publish-project-alist
      '(("org-files"
         :base-extension "org"
         :base-directory "V:/orgmode/"
         :publishing-directory "V:/orgmode/dist/"
         ;; or use `org-tailwind-publish-to-html' to generate the toc after each
         ;; file - *note*: it will be slower to parse the whole project
         :publishing-function org-tailwind-publish-to-html-without-toc) 
        ("images"
         :base-directory "V:/orgmode/img/"
         :base-extension ".*"
         :publishing-directory "V:/orgmode/dist/img/"
         :publishing-function org-publish-attachment)
        ("files"
         :base-directory "V:/orgmode/files/"
         :base-extension ".*"
         :publishing-directory "V:/orgmode/dist/files/"
         :publishing-function org-publish-attachment)
        ("tangles"
         :base-directory "V:/orgmode/tangles/"
         :base-extension ".*"
         :publishing-directory "V:/orgmode/dist/tangles/"
         :publishing-function org-publish-attachment)
        ;; Publish all in one time
        ("notes" :components ("org-files" "images" "files" "tangles"))))

After setting up your notes path, you should use (org-publish-all) to publish all the notes as html.

Note: Before publishing, open a buffer on one of your .org files or just dired into the notes directory. As this back-end needs to create a .js file based on your .org files to enable searching, if the Emacs current directory isn't in the notes directory, it will fail to create this file.

In order to be faster to parse all your notes, it is advisable to only generate the toc file after publishing. Use the following functions instead of the org-export-dispatch to automatically generate the toc after publishing:

(defun publish-file-and-build-toc ()
  "Force publish the current org-mode file."
  (interactive)
  (org-publish-current-file)
  (org-tailwind-build-toc))

(defun force-publish-file-and-build-toc ()
  "Force publish the current org-mode file."
  (interactive)
  (org-publish-current-file t)
  (org-tailwind-build-toc))

(defun publish-all-and-build-toc ()
  "Force publish all org-mode files."
  (interactive)
  (org-publish-all)
  (org-tailwind-build-toc))

(defun force-publish-all-and-build-toc ()
  "Force publish all org-mode files."
  (interactive)
  (org-publish-all t)
  (org-tailwind-build-toc))

Notes output directory

In order for the export to work, you need to put the required files in the output folder. Just copy the /notes_example/dist folder into your notes /dist/ folder.

This is the directory structure of the export folder:

C:/notes/dist

tree /F /a
(out)C:/path/to/your_notes/dist/
(out)|   # The HTML export
(out)|   index.html
(out)|   
(out)+---css
(out)|       prism.css
(out)|       style.css # Your css file
(out)|       tailwind.min.css
(out)|       
(out)+---files
(out)|       # Your attachments
(out)+---img
(out)|       # Your image files
(out)|       spacemacs_1.png
(out)|       spacemacs_2.png
(out)|       
(out)+---js
(out)|       clipboard.min.js
(out)|       mermaid.min.js
(out)|       polyfill.min.js
(out)|       prism.js
(out)|       tex-mml-chtml.js
(out)|       toc_tree.js
(out)|       
(out)+---mathjax
(out)        # Mathjax Files

Cleaning the output folder

When publishing your org files, Org-Mode won't delete any files in the /dist/ folder. If you delete org files and don't delete those files from the /dist/ folder, you will end up with obsolete html files. In this case, what you should is delete all the html files and then use (org-publish-all t) to force publish all your org files again.

Also, if you delete images, tangles or other files from /your_notes/files, /your_notes/tangles or /your_notes/img there will be a copy of them in the /dist/ folder.

To completely clean the /dist/ folder you can delete all the following files and folders:

  • /dist/files,

  • /dist/img,

  • /dist/tangles,

  • all .html files.

Customization

Link in the top

By default, the file-name on the top of the page is a link for org-protocol.

You can customize the link by changing the variable org-tailwind-file-name-link or you can disable the link by changing the variable org-tailwind-file-name-use-link to nil.

Org-protocol settings

Setup the org-protocol.

(server-start)
(require 'org-protocol)

;; Custom protocols
(defun my-open-file-protocol-handler (data)
  (let* ((file-link (plist-get data :file))
         (file-path (replace-regexp-in-string "^\\(.*\\)#.*" "\\1" file-link)))
    (message "file-path: %s" file-path)
    (find-file file-path))
  ;; it must end in nil or it will create a buffer 
  nil)

(setq org-protocol-protocol-alist
      '(("open-file"
         :protocol "open-file"
         :function my-open-file-protocol-handler)))

Define a new link type for protocols and the export method:

(require 'ol)

(org-link-set-parameters "org-protocol"
                         :follow #'my-org-protocol-open
                         :export #'my-org-protocol-export
                         :store #'my-org-protocol-store-link)

(defun my-org-protocol-open (path _)
  "Visit the org-protocol on PATH.
PATH should be a topic that can be thrown at the man command."
  (browse-url path))

(defun my-org-protocol-store-link ()
  "Store a link to a man page."
  (when (memq major-mode '(org-mode))
    ;; This is a man page, we do make this link.
    (let* ((file (my-org-protocol-get-file-name))
           (link (concat "" file))
           (description (format "org-protocol for %s" file)))
      (org-link-store-props
       :type "org-protocol"
       :link link
       :description description))))

(defun my-org-protocol-get-page-name ()
  "Extract the file name from the buffer name."
  (if (string-match " \\(\\S-+\\)\\*" (buffer-name))
      (match-string 1 (buffer-name))
    (error "Cannot create link to this org-protocol file")))

(defun my-org-protocol-export (link description format _)
  "Export a man page link from Org files."
  (let ((path (format "org-protocol:%s" link))
        (desc (or description link)))
    (pcase format
      (`html (format "<a href=\"%s\">%s</a>" path desc))
      (`latex (format "\\href{%s}{%s}" path desc))
      (`texinfo (format "@uref{%s,%s}" path desc))
      (`ascii (format "%s (%s)" desc path))
      (t path))))

Export links to files with a link to open those files in Emacs through the protocol:

(defun my-open-in-emacs-link (backend)
  "Add a link to use org-protocol to open a file in emacs."
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward "\\(\\[\\[file:.*?\\]\\[.*\\]\\]\\)" nil t)
      (message "MATCHED: %s" (match-string-no-properties 1))
      (let* ((file-path (replace-regexp-in-string
                         ".*\\[\\[file:\\(.*?\\)\\]\\[.*"
                         "\\1"
                         (match-string-no-properties 1)))
             (fixed-file-path (replace-regexp-in-string
                               "\\(.*?\\)\\(::.*\\)?"
                               "\\1"
                               file-path)))
        (message "FILE_PATH: %s" fixed-file-path)
        (replace-match
         (format "%s ([[org-protocol://open-file?file=%s][open in Emacs]])"
                 (match-string-no-properties 1)
                 fixed-file-path))))))

(add-hook 'org-export-before-processing-hook 'my-open-in-emacs-link)

Customize Theme

To customize the theme you have to change the org-tailwind-class-... variables. There are multiple classes for all the Html tags. For example, changing the theme of a h1 tag:

(defcustom org-tailwind-class-h1
  "mt-24 mb-6 text-3xl text-gray-700 dark:text-gray-400 border-b \
hover:text-blue-400 dark:hover:text-blue-500 border-gray-500")

You can check all the other Html elements in the ox-tailwind.el file.

TailwindCSS

If you want to change TailwindCSS settings you can use the TailwindCSS - CLI tool. In the folder tailwind you can change the tailwind.config.js and build the css file. Then you just need to copy the output file to the /dist/css folder.

To create a config file you need to run:

Console

cd ./tailwindcss
npx tailwindcss-cli@latest init

To build the css file run:

Console

npx tailwindcss-cli@latest build -o ./tailwindcss/tailwind.css

Prism.js

To customize the code blocks, you can just download another theme from the Prism.js website and save both the js and the css file in your /dist folder. The link already has all the options selected, just select the theme you want and download both the js and css files and save them into the dist folder.

These are the required modules for Prism to work:

  • line highlight

  • line numbers

  • autolinker

  • file highlight

  • show language

  • jsonp highlight

  • inline color

  • previewers

  • autoloader

  • keep markup

  • command-line

  • unescaped markup

  • normalize whitespace

  • data-uri highlight

  • toolbar

  • copy to clipboard button

  • download button

  • match braces

  • diff highlight

  • filter highlight all

  • treeview

Mathjax

Mathjax has been downloaded from source by running:

Console

git clone https://github.com/mathjax/MathJax.git mathjax

And then copy the files from /mathjax/es5 into the /dist/mathjax folder.

Mermaid.js

Mermaid has been downloaded from source by running:

Console

git clone https://github.com/mermaid-js/mermaid.git

And then copy the files from /mermaid/dist into the /dist/js folder.

Elements

Markup

Text

Bold Text

Italic Text

Underlined Text

Strike Through

Verbatim

Inline code

HyperLinks

Lists

Ordered List
  1. Item number 1

    1. Item number 1.1

    2. Item number 1.2

    3. Item number 1.3

  2. Item number 2

  3. Item number 3

  4. Item number 4

  5. Item number 5

Unordered List
  • Like

    • This

      • One

Description List
Tip Blocks
Are for displaying tips.
Warning Blocks
Are for displaying warnings.
Danger Blocks
Are for displaying dangers.
Checkboxes
  • Unchecked 1

  • Unchecked 2

  • Checked 1

Tables

ABC
In this columnIn thisFinally,
the textcolumnin this one
is left alignedit is centeredit is right aligned

This is an example table and description

Images

I don't have words...

Videos

What is this?

Formulas

Inline formulas: \(\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}\)

\[\sum_{i=0}^n i^2 = \frac{(n^2+n)(2n+1)}{6}\]

Blocks

Blockquote

Once upon a time..........

The name of the author

Source Blocks

Source code blocks can be downloaded directly from github:


This uses the following attributes:

#+ATTR_FILENAME: core.cljs
#+ATTR_HIGHLIGHT: 2,6-8,11-20,48-51
#+ATTR_FETCH: https://api.github.com/repos/vascoferreira25/discord-bot/contents/src/main/core.cljs

Custom Blocks

There are four custom blocks: details, tip, warning and danger and these blocks can contain other elements. In order to get syntax highlighting while editing in Emacs, use org as language.

Details

Details

All the stuff in here will be hidden ....

Tip

Tip text.

Warning

Warning text.

Danger

Danger Title

These blocks can contain other blocks.

Code in a shell?

Console

cd c:/emacs/bin/runemacs.exe
(out)I rocks!

Cool! Isn't it?

Mermaids

There are also mermaids.

Diagram
sequenceDiagram participant Alice participant Bob Alice->>John: Hello John, how are you? loop Healthcheck John->>John: Fight against hypochondria end Note right of John: Rational thoughts
prevail! John-->>Alice: Great! John->>Bob: How about you? Bob-->>John: Jolly good!

A simple diagram.

Gantt Chart
gantt dateFormat YYYY-MM-DD title Adding GANTT diagram to mermaid section A section Completed task :done, des1, 2014-01-06,2014-01-08 Active task :active, des2, 2014-01-09, 3d Future task : des3, after des2, 5d Future task2 : des4, after des3, 5d section Critical tasks Completed task in the critical line :crit, done, 2014-01-06,24h Implement parser and jison :crit, done, after des1, 2d Create tests for parser :crit, active, 3d Future task in critical line :crit, 5d Create tests for renderer :2d Add to mermaid :1d section Documentation Describe gantt syntax :active, a1, after des1, 3d Add gantt diagram to demo page :after a1 , 20h Add another diagram to demo page :doc1, after a1 , 48h section Last section Describe gantt syntax :after doc1, 3d Add gantt diagram to demo page : 20h Add another diagram to demo page : 48h

What a beautiful chart. What does it mean?

Custom Attributes

The following blocks have custom attributes that you can change:

Source code
#+ATTR_HIGHLIGHT
lines to highlight in the source code, e.g. 1,5-10,12
#+ATTR_USERNAME
username to show in command-line blocks, e.g. CrazyCat
#+ATTR_HOSTNAME
hostname to show in command-line blocks, e.g. localhost
#+ATTR_FETCH
fetch files from the Github API
#+ATTR_FILEPATH
get files and add a download button, it uses HTTP so, no local files.
#+ATTR_FILENAME
name to display on the source code window.
Custom blocks
#+NAME
the title of the block
Tables
#+NAME
the description of the table
Images
#+NAME
the description of the image
Videos
#+NAME
the description of the video
#+ATTR_TIMELINE
the time of the start and/or end of the video, for example:
  • #+ATTR_TIMELINE: 5

  • #+ATTR_TIMELINE: 5,9

Blockquotes
#+NAME
the name of the author

Known bugs

  • It crashes when it encounters a line that ends in \\ - it works if it is inside a block;

  • It won't export TODO keywords and SCHEDULE dates.