arobase-che / remark-attr

Remark plugin to add support for custom attributes
Other
58 stars 16 forks source link

remark@next (13) #22

Open wooorm opened 4 years ago

wooorm commented 4 years ago

Hi!

remark is switching to a new parser (and compiler) internally (micromark, remarkjs/remark#536), which will break this plugin. Keep an eye out for remark-footnotes, remark-frontmatter, remark-gfm, remark-math, and other syntax plugins similar to this one, which could serve as inspiration for this plugin. Typically, these syntax plugins will be small wrapper code around micromark and mdast extensions btw. Also: feel free to ask me questions!

arobase-che commented 3 years ago

Ok, so I took the night to study the problem.

If I understand well, this plugin is obsolete. The parsing is now done in micromark and than I can't rewrite the tokenizers of remark-parse.

I wonder what to do. How should I support such a syntax ? Basically everything is outdated and I must rewrite a plugin from scratch and first study micromark. It's a lot of hard work.

A hint on how to do it well will be very welcome. I guess that you don't remember what that plugin does and how it works. Here a little summary :

This plugin extends the tokenizers of remark-parse to support syntax like that : *Hello !*{ style="red"}. A new tokenizer is created, it call the old tokenizer and them parse the { style="red"} syntax; and modify the result of the old tokenizer consequently (adding attributes).

So now, they is no more tokernizer to extend. Should I extend the state machine of micromark (SyntaxExtension ?) or do it in the unified/remark ecosystem (maybe hard or near to impossible due to edge-cases as *Hello !*{ comment="I _like_ the work on remark"} where the AST will be complex ) ?

Ok, thinking about it made me realize that I only one main question. I think, i have no chose but to extend micromark. Do you think it's possible ? Is there any doc available about it or I will have to study the source code ?

Thank for improving the remark ecosystem and in advance to any help provided.

wooorm commented 3 years ago

For generic directives, I wrote an attribute parser, that support #id.class boolean key=value or="quoted" attributes: https://github.com/micromark/micromark-extension-directive/blob/main/lib/factory-attributes.js. That’s a lot of what you’d need to write.

Architecturally, there is a) a tokenizing part, where you parse {attributes} in micromark, and b) a tree building part, where you turn those tokens into mdast—for directives, that’s mdast-util-directive/from-markdown.js. Last, the remark part bundles it all so that users don’t need to know about everything.

I like building things in separate projects. But you don’t have to: it can all live in this project. The reasons why I split things up, are a) just nice coding for me, b) not everyone needs a syntax tree, so I also have micromark html extensions, c) in the future there will be a CST, not sure how or when that would be, but mdast wouldn’t be the only tree format.

For the tokenizing part, I think you’d be looking for {, then try and parse attributes, then find a }, and you‘re done! With one exception, which I’ll describe later. The second thing is in the adapter, where you‘re taking tokens and building trees, to take your attribute tokens and add them to the preceding link or heading. mdast-util-directive has attributes which aren’t hast, but the remark-directive readme shows an example of making hProperties out of ’em.

The complex part for attrs is that they’re added to other constructs. You can access the events on micromark, so that you can check to parse attributes or not based on whether there’s a link or image right before it. Links and images are made up of a start (![, [), and an end (](asd), ][asd], ][], or ]), which is done here: https://github.com/micromark/micromark/blob/ed4d78e4e2a799858d2c2c4f87b09163f1eae9da/lib/tokenize/label-end.js#L164. Those and autolinks, code spans, footnote calls, inline footnotes, are pretty easy. But emphasis, strong, and strikethrough are harder because those sequences can open and close, which is figured out at the end.

For the flow part (blocks), I see that you have two different ways: setext headings attributes on their own line, ATX headings inline. I would suggest doing only the setext heading way: a “block” attribute starts at a line, cannot include line endings, and ends at a line ending. Then, it’s attached to the preceding block, whether it’s a paragraph, a heading (ATX or setext), thematic breaks, code (fenced or indented). Containers (lists and block quotes) would be more complex, but as you’re not handling them yet, we can skip that for now.


Alright, already a wall of text, so I’ll quit now. That should give you some ideas on how to go about it. An alternative is: The downside of attributes is that they don’t work in other markdown parsers. More markdown parsers are moving to generic directives. You could push folks to instead switch to a directive based approach? Such as the example in the remark-directive readme.

janosh commented 3 years ago

What's the current status? Is there a new remark plugin on the horizon that will provide the functionality of this one with the new micromark parser?

arobase-che commented 3 years ago

No progress. I don't have any need for it, so I focus on other topics.

Any contribution will be welcomed.

david-sharer commented 3 years ago

For anyone else who runs into this, I got this working with a custom component for my scenario (I needed call-to-action button-links). All buttons that have \ as the first-child [**text here**](... "custom:attribute title text") are rendered as buttons and their title text is parsed for custom attribute strings. Highly limited, but worked for me! image

This pipeline relies on react-markdown.

// typescript component rendering
import React, { ReactElement } from "react";
import ReactMarkdown from "react-markdown";
import { Components, ReactBaseProps, ReactMarkdownProps } from "react-markdown/src/ast-to-react";
import remarkGfm from "remark-gfm";
import { first, fromPairs } from "lodash";
import { Button } from "@material-ui/core";

const components: Components = {
  a: ({
    node,
    children,
    href,
    title,
    ...props
  }: ReactBaseProps & ReactMarkdownProps & { href: string; title: string }) => {
    // if the first child of the button is a bold element, make this a CTA button
    if ((first(children) as ReactElement)?.type === "strong") {
      const splitTitle = title.split(' ');
      const titleAttrEntries = splitTitle.filter(t => t.includes(':'));
      const remainingTitleEntries = splitTitle.filter(t => !t.includes(':'));
      const finalTitle = remainingTitleEntries.join(' ');
      const titleAttrPairs = titleAttrEntries.map(te => te.split(':'))
      const titleAttrs = fromPairs(titleAttrPairs);
      return (
        <Button variant="contained" href={href} title={finalTitle} {...props} {...titleAttrs}>
          {children}
        </Button>
      );
    }

    return <a href={href} title={title} children={children} {...props}></a>;
  },
};

export function renderMarkdown(markdown: string) {
  return (
    <ReactMarkdown
      plugins={[remarkGfm]}
      children={markdown}
      components={components}/>
  );
}
<!-- markdown -->
[Regular Link](/where_to_go/)

[**CTA Button Link**](/where_to_go/ "color:primary any:attribute things without colons will be treated as the title")

output image

david-sharer commented 3 years ago

It looks like this may be a suitable replacement, as well https://github.com/remarkjs/remark-directive

arobase-che commented 3 years ago

Directive should have been the way to go since the CommonMark compatibility, but directive never really make it way to the CommonMark standard.

I wonder if it's used anywhere. But in principle, it's nice, maybe a little complex but still nice.

It's also difficult to implement to remark but that not a real argument because attributes-style (like remark-attr) become slightly more complicated to implement with µmark.

PS: If you plan to use remark-directive take a look at https://github.com/micromark/micromark-extension-directive.

wooorm commented 3 years ago

I wonder if it's used anywhere. But in principle, it's nice, maybe a little complex but still nice.

It’s getting some traction: a growing number of questions about it and users of it!

cah4a commented 2 years ago

Made a simple plugin from scratch that works well with remark@next: https://gist.github.com/cah4a/9b75c531540e2891599453863dd24881

Don't know how well it is aligned with the plugins ecosystem. Patching hProperties seems not a very delicate solution, but it works.

If somebody has the power to make a package out of it, feel free to grab the source.

swamidass commented 2 years ago

Don't know how well it is aligned with the plugins ecosystem. Patching hProperties seems not a very delicate solution, but it works.

If somebody has the power to make a package out of it, feel free to grab the source.

Is this code still working? Has anyone made a package out of it yet? That would be much appreciated!