nabladown.js v2.0.30
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
,html
and custom behavior
.
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 -> AST
render: 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
// 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, and HTML.
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
// numbers don't really matter,
// they just need to be numbers
1. Parent
2. Child
3. GrandChild
3. GrandChild
8. Child
Mixed type
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.js
is not able to write paragraphs in lists. Like here. To be added in future.
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
Images/Videos
// simple image
![Image legend](https://picsum.photos/200)
// image with title
![Image _legend_ with $\nabla$](https://picsum.photos/200)
// Image with link to it
[![Image reference + *link* + reference][link_variable]][link_variable]
[link_variable]: some link to image
// youtube video with legend
![*Gradient* youtube video](https://www.youtube.com/watch?v=tIpKfDc295M)
// video
![Free *video*](https://archive.org/download/BigBuckBunny_124/Content/big_buck_bunny_720p_surround.mp4)
// sound
![Free _sound_](https://www.bensound.com/bensound-music/bensound-ukulele.mp3)
When embedding youtube videos, it uses the private option in youtube-nocookie.com
.
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
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!!
-->
Custom
<style>
.quote {
background: #f9f9f90d;
border-left: 10px solid #ccc;
margin: 1.5em 10px;
padding: 1em 10px .1em;
}
</style>
[quote]:::
lorem *ipsum* $\dot x = -\nabla V $!
:::
// generates div with class=quote while rendering nabladown inside
Plugins could be added here in a custom render, check custom render section.
Line Separation
lorem ipsum
---
lorem ipsum
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.js
from 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 class
from 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 abstraction
object, described here.
For more details, you need to dig the source code :D
Creating details section using a custom section
<html>
<body></body>
<script type="module">
import { parse, buildDom } 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 DetailsRender extends Render {
/**
* (custom, context) => DomBuilder
*/
renderCustom(custom, context) {
const { key, value } = custom;
const [keyType, title] = key.split(' "');
const customDomB = super.renderCustom(custom, context);
customDomB.attr("class", keyType);
if (keyType !== "details") return customDomB;
const dialog = buildDom("details")
.appendChild(
buildDom("summary")
.inner(title.replaceAll('"', " "))
);
dialog.appendChild(customDomB)
return dialog;
}
}
const render = syntaxTree => new DetailsRender().render(syntaxTree);
const renderToString = syntaxTree => new DetailsRender()
.abstractRender(syntaxTree)
.then(domB => domB.toString({ isFormatted: true }));
const text =
`# A details example
[details "A title"]:::
\`\`\`python
def factorial(n):
return 1 if n <= 1 else n * factorial(n - 1)
\`\`\`
:::
`;
const ast = parse(text);
render(ast).then(dom => document.body.appendChild(dom));
renderToString(ast).then(console.log);
</script>
</html>
Develop Nabladown.js
Dependencies
nabladown.js
is using bun@^1.0.3
, nodejs@20.8.1
and npm@10.1.0
Building library
bun run build
Testing
Running unit tests: bun test
.
Running playground index.html
, just use bun serve
.
Influences
TODO
- Optimize html generation
- Remove unnecessary spans, divs, etc.
- Optimize fetching styles
- 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
- x Loading screen
- Render by chunks
- Show token info in playground
- Total compatibility between nodejs and browser rendering.
- Copy button doesn't work when generating html as string
- Add dialog in images (expanding images in cell phone) - Check photoswipe, glightbox
- Use local katex style instead of online one
- 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, ...
- Optimize parse to use success/fail/either (?) monad instead of exceptions(the or function)
4 months ago
4 months ago
4 months ago
4 months ago
5 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago