An rvmark file is just a bullet point tree in plain text. Every point is called a node.
- Root node.
- Child node.
- Grandchild node.
- Other child node.
Nodes begin with an indent and a dash. (Asterisks also work.) The remaining text is called the label.
Child nodes are created with indents. This indent can be as large or small as you want, but must be the same for all children of a node.
- Root node.
- Child node.
- Grandchild node.
- Other child node.
- Other grandchild node.
- Great grandchild node.
You could write rvmark this way. (Please don't.) It's strongly recommended to use indents of two spaces per level.
A node without an indent is called a root node. You can have as many root nodes in an rvmark file as you want, but only the first root node is displayed by the renderer.
- Displayed by the renderer.
- Also displayed.
- Not displayed.
Effectively, the first root node is the document entry point, and the other root nodes are additional data meant to be transcluded. (More on this later.)
$\sqrt{x^2}$renders as \sqrt{x^2}. To fully understand how to write a node, there are two last elements you need to be familiar with:
- {flag; key: value} [Tag] [Other tag] Label text.
{flag; key: value} is called the attribute block.
[Tag] [Other tag] are called the tags.
Attributes can dramatically change the appearance and functionality of a node. They are separated by semicolons ;.
Some attributes take a value, others are stand-alone flags.
Attribute blocks may also contain other elements called declarations. You'll learn about these coming up.
Tags are by default purely visual, but can be configured to also change the appearance and functionality of a node.
Tags always come at the beginning of a node after the attribute block.
The visible tag displayed by the renderer is sometimes called the tag chip.
The entire fun of rvmark is that you can transclude tree sections from one place to another.
- {#root} Root node.
- Child node.
- {=> #other-child}
- {#other-child} Other child node.
- {=> #root}
- Whoa, I'm a sibling of my own grandfather.
{#other-child} is an ID declaration setting the ID #other-child for that node.
{=> #other-child} is a transclusion declaration placing the node with ID #other-child in its location
IDs are defined in the attribute block using a declaration starting with #.
You must only set one ID per node.
They are scoped to the rvmark document. You are free to reuse them across different documents within the same site.
Users are only offered permalinks by the UI on nodes with IDs. Make sure to give important nodes an ID!
With the {=> #id} declaration, you transclude another part of the tree wherever you like.
Transclusions can be recursive. Technically this makes the tree a directed graph. Since the tree is lazy-expanded, recursion does not cause infinite regress.
Transclusion has two modes, switched by whether the transcluding node has label text.
If there is no label text, the label text of the transcluded node is copied. In this case, the transcluding node's tags and attributes are prepended to those of the transcluded node. The transcluding node's attributes win against conflicting transcluded attributes.
If there is label text, this will replace that of the transcluded node, and tags and attributes will not be copied.
Children of transcluding nodes are dropped.
Instead of targeting the children of a node for transclusion,
you can transclude a node itself as a child with the ^ sigil: {=> ^#id};
Occasionally it's useful to transclude the children of multiple nodes into one. This can be done using the multiple-transclusion syntax.
- {=> #a, *, #b} Parent.
- Second child.
- {#a}
- First child.
- {#b}
- Third child.
If instead of one node address, you enter a comma-separated list of multiple addresses, the children of these nodes get concatenated in the listed order.
The children of the node itself can be accessed using an asterisk *.
Transcluding a multiple-transclusion by address will transclude the calculated children. Naturally, a multiple-transclusion cannot transclude itself. Infinite regress is prevented by the renderer through cycle detection.
Multiple-transclusion will always drop the labels, attributes, and tags of the transcluded nodes. (It wouldn't be clear which ones to copy.)
At the start of an rvmark document, you can define attributes for tags.
[Tag name {attribute; another-attribute}]
[Another tag {yet-another-attribute}]
These definitions come before the first node of the document.
The node.* attribute namespace allows you to add an attribute to every node a tag is applied to.
For example, the definition [Exclamation {node.bullet: /icons/exclamation.svg}]
could give every node with the [Exclamation] tag ! for its bullet.
It's conventional to use place a period . before the tag name of tags with
such additional behavior. This will also hide the tag chip (though you can override this).
The look and behavior of text inside of labels can be customized further with the help of span attributes. These have a syntax similar to regular markdown links, but with curly brackets rather than parentheses.
Markdown hyperlink: [link text](https://www.example.org/)
rvmark span attribute block: [span text]{flag-attr; key-attr: "example value"}
You can see that the syntax is identical to the node attribute blocks we discussed earlier in this tutorial.
Many span attributes are self-explicatory. The rest of this section discusses some of the more novel and subtle things you can do with span attributes.
Span attributes should work identically in both regular text nodes, table nodes, and block nodes.
Toggle nodes solve a basic problem: How can you put links to other nodes inside text? The solution is to, rather than links, have expandable toggles that switch out the children below the node.
These look like this. If you have multiple of them, any others close when opening a new one.
- This is a [text node]{=> #first-toggle} with multiple [toggles]{=> #second-toggle}.
- {#first-toggle}
- You've expanded the term "text node".
- {#second-toggle}
- You've expanded the word "toggles".
Listbox nodes are a different kind of toggle and are navigable with the arrow keys. These toggles trigger as soon as they are selected, giving a tab-like user experience. Try it yourself.
- This is a [listbox]{option; => #first-option} with multiple [options]{option; => #second-option}.
- {#first-option}
- You've selected the word "listbox".
- {#second-option}
- You've selected the word "option".
One possible application of rvmark is to make choose-your-own-adventure stories. When writing such stories, you may want to return to the same crossroads with new options unlocked.
For this reason rvmark includes a basic state machine. Think of this as a device that stores a small amount of custom information per node. This information is stored per rendered node, not per source node; that way the same source node could render differently depending on how and when a user accesses it. Example: a gate on a crossroads is now open, now closed.
The total custom information per node is called state,
each little bit of information is called a variable.
In this section you will learn how to use variables.
You can recognize variables by the & sigil: &var.
A node's entire state is stored in the query string of its permalink. This way you can easily bookmark and share a position in the tree, even if it has complex state.
The simplest way to use variables is with the let keyword and show-when attribute.
With let you declare a variable.
With show-when you make a node's visibility depend on its value.
- Root node
- {let &var = "value"} Declares the variable `&var` with value `value`
- {=> #test}
- Does not do this.
- {=> #test}
- {#test} Did the assignment happen?
- {show-when: &var == "value"} It did!
- {show-when: &var != "value"} It did not.
&var with value valueVariables can be used as flags. This means that all information the variable communicates, it communicates by doing nothing more than existing. The value doesn't matter.
To make this easier, you can write {let &var} without any value,
and check it similarly with {show-when: &var}.
To remove a variable from a subtree, use the remove keyword.
- {#test} Root node
- {let &var} This node adds the `&var` flag.
- {=> #test}
- {remove &var} This node removes the `&var` flag.
- {=> #test}
- {show-when: &var} The `&var` flag is present.
- {show-when: !&var} The `&var` flag is not present.
Variable declarations only carry information down into the subtree. You might want to send information up into the parent branch. This is possible via variable assignments.
An assignment takes a declared variable from the parent branch and gives it a new value. All nodes that read the variable are listening for any changes, and will respond as soon as the variable is reassigned.
Variable assignments are written like variable declarations,
but with the set keyword instead of let.
- {let &var = "A"} Declare `&var` with value `A`.
- {set &var = "B"} Press on this node to assign the value `B` instead.
- {set &var = "C"} Press on this node to assign the value `C` instead.
- {show-when: &var == "A"} The variable now has value `A`.
- {show-when: &var == "B"} The variable now has value `B`.
- {show-when: &var == "C"} The variable now has value `C`.
If you "set" a variable that doesn't exist yet, the program will automatically create it, but, of course, no information will be communicated to the parent tree.
&var with value A.B instead.C instead.A.B.C.{let &var = 1} is equivalent to on-spawn: let &var = 1.{remove &var} is equivalent to on-spawn: remove &var.{set &var = 1} is equivalent to on-action: set &var = 1.If variables persisted endlessly into a subtree, things would get messy for deep trees. Different parts of the tree would have control over each other in ways they really shouldn't.
This is why variables get deleted when you transclude
into another document.
You can override this behavior with the pass attribute.
Suppose you want another document to read whatever value
you've given to the variables &var, &foo, and &bar.
This can be done the following way:
- {=> ./other-document.rvmark#some-node; pass: &var, &foo, &bar}
It's no more complicated than that.
Note, however, that the other document can only read these variables, not set new values. Setting a new values on the receiving end will instead "let" a new variable on that node.
To learn how change this behavior, see the next section.
Suppose you not only want a transcluded document to read a variable, but also to write to it ("set" it).
This can be done the following way:
- {=> ./other-document.rvmark#some-node; pass: &var rw, &foo rw, &bar rw}
The letters rw stand for read-write. It means the variable is passed
with reading and writing permission.
You can also give only writing permission.
- {=> ./other-document.rvmark#some-node; pass: &var w, &foo w, &bar w}
In this case, show-when will think these variables do not exist,
but set will write to them just fine, and change their value
inside the original document.
Another feature that may sometimes be useful is renaming.
This can be done the following way:
- {=> ./other-document.rvmark#some-node; pass: &new-name=&old-name}
Here, the transcluded document will receive the variable &old-name
as if it were called &new-name.
Let me horribly undersell the idea of sidepanels: it's a way to show PowerPoint slides together with your rvmark text.
More abstractly, it is a way to leverage the psychology of shared attention. A powerful way people learn is by inspecting a shared object together with a teacher. It'd be a shame if rvmark couldn't use this.
Sidepanels are little exhibits containing HTML iframes. In this section you will learn something about how they work.
openopen: trueopen.open: alwaysopen: neveropen: falseaction: linkaction: sidepanelaction: noneli: <int>{li} to number nodes.{li}.{li: 15}.{li} continues numbering upward.bullet: <address>bullet-alt: <str>bullet-open: <address>bullet-spinslistboxlistbox-volatilesrc: <address><address>.alt: <str>width: <css-width>align: leftalign: centeralign: rightdark-mode: invertdark-mode: backgroundheight: <css-height>ratio: <css-ratio>iframe-pass: <pass-spec>start: <seconds>end: <seconds>cols: <grid-cols>draft--include-drafts flag on the build-tool will build the website with these drafts included.id: <str>#<str>.transclude: <address>=> <address>.type: <nodetype>= <nodetype>.class: <css-class>.<css-class>.sidepanel: <address><address> in sidepanel.searchableshow-when: <condition><condition> is satisfied.on-spawn: <expr>on-select: <expr>on-deselect: <expr>on-focus: <expr>on-blur: <expr>on-action: <expr>on-expand: <expr>on-collapse: <expr>on-destroy: <expr>on-no-option-select: <expr>pass: <pass spec>children-pass: <pass spec>sidepanel-pass: <pass spec>#<str>value.=> <address>value in the place of the node.= <nodetype>value..<css-class>value to be a CSS class for the node content.meta.*{= text}{= hr}{= gap}{= block}Though rvmark encourages a nodes-are-phrases idiom, it's sometimes nice to relax and just have paragraphs. (This guide, being a soft introduction, makes heavy use of them.)
Longer sections can be included with the {= block} nodetype.
- {= block}
~~~
Paragraph text.
~~~
The label text on a node with block content must always be empty.
Block delimiters work the same as code-block fences in Commonmark.
This means you can use either ~~~ or ``` as a fence.
We advice using ~~~ because ``` is idiomatic in Markdown
and likely to be used in the section content itself.
It's also possible to make fences longer and use ~~~~ or ~~~~~, etc.
The parser will look for the next fence of the same length or longer.
This way you'll never run into a situation where you can't write something
due to limitations in the markup language.
- {= block} ./some-file.md will use the contents of the some-file.md file in the same directory as its block contents.- {= block} ./some-file.md##some-heading will only use the contents under the ## Some heading section as block content. {= image}The {= image} nodetype allows you to embed images as nodes.
- {= image} ./some-image.jpg
This embeds the image named some-image.jpg from the same directory as the rvmark file.
{= iframe}The {= iframe} nodetype allows you to embed HTML pages as nodes.
- {= iframe} ./some-page.html
This embeds the HTML page named some-page.html from the same directory as the rvmark file.
{= video}The {= video} nodetype allows you to embed videos as nodes.
- {= video} ./some-video.mp4
This embeds the video named some-page.mp4 from the same directory as the rvmark file.
Also supported are certain online video platforms, such as YouTube video and playlist URLs, as well as bare YouTube video and playlist IDs.
{= table}{= tr} children.{= tr}{= table}.optionselectedtoggletransclude: <address>=> <address>.show-when: <condition>href: <url>img: <address>ruby: <str>class: <css-class>.<css-class>.style: <css>role: <aria-role>on-action: <expr>on-select: <expr>on-deselect: <expr>on-focus: <expr>on-blur: <expr>on-on: <expr>on-off: <expr>=> value.valuelet …on-on: let … for toggle nodes, and on-select: let … for option nodes.set …on-on: set … for toggle nodes, and on-select: set … for option nodes.remove …on-on: delete … for toggle nodes, and on-select: delete … for option nodes.internal. and that lack a label.color: <css-color>--tag-color CSS variable on the tag chip, changing its color.label: <string>tip: <string>title attribute) on the tag chip.href: <url>class: <css-class>.node-content div of nodes it's applied to. Alias for .value..<css-class>value to be a CSS class for the node content of nodes it is applied to.meta.*node.*