Nabladown.js
A parser and renderer for the Nabladown language.
NablaDown.js is a JS library able to parse: String -> Abstract Syntax Tree a pseudo/flavored Markdown language and render: Abstract Syntax Tree -> HTML it into HTML.
The purpose of this library is to render beautiful documents in HTML, using a simple language as Markdown, with the focus on rendering code,equations and html. It includes macros for extending the language features.
The library is written in a way, that is possible to create and compose multiple renderers together. This way is possible to add features on top of a basic renderer. More on that below (check the Advanced section).
Contents
Quick Start
Nabladown.js provides two main functions:
parse: String -> ASTrender: AST -> HTML
The parser will produce a Abstract Synatx Tree(AST) from a string, and render will create HTML nodes from the parsing tree.
Web
<!DOCTYPE html> <html lang="en"> <head> </head> <body> </body> <script type="module"> import { parse, render } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/index.js"; // You can also import from local file e.g: // import { parse, render } from "./node_modules/nabladown.js/dist/web/index.js"; const content = "#$\\nabla$ Nabladown`.js`\n <span style='background: blue'>Check it out</span> [here](https://www.github.com/pedroth/nabladown.js)\n"; render(parse(content)).then(dom => document.body.appendChild(dom)); </script> </html>
React
Install it using npm install nabladown.js
import { useEffect, useState } from 'react' import { parse, render } from "nabladown.js/dist/web/index" import './App.css' const content = "#$\\nabla$ Nabladown`.js`\n <span style='background: blue'>Check it out</span> [here](https://www.github.com/pedroth/nabladown.js)\n"; function App() { const [dom, setDom] = useState(""); useEffect(() => { render(parse(content)).then((nablaDom) => setDom(nablaDom)); }, []) return ( <div dangerouslySetInnerHTML={{ __html: dom.innerHTML }}> </div> ) } export default App
Node / Bun
Install it using npm install nabladown.js / bun add nabladown.js
import { parse, render } from "nabladown.js/dist/node/index.js"; const content = "#$\\nabla$ Nabladown`.js`\n <span style='background: blue'>Check it out</span> [here](https://www.github.com/pedroth/nabladown.js)\n"; (async () => { const domStr = await render(parse(content)) console.log(domStr); })();
With formatted string:
import { parse, renderToString } from "nabladown.js/dist/node/index.js"; const content = "#$\\nabla$ Nabladown`.js`\n <span style='background: blue'>Check it out</span> [here](https://www.github.com/pedroth/nabladown.js)\n"; (async () => { const domStr = await renderToString(parse(content), {isFormatted: true}) console.log(domStr); })();
NPM
Check npm page here, to check all nabladown.js versions.
Language cheat sheet
This language is similar markdown syntax but adds some extras like formulas, code, HTML, and macros.
Although similar to markdown, it has some minor differences
Headers
# H1 ## H2 ### H3 ... ###### H6
Style
_italics_ *bold* *_bold and italics_* _*italics and bold*_
Paragraph
lorem ipsum lorem ipsum // paragraph lorem ipsum lorem ipsum. lorem ipsum lorem ipsum. lorem ipsum lorem ipsum lorem ipsum lorem ipsum // paragraph lorem ipsum lorem ipsum. lorem ipsum lorem ipsum. lorem ipsum lorem ipsum lorem ipsum lorem ipsum. lorem ipsum lorem ipsum. lorem ipsum lorem ipsum. // paragraph
Lists
Unordered
- Parent - Child - GrandChild - GrandChild - Child
Ordered
+ Parent + Child + GrandChild + GrandChild + Child
Compatibility with markdown ordered lists
// numbers don't really matter, // they just need to be numbers 1. Parent 2. Child 3. GrandChild 3. GrandChild 8. Child
Mixed type
+ Ordered Parent - Unordered Child - Unordered Child - Unordered Child + Ordered Parent - Unordered Child - Unordered Child
Or,
1. Ordered Parent - Unordered Child - Unordered Child - Unordered Child 2. Ordered Parent - Unordered Child - Unordered Child
Indentation
Single spaces:
- A list - A sublist - A subsublist - A sublist - A list
Single tabs:
- A list - A sublist - A subsublist - A sublist - A list
For now,
nabladown.jsis not able to write paragraphs in lists. Like here. To be added in future. But there is an hack:- A list - <div> !!! Write paragraph as usual !!! </div> - Another list item
Links
// simple link [nabladown.js](https://pedroth.github.io/nabladown.js/) // link using reference [brave][ref] Some optional text... [ref]: https://search.brave.com/
It is possible to link to titles:
# A Title [Go to title](#a-title)
You can also use bare links like this:
https://pedroth.github.io/nabladown.js/Footnotes
Some optional text [^1] blablabla [^foot] blablabla ... [^1]: Text with *nabladown* syntax [^foot]: You can use any identifier
For now, it's not possible to add paragraphs in footnotes, like here. But there is an hack:
A complex footnote[^complex] !! --- [^complex]: <div> !!! Write nabladown as usual! !!!! </div>
Images/Videos
// simple image  // image with title  // Image with link to it [![Image reference + *link* + reference][link_variable]][link_variable] [link_variable]: some link to image // video ![Free *video*][open_video_var] [open_video_var]: https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4 // youtube video with legend  // sound 
Math
Use Tex syntax inside '$'.
// inline Lorem ipsum $x^2+1 = 0$ // paragraph $$e^{2\pi i} - 1 = 0$$ // paragraph $$ \oint_{\partial\Omega} \alpha = \ int_\Omega \text{d} \alpha $$
Code
Inline code
lorem ipsum `inline code here` lorem ipsum
Block code
```java
class Main {
public static void main(String[] args) {
System.out.println("Hello")
}
}
```
In the abstract
```<language>
block code...
```
Syntax here. Name of the available languages according to highlight.js
Line Separation
lorem ipsum --- lorem ipsum
HTML
# Normal markdown with <span style="color: red"> red text </span> inline A paragraph with html and nabladown inside: <div> <a href="https://pedroth.github.io/nabladown.js"> $1 + 1 = 2$ </a> <button onClick="alert('hello world')"> hello *world* </button> </div>
Comments
Normal html comments:
<div class="quote"> <a href="https://pedroth.github.io/nabladown.js"> $$\sum_ {n=1}^\infty 1 / n^2 = \pi^2 / 6$$ </a> <hr /> <!-- A comment --> <div style="text-align: center"> <button onClick="alert('hello world')"> hello _*world*_!! </button> </div> </div> <!-- Another comment With text in it!! -->
Macros
Macros definitions:
:: // Define a function in js, with form: // f: (input: string, array: string[]) => string (a string that contains nabladown.js syntax) function addClass(input, args) { // this macro add a particular class to nabladown input const [className] = args; return `<div class="${className}">${input}</div>` } function quote(input, args) { return `<blockquote>${input}</blockquote>`; } function date(input, args) { const now = new Date(); return `_${now.toDateString()}_`; } // export function in special way MACROS = {addClass, quote, date} ::
Macros usage:
@addClass(myClass){ Normal $\nabla$nabladowns`.js` } @quote(){ A _quote_ *in nabladown* here }
Macros usage inline:
Hello @addClass(red){world}!!
Macros without nabladown input:
Arguments are differentiated through the , character unless they have " quotes:
:: function id(input, args) { // this macros adds an id to a particular nabladown input const [name] = args; return `<div id="${name}">${input}</div>`; } MACROS={id} :: @id("hello world"){ *Hello world!!!* }
A general usage of macros would be:
@alreadyDefinedMacroFunction(arg1,arg2, ..., argN){
A nabladown.js string
}
As inline:
... @alreadyDefinedMacroFunction(arg1, arg2, ..., argN){A nabladown.js string} ...
It is possible to import macros:
::
import "./path2macros.js";
import "./src/macros.js";
::
That is the only way to import files, for now. The file with defining macros should be something like this:
// macros.js
function macro1(input, args) {
...
}
...
function macroN(input, args) {
...
}
MACROS = {macro1, ..., macroN}
Example: creating details section using a macro
:: function details(input, args) { const [title] = args; return ` <details> <summary>${title}</summary> ${input} </details>` } MACROS = {details} :: # A details example @details("Factorial definition"){ $$ n! = \begin{cases} 1 & \text{if } n = 0, \\ n \times (n-1)! & \text{if } n > 0. \end{cases} $$ }
Try it
You can try nabladown.js language in two ways:
- Using playground
- Using the nabladown-server
Advanced
Library exports
This library exports:
- Parser.js
- Render.js (vanilla render)
- MathRender.js (vanilla + math)
- CodeRender.js (vanilla + code)
- NablaRender.js (vanilla + math + code)
Web import
import {parse} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/Parser.js" import {render as vanillaRender, Render} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/Render.js" import {render as mathRender, Render as MathRender} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/MathRender.js" import {render as codeRender, Render as CodeRender} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/CodeRender/CodeRender.js" import {render as codeRender, Render as NablaRender} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/NabladownRender.js"
You can also import a particular version of
nabladown.jsfrom jsdelivr
You can also point to local nabladown.js
import {parse} from "<LOCAL_NABLADOWN.JS>/dist/web/Parser.js" import {render as vanillaRender, Render} from "<LOCAL_NABLADOWN.JS>/dist/web/Render.js" import {render as mathRender, Render as MathRender} from "<LOCAL_NABLADOWN.JS>/dist/web/MathRender.js" import {render as codeRender, Render as CodeRender} from "<LOCAL_NABLADOWN.JS>/dist/web/CodeRender/CodeRender.js" import {render as codeRender, Render as NablaRender} from "<LOCAL_NABLADOWN.JS>/dist/web/NabladownRender.js"
Node / Bun
import {parse} from "nabladown.js/dist/node/Parser.js" import {render as vanillaRender, Render} from "nabladown.js/dist/node/Render.js" import {render as mathRender, Render as MathRender} from "nabladown.js/dist/node/MathRender.js" import {render as codeRender, Render as CodeRender} from "nabladown.js/dist/node/CodeRender/CodeRender.js" import {render as codeRender, Render as NablaRender} from "nabladown.js/dist/node/NabladownRender.js"
Using all renders
<html> <body></body> <script type="module"> import { parse } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/Parser.js" import { render as vanillaRender } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/Render.js" import { render as mathRender } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/MathRender.js" import { render as codeRender } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/CodeRender/CodeRender.js" import { render as nablaRender } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/NabladownRender.js" (async () => { // append basic rendering await vanillaRender(parse("# $\\nabla$ Nabladown`.js` \n")).then(dom => document.body.appendChild(dom)); // append code rendering await codeRender(parse("# $\\nabla$ Nabladown`.js` \n")).then(dom => document.body.appendChild(dom)); // append math rendering await mathRender(parse("# $\\nabla$ Nabladown`.js` \n")).then(dom => document.body.appendChild(dom)); // append nabladown rendering await nablaRender(parse("# $\\nabla$ Nabladown`.js` \n")).then(dom => document.body.appendChild(dom)); })() </script> </html>
Extending basic renderer
It is possible to extend the basic renderer, to build a custom one.
There are a few ways of doing this:
- Adding style to HTML components using regular CSS.
- Extending
Render classfrom Render.js
The CodeRender class is an example of extending the Render class, where code highlight was implemented.
The MathRender class is an example of extending the Render class, where katex rendering was added.
You can also combine multiple renderers together using composeRender function. Check NabladownRender class for an example of that.
Changing CSS
<html> <head> <style> body { background-color: #212121; color: white; font-family: sans-serif; } body h1 { text-decoration: underline; background-color: blue; } body code { border-style: solid; border-width: thin; border-radius: 6px; box-sizing: border-box; background-color: red; border: hidden; font-size: 85%; padding: 0.2em 0.4em; color: green; } </style> </head> <body></body> <script type="module"> import {parse, render} from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/index.js" render(parse("# $ \\nabla $ Nabladown`.js` \n")).then(dom => document.body.appendChild(dom)); </script> </html>
Extending NabladownRender class
Change headers color based on their level
<html> <body></body> <script type="module"> import { parse } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/index.js" import { Render } from "https://cdn.jsdelivr.net/npm/nabladown.js/dist/web/NabladownRender.js"; class CustomRender extends Render { /** * (title, context) => DomBuilder */ renderTitle(title, context) { const colors = ["red", "orange", "yellow", "green", "blue", "purple"]; const { level } = title; const header = super.renderTitle(title, context); header.attr("style", `color:${colors[level - 1]}`); return header; } } const render = syntaxTree => new CustomRender().render(syntaxTree); const text = `# $ \\nabla$Nabladown.js \n#### $ \\nabla$Nabladown.js \n#####$ \\nabla$Nabladown.js \n`; // append custom rendering render(parse(text)).then(dom => document.body.appendChild(dom)); </script> </html>
All render methods return a
DOM abstractionobject, described here.
For more details, you need to dig the source code :D
Develop Nabladown.js
Dependencies
nabladown.js is using bun@^1.1.21, nodejs@^22.3.0 and npm@10.8.0
Building library
bun run build
Publishing library
bun run pub
Testing
Running unit tests: bun test.
Running playground index.html, just use bun serve.
Influences
TODO
-
Optimize html generation
- Remove unnecessary spans, divs, etc.
-
Total compatibility between nodejs and browser rendering.
- Copy button doesn't work when generating html as string
-
Optimize fetching styles
-
Make nabladown.js a totally offline lib
- Use local katex style instead of online one
-
Add paragraphs to lists as here and footnotes
-
Add inline attributes to links, equations, custom... as Quatro and this or this
-
Add easy tables, check AsciiDoc tables and Orgmode tables
-
Think about escaping characters, like `, <, *, >, _
-
Optimize Playground
- Loading screen
- Render by chunks
- Show token info in playground
-
Add dialog in images (expanding images in cell phone) - Check photoswipe, glightbox
-
Multiple styles in code rendering
-
Add metadata space such Quatro
-
Change some recursions to linear recursions or just loops (?)
- Apply parseAnyBut loop to parseDocument, parseExpressions, ...
