Generic directives/plugins syntax

Hi, everyone!

I made a plugin for markdown-it which fully implement this spec. :tada:

If you use javascript as your development language, just have a look!

If you think this work can help you, please give me a star! :smiley:


3 Likes

I don’t think it’s yet in the current official standard, but thanks @lookas for making an implementation. It will be interesting to see how well it will work in the real world. Is there a way to track if people are having issues with how your plugin is implemented that the community should account for?

It doesn’t look like anyone has explicitly discussed significant whitespace in directives. Currently the only Markdown syntax that supports significant whitespace is code blocks, and otherwise the spec says that leading whitespace should be trimmed. However, even in the OP’s examples, there is code inside the directives. I’m currently using an implementation of generic directives via the remark-directive plugin, and virtually all of the container directives I’ve implemented could benefit greatly from respecting significant whitespace.

Interestingly, I can maintain significant whitespace in my directives by also wrapping them in code fences, e.g.

:::ditaa
```
+---+
| A |
+-+-+
  |  +---+
  +->+ B +
     +---+
```
:::

While this works, it’s not particularly intuitive, and honestly it’s probably more of a bug than a feature.

The biggest concern that I have with making all directives respect significant whitespace is that there may be some directives that specifically shouldn’t respect it. Considering that all generic directives will need an implementation to be built into their renderer, though, I think it’s reasonable to assume that significant whitespace could be trimmed in the renderer in those cases.

considering that already the parser has to decide how to handle the contents of a directive, this is not something that can be handled in the renderer. IMHO, the contents of directives should always be parsed as markdown. For your use-case, using code blocks (with attributes) seems to be the right solution:

``` {.ditaa}
+---+
| A |
+-+-+
  |  +---+
  +->+ B +
     +---+
```

Reading your original post, it really feels to me like that wasn’t the intention, specially given directives such as :::eval, :::form and :::md-example given as examples in the post, which definitely contain things that cannot be interpreted as plain Markdown.

1 Like

Also, I wanted to be able to share a suggestion regarding this. Talking specifically about block directives: Is it really necessary to interpret attributes in any way in the CommonMark specification itself?

The specification currently makes no effort to actually parse the info string in code blocks, leaving it up to implementations to handle it whichever way they prefer. Would it not make sense to do the same with blocks of nested Markdown?

For example, consider the following:

~~~ class="lang-c" id="block"
int main() { return 0; }
~~~

CommonMark doesn’t specify that the info string should be interpreted as a sole language name, so if some implementation wants to parse the above as attributes to apply to the pre or code elements (if converting to HTML), it is free to do so. (And it can choose whatever syntax it wants.)

I think that could be extended for blocks of Markdown too, where the “info string” is also left to be parsed by the implementation itself:

::: aside class="ad"
Buy **our product**!
:::

The whole aside class="ad" would be parsed as an opaque string in the CommonMark spec, and the implementation could be free to interpret whichever way it wants.

For inline text spans, I think a good approach would be to simply have a []{} syntax, where the text inside [] is always parsed as inline Markdown, and the text inside {} is also an opaque string (that implementations can choose what to do with).

1 Like

Considering that already the parser has to decide how to handle the contents of a directive, this is not something that can be handled in the renderer.

Directives seem like a really great opportunity to enable any type of custom rendering, but being prescriptive about how the contents should be handled limits the opportunities to any type of custom rendering, so long as you’re good with the contents being converted to Markdown first.

To remedy this, I think that the parser should decide to leave the content of the directive alone. If the user wants the directive to be rendered to Markdown, they can rerun the parser separately.

As a more concrete example, consider a directive that renders Markdown into a specific element, like a figure with a figcaption:

:::figure
![Image](image_url)
::figcaption[A description of the *diagram* rendered ~below~ above.]
:::

Outputs:

<figure>
  <img title="Image" src="image_url">
  <figcaption>A description of the <em>diagram</em> rendered <s>below<s> above.</figcaption>
</figure>

The parser would not parse the contents of the directive, which kind of sucks. However, if the contents of the directive need to be rendered as Markdown, the renderer could run the parser again over contents of the directive. That’s maybe not ideal, but it enables so many more opportunities.

For your use-case, using code blocks (with attributes) seems to be the right solution…

In my case, I’m not trying to render a code block. The contents of the directive are actually being captured, encoded, passed to an API, then the API returns an SVG to be rendered in place of the original ASCII diagram. You can see a live example of this on my website. All of the diagrams are rendered with a generic directive, similar to the one I posted above with the code fences. The figures are also using generic directives. The implementation looks like this:

::::figure
:::ditaa
```
(diagram)
```
:::
::figcaption[A description of the diagram rendered above.]
::::

You can also check out this Github issue that I created on the remark-directive repository about enabling this option where I go into some more technical details of how I’d like to see it work.

Now, I realize that I’m talking very specifically about my personal desires for what generic directives could be, but I feel like it’s a really good example of the types of opportunities that generic directives can enable.

That doesn’t work well because of reference links and embedded HTML. It can also behave awkwardly in light of code blocks.

Example 1:

[a]: https://example

See [b].

::: foo
[b]: https://example

See [a].
:::

Example 2:

::: foo
<script>
/*
:::
*/
</script>
:::

Example 3:

::: foo

~~~ colons
:::
~~~

:::

Edit: Removed “code spans” from the list of potential problems, because it doesn’t make much sense.

Using indented code blocks instead of fenced makes this syntax a bit less noisy. It might still be unintuitive to require a code block, but custom directives already presume some author knowledge.

::: ditaa
    +---+
    | A |
    +-+-+
      |  +---+
      +->+ B +
         +---+
:::

The most likely issue with this is uninitiated authors editing an existing document and not understanding why the new diagram they added isn’t displaying properly.

Another option is to copy what Pandoc does: It’s quite established and I like its syntax. It uses class names for commands (and omits the prefixed dot outside curly braces):

[markdown]{.cmd key=val}

::: cmd {key=val}
markdown
:::

``` cmd {key=val}
text
```

<!--New syntax: attributes after a thematic break -->
--- cmd {key=val}

However, it may also make sense to keep the namespaces of commands and classes separate. That could be achieved as follows:

[markdown]{!cmd key=val}

::: !cmd {key=val}
markdown
:::

``` !cmd {key=val}
text
```

--- !cmd {key=val}

Example – figure captions:

::: !figure
![](some/image.jpg)

::: !figcaption
This is an image.
:::
:::

Example – index entries:

[]{!index key="true" md="`true`"}
`true` is a boolean value.
2 Likes

The exclamation mark syntax [content]{!cmd key=val} looks good, and doing something that’s already in use is a great idea.

How are key-value pairs separated? And is it possible to have keyless (i.e. numerical, ) values (e.g. I was working the other day on a Wikidata linking synax that in this scenario would look something like [D. Adams]{!wd Q42}).

1 Like

Oh, looking at the Pandoc docs, it looks like the attributes are space-separated and optionally quoted, I guess following the same rules as XML attributes. So it wouldn’t make sense to have a keyless value; such a thing would be a valueless key (and permitted).

1 Like