markdown-it / markdown-it-footnote

Footnotes plugin for markdown-it markdown parser
https://markdown-it.github.io/
MIT License
212 stars 59 forks source link

Footnotes not sequenced / grouped correctly within a plugin #43

Closed coryschires closed 3 years ago

coryschires commented 3 years ago

I'll start by saying this problem is sorta complicated, and I don't expect you to solve it for me. This is really more of a "what do you think is the best approach" type question.

Background

I have a private markdown-it plugin which renders figures (e.g. tables, images, etc) based on JSON data which I pass into the plugin as an argument. Here's a simplified example of what my JSON looks like for a given figure:

let attachments = [
  {
    id: 123,
    kind: 'image',
    url: "https://example.com/image.png",
    title: "Figure 1: Example Title",
  }
]

Also, separately, I am using the markdown-it-footnote plugin as well.

Problem

I encounter problems when trying to add a footnote to the figure title metadata. For example:

let attachments = [
  {
    id: 123,
    kind: 'image',
    url: "https://example.com/image.png",
    title: "Figure 1: Example Title^[Short example footnote]",
  }
]

I would like this footnote to be included alongside the other footnotes in the document. For example, if 3 footnotes appear before this figure, then I would like this footnote to render as number 4. Additionally, I would like this footnote to be listed alongside other footnotes in the footnote list at the end of the document.

Instead, unfortunately, my figure title footnote is rendered in isolation (i.e. it starts at 1 and generates its own separate footnote list). In other words, my figure title footnote seems to have no knowledge about the other footnotes within the document.

Within my extension, I am using the internal rendered to render the figure title:

this.env.md.render(figure.title)

This works perfectly for other use cases (e.g. bold, italic, and even equations via markdown-it-mathjax). But it does not work as expected / desired for footnotes.

Solution?

I could image ways to solve this problem (e.g. post-processing, outside markdown-it where I gather all the footnotes and adjust the HTML as desired).

But before charging ahead with this (or some other) crude solution, is there a better way? Perhaps there's some feature of markdown-it-footnote or the markdown-it internals that would provide a more elegant solution?

Any advice is much appreciated! markdown-it is a great tool!

GerHobbelt commented 3 years ago

Just a thought: I would start by looking into other plugins and how they do their thing. The markdown-it parse() or parseInline() calls, etc. are meant to be used by userland code that takes markdown as a given and a blackbox; you on the other hand are writing a plugin, so there's other stuff to look at instead.

The key then becomes to parse those figure titles while using the very same env environment as used for the main text. Reason being: the footnote plugin tracks the list of footnotes in env.footnotes.list[] and thus SHOULD assign the next id to your title footnote, depending on when you invoke any parse() API in your own plugin.

BTW: You can pass an environment object reference as a parameter to these calls: see the documentation and source code (RTFC šŸ˜„)

Just an idea, not tested, so it's into the rabbit hole after this. šŸ¤Æ

PS: the key is to separate the parse() calls from the render() calls in your mind: first you collect the entire AST (token stream in the case of markdown-it), then only when all is done, you invoke render() with the thusly constructed token stream.

See the code and docu: Renderer.render(tokens, options, env) -> String

Also note how the footnote plugin is organized itself: there's a bunch of handlers for the parse phase, and then there another bunch for rendering the Tokens produced by the parse phase calls. The whole shebang is registered (hooked up) by a set of md.block.ruler... and md.inline.ruler.... calls.

PPS: I'm working on an augmented version of this plugin, where I discovered I would only get my way if I changed the footnote parse behaviour a little by making sure all footnotes are completely decoded (including their 'mode' metadata which is specific to my derivative). The key bits of knowledge gained there were that:


Ok, that's my 50 cents. Hope it helps you on your way and have fun!

rlidwka commented 3 years ago

As described above, use state.md.inline.parse.

If you use this.env.md.render(figure.title), you run markdown parser as if figure.title was a completely new document. But state.md.inline.parse continues parsing existing document with old context, that's the difference.

coryschires commented 3 years ago

@GerHobbelt @rlidwka Thanks! This is all very helpful! I'm gonna dig in and see what I can figure out. Based on these comments, I'm feeling confident my problem is solvable without having to do post-processing outside markdown-it.

But it's complex stuff and may take a while to find the best method. I'll follow up on this thread with whatever I learn.