MarkdownMesh
is a not-for-profit project whose goals include:- Introducing people to the Markdown language and various ways to use it
- Creating tools for Markdown that are simple, carefully-scoped, secure, extensible, and blazingly fast
- Being a little bit of extra wind in people's sails as they do good stuff
Command-line tool
mm live-preview md/
mm live-preview md/
The live-preview
action starts live preview mode for the docset in the specified dir.
--allow-external-connections
--allow-external-connections
By default, live preview mode only accepts network connections from the same computer.
If you want to see the preview page from another computer on your local network (e.g. a phone or tablet connected to the same Wi-Fi), you'll need to specify --allow-external-connections
, which will accept connections from any computer. In networking software terms, the server will listen on 0.0.0.0
instead of 127.0.0.1
.
This will accept connections from any computer that can communicate with yours on the network. But with most network routers / internet connections, you're behind a NAT and other computers probably cannot talk to yours unless they're on the same LAN.
$ mm live-preview md/ --tls … --allow-external-connections
Network: --allow-external-connections
TLS: …
Auth:
- Authentication mode: token-in-URL
- Authorization scope: all pages in this docset
→ https://192.168.1.293:8000/preview?token=9f5be4b…
--tls
--tls
TODO write this up
You may want to define a command-line alias such as this:
alias lp='mm live-preview --tls … --allow-external-connections'
zip -0 -r - md/ | mm zip2zip > www-html.zip
zip -0 -r - md/ | mm zip2zip > www-html.zip
This action is a function from a .zip
archive to a .zip
archive:
- Input: a
.zip
whose file tree contains a directory of docset source (.md
+ other files) - Output: a
.zip
of rendered HTML (+ assets), suitable for deploying to a static web server
To emit a .zip
of a directory foo
in the current working directory:
zip -0 -r - foo
To emit a .zip
of a specific commit abcdef123
of a git repository:
git archive --format=zip -0 abcdef123
--docset-root
--docset-root
In most contexts, you don't need this option.
Unless you've got some sort of multiple-projects-in-one-folder-with-relative-links-between-the-project-dirs scenario going on, you probably don't have to read about this.
Here's the logic for finding the root of the docset within the zip:
- If there are zero
.MarkdownMesh.json
files in the input zip, thendocset root
defaults toroot of the zip
. - If there is exactly one, then
docset root
defaults tothe dir which contains the config JSON file
. - If there are multiple, then
zip2zip
will error unless you specify the--docset-root
.
For example, suppose you have this file layout:
some-monorepo/
foo/
site/
.MarkdownMesh.json
...
src/
...
bar/
site/
.MarkdownMesh.json
...
src/
...
...and that you want to pass the entire monorepo as a zip file to zip2zip
.
Possible reasons for this include:
- pages within
foo/site/
want to use assets that live outside offoo/
- pages within
foo/site/
want to import fragments of code (to CodeBlock elements) frombar/src/
- pages within
foo/site/
want to import fragments of Markdown (as parts of the doc) frombar/site/
- pages within
foo/site/
want to link to a sections of a pages withinbar/site/
using a relative Markdown link like[](../bar/site/….md#…)
that gets converted to a URL based in part on theurl_root
inbar/site/
's.MarkdownMesh
config JSON.
In this scenario, you'll need to specify --docset-root foo/site/
echo $'## Foo\n...' | mm ast-json > ast.json
{"type": "Document", "children": [
{"type": "Section", "level": 2, "children": [
{"type": "Heading", "level": 2, "children": [
{"type": "Text", "value": "Foo", "children": []}
]},
{"type": "Paragraph", "children": [
{"type": "Text", "value": "...", "children": []}
]}
]}
]}
echo $'## Foo\n...' | mm ast > ast.txt
Document
╷
╰╴Section { level: 2 }
╷
├╴Heading ── Text { level: 2 }, "Foo"
│
╰╴Paragraph ── Text {}, "..."
Other actions
echo '[Foo](#foo)' | mm html-fragment
echo '[Foo](#foo)' | mm html-fragment
<p><a href="#foo">Foo</a></p>
echo '[Foo](#foo)' | mm html-page
echo '[Foo](#foo)' | mm html-page
<!DOCTYPE html>
<html>
...the details of this HTML depend on your config...
<p><a href="#foo">Foo</a></p>
...
</html>
Sandbox model (WASM sandbox + explicit authz for reqs / subprocs beyond that)
This tool consists of a WASM blob plus a simple wrapper.
Plugins only run within that WASM sandbox. They can request external things, but those requests are mediated by the wrapper, outside of the WASM VM, and they require explicit and carefully-scoped authorizations.
Examples:
-
"Plugin
foo
may make HTTP requests, but:- only to the host
localhost:8000
- only
GET
the pathname/asdf
- only send querystring params whose keys are in
{ id }
- only to the host
-
"Plugin
bar
may run subprocesses, but: -
Plugin
asdf
may run a blob of web stuff inside a sandboxed / offline headless web browser instance and obtain the result
Text editor extension
Initially, this is for the text editor family known as Visual Studio Code™ / VSCodium / Code - OSS.
This optional extension helps mm live-preview
to :
- Show the document that you're currently editing / viewing
- Scroll to the Section / CodeBlock / etc that you're currently editing
This extension also lets plugins propagate changes in the other direction, e.g. making by edits to your CodeBlock elements' code based on interaction with the preview.
Live preview mode
Live preview mode is like the preview pane you can see when editing Markdown in most IDEs, but it can be accessed from any web browser, including from a web browser on an external device.
Markdown
Markdown is a markup language for documents.
Markdown files (extension: .md
) consist of plain text, with certain symbols indicating various things beyond plain text (list items, links, footnotes, images, etc).
Markdown syntax is similar to the formatting symbols in most workplace chat systems
(- list item
, `code`
, _italic/em_
, etc), but more extensive.
Select any of these syntax tree elements for an example and more details:
|
|
|
JSON Schema
|
|
|
|
Note: how you configure the plugins affects whether any
<section> elements appear in the HTML, and for which Heading level(s) they appear.
|
JSON Schema
|
|
|
|
Note: how you configure the plugins affects whether any
<section> elements appear in the HTML, and for which Heading level(s) they appear.
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
Note: by default, comments do not get included in the HTML. You can override that in your config.
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
Note:
HTML supports having multiple GfM-Markdown tables don't provide a way to express that, but Plugins can create them in the AST based on whatever (e.g. based on blank rows between non-blank rows). |
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
|
|
|
JSON Schema
|
Creating plugins
Plugin API Overview
Stages / hooks
doc_on_ast_draft
doc_on_ast_locked
doc_on_html_draft
doc_on_html_locked
docset_on_html_drafts
docset_on_assets_ready
docset_on_htmls_locked
Docset
.config
.filebytes_with(…)
.docs_with(…)
.doc_with(…)
.add_file(…)
.asset_via_subproc(…)
.asset_via_http(…)
.asset_via_browser(…)
AssetRef
.as_image(…)
Doc
(this a subclass of MarkdownElement)
doc.url_full
doc.url_pathname
doc.path_in_docset
doc.html_tree
doc.html # read-only
doc.docset
doc.add_css(…)
doc.add_js(…)
doc.add_head_tag(…)
MarkdownElement
.isa(T.CodeBlock)
.find_all(…)
.find_first(…) # or None
.find_last(…) # or None
.find_sole(…)
# ^ Error iff not onee
.find_images(…)
.find_code_blocks(…)
…
.parent
.children
.siblings_after
.siblings_before
.preceding_sibling_or_none
.delete()
.replace_with(nodes)
.set_html_attr(k, v)
.props
# (for the current plugin)
When running in live preview mode, if you modify your plugin's source code file and then save that file, the file system event will be detected and mm live-preview
can refresh its preview pages, with those docs being rendered using your updated plugin code.
You will be able to use either Zig or Python to create a plugin.
With Zig, your plugin gets compiled and ends up as WASM.
With Python, the plugin doesn't need to be compiled. It gets run within a compiled-to-WASM Python interpreter with a reduced Python standard library. (re: sandboxing model)
Plugins ✨
List of plugins
Plugin | Example | Result |
---|---|---|
e.g. |
||
import-code | !![](../src/code.py#foo) |
→ Inline a copy of the function definition foo from code.py as a CodeBlock with { lang: "py" }
|
fe-components | <SomeWidget … /> |
→ HTML / CSS, rendered at build time from that component/template (.html / .tsx / .svelte ).
Later: also support client-side Preact, React, or Svelte |
infd-set-brackets |
The `{red,green,blue}`:sb LEDs
|
(HTML/CSS T.B.D., but will support screen reading and plaintext copy-pasting as "The red, green, and blue LEDs")
|
...for language extensions (beyond CommonMark) |
||
md-math |
$…$ or $`…`$ for inline
$$…$$ for block
|
→ CodeInline or CodeBlock with { lang: "math" }
→ HTML/CSS, rendered at build time by KaTeX |
md-footnotes |
[^Foo] / [^Foo]: ...
|
→ FootnoteRef / FootnoteDef elements, HTML |
md-tables |
See the GfM spec § 4.10
|
Table, TableHead, TableBodys, TableRows, TableCells |
md-tabnavigator | TabNavigator / Tab : e.g. the Markdown section of this page has one Tab for each element. There are many ways to style a TabNavigator, and most do not require any JavaScript. | |
md-layout |
LayoutRow / LayoutColumn
→ <div> s + CSS
|
|
md-small |
<small>…</small>
|
→ Small
→ <small>
|
md-codeinline-info-string |
`{}`:json
`{}`:"foo bar"
|
→ CodeInline with { info_string: "json" }
→ CodeInline with { info_string: "foo bar" }
|
md-import ✨ |
!![](…)
!![](…):…
!![](…):"… …"
|
This plugin's job is to turn that syntax into an Import element. To have that element result in any actual importing, use the ...for importing plugins (in the next plugin group) |
...for importing ✨ |
||
import-markdown | !![](../foo.md#bar):firstP |
→ Inline a copy of the first paragraph of section Bar from foo.md
|
import-code | !![](../src/code.py#foo) |
→ Inline a copy of the function definition foo from code.py as a CodeBlock with { lang: "py" }
|
import-usda-from-usdz | !![](../x.usdz#/a/b):usda |
→ CodeBlock with { lang: "usda" }
(of the OpenUSD prim at path /a/b in x.usdz )
|
...for the world wide web |
||
www-sitemap-xml | → /sitemap.xml file for search engines, optionally including the SHA-256 of each page. | |
www-validate-internal-links |
Validate all internal links, confirming that:
|
|
www-validate-external-links |
Like www-validate-internal-links, but for external links:
|
|
...for frontend stuff (CSS/JS/assets/etc) |
||
fe-page-template | ||
fe-assets |
|
|
fe-css-from-style-elements |
If you include a <style> element in your .md , e.g. at the end of the file, this plugin will include it in the main CSS for the page (which could be inline or as an asset, depending on config).
|
|
...for images |
||
img-iiif |
![](./….png#IIIF/…/…/…/….jpg)
|
Using some external IIIF tool/server, render image clips using IIIF Image API syntax (for region, size, rotation, quality, and format). |
img-thumbnails |
![](foo.png):t
|
Create a thumbnail (with custom profile "t"), using img-iiif Have the <img> link to the full image
You define profiles in your config for this plugin. |
...for code |
||
code-highlighting | ||
code-ast |
```py :ast-side-by-side …
|
Show a CodeBlock's code side-by-side with its AST, optionally with some subsets highlighted.
At first, this will only support { py , md , ts , zig }
|
...for diagrams |
||
diagrams-mermaid | ||
diagrams-excalidraw | ||
diagrams-jsoncanvas | ||
diagrams-graphviz | ||
...for music |
||
music-lilypond | ||
...for information design |
||
infd-set-brackets |
The `{red,green,blue}`:sb LEDs
|
(HTML/CSS T.B.D., but will support screen reading and plaintext copy-pasting as "The red, green, and blue LEDs")
|
infd-style-links-by-category | Link elements |
e.g. include an icon before some link types:
|
Project status
Not yet published.
Project itinerary
- Not yet published
- Draft WASM blob (+ wrapper code) available at no cost
- FLOSS, v1.0
Acknowledgements
The command-line tool builds upon parts of the following FLOSS works:
This page
does not use
any JavaScript.