markdown: Proposal: a new general purpose Block extension

I haven’t solidified the proposal yet, but I am envisioning something like a fenced code block (except its not for code and would use different delimitators) which might support two different block types:

  1. Element Block: The user would define the HTML element (tag) and any attributes. This would be simpler to deal with (read, write and parse) that md_in_html as it avoids lots of HTML tags in your Markdown.
  2. Templated Block: The user would define the block ‘type’ and any attributes. So long as a template exists for the ‘type’, then the content is inserted into the template after being parsed as Markdown.

Therefore, if the user provided div (for an element block), the content is parsed as Markdown and then wrapped in a div element with any additional attributes being set on the div. However, if the user provides a ‘type’ of admonition, then the admonition template is used. Presumably the template would expect a set of attributes (including the admonition type), a title and a body and insert those values into the HTML template. Other templates might accept other options.

This would also allow users to use the CSS provided by whichever CSS framework they are using. For example, the MkDocs default theme is using Bootstrap, which provides it own set of alerts. However, MkDocs doesn’t use them, but instead also provides CSS for Rst style admonitions because that is what the admonition extension expects. With a new block extension, a Bootstrap alert template could allow Bootstrap’s CSS to be used along with all of Bootstrap’s alert features (icons, dismissal, etc) removing the need for the MkDocs theme to also include the Rst based CSS.

A few additional things to note:

I would prefer to not add any new extensions to the core library. So I would expect this to be developed in a separate repo. However, I mention it here because it could effect how we proceed with #1174. Also, I would appreciate any feedback on the idea and/or input on a possible syntax proposal.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 84 (22 by maintainers)

Most upvoted comments

@matchavez I think that syntax has zero ideological overlap with this particular thread. The GitHub syntax lets you achieve only these 5 exact blocks with absolutely zero leeway for extensibility (any deviation makes the syntax unrecognized by GitHub).

That said, I will advertise a thread where there is interest in making this work despite these limitations, not as a general syntax

Well, it may be time to update all of this.

GitHub has implemented their Admonition style, and it’s a Blockquote fallback.

Maybe implement the new style for specific admonitions (presumed), and then leave the !!! for an unstructured block as suggested, that continues to render as it does.

>[!NOTE] !!! - follow the Material for MkDocs style for custom blocks ??? - use as a “Spoiler” indicator, with the same unstructured block hidden by default.

Here’s the current supported array:

[!NOTE]
Highlights information that users should take into account, even when skimming.

[!TIP] Optional information to help a user be more successful.

[!IMPORTANT]
Crucial information necessary for users to succeed.

[!WARNING]
Critical content demanding immediate user attention due to potential risks.

[!CAUTION] Negative potential consequences of an action.

> [!NOTE]  
> Highlights information that users should take into account, even when skimming.

> [!TIP]
> Optional information to help a user be more successful.

> [!IMPORTANT]  
> Crucial information necessary for users to succeed.

> [!WARNING]  
> Critical content demanding immediate user attention due to potential risks.

> [!CAUTION]
> Negative potential consequences of an action.

@squidfunk I am purposely not focusing on the API right now as I want to nail down syntax first. If you really want to jump into the API, I think the Admonition block is a good template to get started. I would just ignore the on_register and on_parse events as those are used to handle dynamic type generation for the block and are more advanced.

At the very least you need an on_create event.

  • NAME must be a unique name to avoid conflicts with other registered blocks.
  • ARGUMENTS is used to specify what some may think of as the “title” input: /// block | argument.
  • OPTIONS specifies block-specific inputs that can be given via a YAML block in the header. Regardless of the block, the attributes option is always special and allows adding extra classes, ID, or whatever else is desired, so that option name is kind of reserved.
  • CONFIG is used to specify any global config settings for the block. These are done when registering your block and only then.

If you have general feedback, please provide it at the alpha’s discussion topic here: https://github.com/facelessuser/pymdown-extensions/discussions/1868

If you have additional questions related to the API, please create a separate discussion thread on the repo. I’m happy to answer any additional questions, but since API is not the primary focus of the first alpha, and the API could change some before I document the API for a future alpha, that is not my focus, and I’d like to keep the main focus, at least in the alpha release topic, on nailing down the syntax.

I’ll go a little further to say, the intent is not to invalidate work others have done, and if an existing generic block satisfies your needs, I’d say that maybe what we are doing here won’t make a difference to you.

With that said, I’ve tried to put a lot of thought into the generic blocks we are currently implementing.

  • They won’t require indentation which many have complained about before.
  • They should be flexible allowing users to specify if they should treat the content as a block, span, or even raw text which they should preserve through the markdown process.
  • They should allow you to tap into various events to make things a bit easier. Do you need to process the content after the entire content under a block has been parsed? There’s an on_end event for that. Does your generic block need to parse the content but then run a treeproessor on it later, maybe after some other extension? You should be able to use the on_register event to add a tree processor in conjunction.

While there are other solutions that may be sufficient for others, I am hoping that some will find what we are trying to do here a welcome addition. I’m trying to involve users of Python Markdown to give their input to try and make this a useful solution moving forward. I am hoping to have a pre-release soon to start allowing people to try it out. It has definitely been more involved to write this extension than what I initial thought, so work near the end has slowed as other obligations in life have crept in, but I think we are very soon reaching an alpha release. Hopefully, with feedback, we can polish any rough edges and get something useful out.

Just in case you don’t know it, there is an extension, Custom Blocks, quite similar to what you are proposing here but in my opinion (skewed as i am the author) it uses a more simpler syntax (no need for a closing tag, no need for a separator between the block name and the parameters… It also has a more flexible mapping of parameters to python function parameters which makes defining custom blocks quite easy.

::: blocktype value1 "long value2" param3=value3 param4="long value4"
    Indented content which may contain any other blocks as long as they are **indented**.

By default it generates:

<div class="blocktype value1 long-value2" param3="value3" param4="long value4">
<p>Indented content which may contain any other blocks as long as they are <b>indented</b>.</p>
</div>

That is:

  • the type of the block as class
  • keyless values as classes
  • keyworded values as attributes
  • indented content reparsed as markdown

But you can customize it with a simple python function like this one:

def blocktype(ctx, param1, param2, param3, param4='default'):
     return E('.blocktype', dict(param1=param1), E('span.title', param2), ctx.parse(ctx.content))

To generate:

<div class="blocktype" param1="value1">
<div class="title">long value2</div>
<p>Indented content which may contain any other blocks as long as they are <b>indented</b>.</p>
</div>
  • keyworded attributes are assigned first to parameters by name
  • keyless values are then assigned by position to the rest of the parameters
  • the indented content is passed as ctx.content, and ctx.parser generates html with markdown. Depending on your block type you might not do that (source code, dot diagrams…)

I am still moving forward with this, but I’m finding that I like the requirement less and less which requires the content block to have a new line before it.

/// test

content
///

I really just want to make compact blocks at times:

/// test
content
///

I think compact blocks are a bit more readable when compact (when possible) especially when you have a number of blocks or nested blocks.

If people feel very strongly with hate for YAML fences (---), maybe I can add a general option to make them optional (or not optional depending on what the default is).

I didn’t think it would bother me as much as it does, but as time has gone on, I think it really does bother me 😅.

I think we might move towards this syntax:

/// name | string

content
///

I wanted to use ::: as it aligned (at least in spirit) to what Myst and Pandoc use for generic blocks. With that said, there are some MkDocs and Python Markdown extensions in the wild using ::: for other purposes.

The reality is there is limited, reasonable syntax available for Markdown, which is why a general purpose block extension is a good idea moving forward – inventing non-conflicting new syntax becomes more of a chore as time goes on. I’m okay with conflicting with existing plugins in order to expose a, hopefully, more sane approach to generic-blocks. /// may also conflict with something out in the wild, but it will probably conflict with less, at least that is my thinking.

Ideally, moving forward, extensions will try to adopt this more generic approach for generic blocks and help consolidate some of this syntax (this might not actually happen, but we can dream), but it may be nicer to reduce the impact by choosing a syntax at least less conflicting.

That is my current leaning.

Okay, blocks actually aligns with what I was already thinking. I’ll probably just do that.

Yeah, nesting under attributes would be a requirement if exposing any attribute on any block.

Some blocks could potentially have options to target specific elements with specific attributes (assuming some complex HTML structure), but attributes would assume the outer HTML element under evaluation.

Interesting. Over : and |, I think I prefer | as well. I had not considered doubling them though, so :: is interesting as well. I’m not sure why I don’t like || as much, but I may need to play with it. This really does help, and I think at this point I’m going to forgo quotes.

At this point, I think I’m ready to code up the final bits as whatever tokens we chose, they will not affect the parsing logic. I can easily swap out ///, :::, and separators without upending any of the core logic, so I can figure out the final tokens we use after finalizing core logic.

As far as --- is concerned, if we do end up requiring content having 1 blank line before it, we could make --- optional. For YAML purists who like to use it, or for people who just want to remind people they are working in YAML, they could use it. For others, they could omit it.

However, a wrapper around what you have so far could provide a more general purpose system that uses actual templates. I just wouldn’t name what you have “template.”

Yeah, the idea of templates wasn’t really laid out, and I do realize what I have isn’t a template. The naming is quite wrong in that regard. I haven’t even thought about true templates yet. I’m still working through getting the blocks to not get messed up when passing through lists. There are always list corner cases…always 😢 .

I feel there is an advantage to more advanced, non-template type variants, but I can also see the attraction for actual tempaltes.

As a user, I think this is a wonderful idea! I’m currently migrating a mediawiki site to git/markdown. For admonitions, the pandoc mediawiki→markdown converter produced ::: warning, which is the same that markdown-it, docusaurus and some others use. It’ll take time to update hundreds of wiki pages to !!! warning and indented content that mkdocs uses. Also, if I ever need to switch away from mkdocs in the future, I’ll have to convert the content once more. The ::: class (and the ::: tag) ideas are so powerful, that will probably make !!! admonitions obsolete if they’re implemented! I hope we can see this in production soon! 😃