2 Custom elements
You can use Racket code to introduce new elements to the document’s structure.
A custom element is any xexpr? that begins with a symbol. A custom element may optionally have a set of attributes, which is a list of key/value pairs that appears as the second item in the list. The keys must be symbols and the values must be strings, or an exception is raised.
Here is an example of a function that produces a custom abbreviation element with a term attribute:
#lang punct •(define (a term . elems) `(abbrevation [[term ,term]] ,@elems)) Writing documentation in Javascript? •a["Laugh out loud"]{LOL}.
Produces:
'#s(document #hasheq((here-path . "7-unsaved-editor")) ((paragraph "Writing documentation in Javascript? " (abbrevation ((term "Laugh out loud")) "LOL") ".")) ())
2.1 Inline and Block Content
Any custom elements you introduce need to play nicely with CommonMark’s Document structure, in particular its distinction between Blocks and Inline content.
Inline content must be contained in a block and can only contain other inline content. If found on a line by itself, inline content will be automatically wrapped in a paragraph block element. Italics and links are examples of inline content.
Blocks can contain other blocks as well as inline content, and will not be auto-wrapped in paragraph elements. Paragraphs and headings are examples of block content.
By default, Punct will treat custom elements as inline content.
If you want a custom element to count as a block (that is, to avoid having it auto-wrapped inside a paragraph element), you must give it a 'block attribute with a value of either "root" or "single":
"root" should be used for blocks that might contain other block elements. Limitations: "root"-type blocks cannot be contained inside Markdown-created flows (such as block quotations notated using >); if found inside such a flow, they will “escape” out to the root level of the document.
"single" should be used for block elements that might need to be contained within Markdown-created flows. Limitations: "single"-type blocks must appear on their own line or lines in order to be counted as blocks.
If that seems complicated, think of it this way: there are three kinds of flows that you can notate with Markdown: block quotes, list items, and footnote definitions. If your custom block element might appear as a direct child of any of those three Markdown notations, you should probably start by giving it the '(block "single") attribute.
You’ll never get an error for using the wrong 'block type on your custom elements; you’ll just get unexpected results in the structure of your document.
Under the hood: The two types of blocks above correspond to two methods Punct uses to trick the CommonMark parser into treating custom elements as blocks. With "root"-type blocks, Punct inserts extra line breaks (which is what causes these blocks to “escape” out of Markdown blockquotes to the document’s root level, just as it would if you typed two linebreaks in your source). With "single"-type blocks, Punct allows CommonMark to wrap the element in a paragraph, then looks for any paragraph that contains only a "single"-type block and “shucks” them out of their containing paragraphs. The need for such tricks comes from a design decision to use the commonmark package exactly as published, without forking or customizing it in any way.
2.2 Rendering custom elements
When rendering your document to a specific output format (such as HTML) you’ll want to provide a fallback procedure to the renderer that can convert those elements into that specific format.
Your fallback function will be given three arguments: the tag, a list of attributes, and a list of sub-elements found inside your element. The elements will already have been fully processed by Punct.
Here’s an example pair of functions for rendering documents containing the custom abbreviation element (from the examples above) into HTML:
(define (custom-html tag attrs elems) (match `(,tag ,attrs) [`(abbreviation [[term ,term]]) `(abbr [[title ,term]] ,@elems)])) (define (my-html-renderer source-path) (doc->html (get-doc source-path) custom-html))