2.0.30 • Published 4 months ago

nabladown.js v2.0.30

Weekly downloads
-
License
Apache-2.0
Repository
-
Last release
4 months ago

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

  1. QuickStart
  2. Language cheat sheet
  3. Try it
  4. Advanced
  5. Develop nabladown.js
  6. Influences
  7. TODO

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:

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

  1. Optimize html generation
    • Remove unnecessary spans, divs, etc.
  2. Optimize fetching styles
  3. Add paragraphs to lists as here and footnotes
  4. Add inline attributes to links, equations, custom... as Quatro and this or this
  5. Add easy tables, check AsciiDoc tables and Orgmode tables
  6. Think about escaping characters, like `, <, *, >, _
  7. Optimize Playground - x Loading screen - Render by chunks
    • Show token info in playground
  8. Total compatibility between nodejs and browser rendering.
    • Copy button doesn't work when generating html as string
  9. Add dialog in images (expanding images in cell phone) - Check photoswipe, glightbox
  10. Use local katex style instead of online one
  11. Multiple styles in code rendering
  12. Add metadata space such Quatro
  13. Change some recursions to linear recursions or just loops (?)
    • Apply parseAnyBut loop to parseDocument, parseExpressions, ...
  14. Optimize parse to use success/fail/either (?) monad instead of exceptions(the or function)
2.0.29

4 months ago

2.0.30

4 months ago

2.0.28

4 months ago

2.0.27

4 months ago

2.0.26

5 months ago

2.0.25

5 months ago

2.0.24

6 months ago

2.0.23

6 months ago

2.0.22

6 months ago

2.0.21

6 months ago

2.0.20

6 months ago

2.0.19

7 months ago

2.0.18

7 months ago

2.0.17

7 months ago

2.0.16

7 months ago

2.0.15

7 months ago

2.0.14

7 months ago

1.0.2

7 months ago

2.0.13

7 months ago

2.0.12

7 months ago

2.0.11

7 months ago

2.0.10

7 months ago

2.0.8

7 months ago

2.0.7

7 months ago

2.0.6

7 months ago

2.0.5

7 months ago

2.0.4

7 months ago

2.0.3

7 months ago

2.0.2

7 months ago

2.0.1

7 months ago

2.0.0

7 months ago