Skip to content

[5.x] Added the option to add renderers to markdown parsers #11827

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 4, 2025

Conversation

CapitaineToinon
Copy link
Contributor

@CapitaineToinon CapitaineToinon commented May 28, 2025

Hey,

This is an attempt to add a new feature that allows developers to add custom markdown Renderers when creating or extending markdown parsers, see: https://commonmark.thephpleague.com/2.7/customization/rendering/

This PR fixes #11825 and follows up to a discord thread.

As of today, the Markdown Parser only keeps tracks of custom extensions when creating or copying parsers, making it impossible to add custom renderers. This can be very useful for customizing how the html is rendered, for example if you already have a blade component for your table, link, headers, etc and which to reuse them directly with your markdown.

I added a simple test with a LinkRenderer that adds some attributes and extended the newInstance test to ensure renderers are copied over. I mostly just copy and pasted the code implemented for how extensions were saved and did the same for renderers.

Ran the tests locally and seems to be working just fine. I'm opened to suggestions.

@CapitaineToinon CapitaineToinon changed the title Added the option to add renderers to markdown parsers [5.x] Added the option to add renderers to markdown parsers May 28, 2025
@duncanmcclean
Copy link
Member

duncanmcclean commented May 30, 2025

Can you provide an example of how you'd add a renderer using the addRenderers() method?

@CapitaineToinon
Copy link
Contributor Author

CapitaineToinon commented May 30, 2025

In your extend function, you'd pass an array or arrays to the addRenderers function:

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Markdown::extend('default', function (Parser $parser) {
            $parser->addRenderers(function () {
                return [
                    [Link::class, new LinkRenderer],
                    [Heading:class, new HeadingRenderer, 100], // Can also set the priority
                ];
            });


            return $parser;
        });

    }
}

Although I've noticed the Parser class also have an addExtension singular function, maybe I could add an addRenderer singular function too?

@CapitaineToinon
Copy link
Contributor Author

CapitaineToinon commented Jun 2, 2025

Actually it seems like I can get around this issue with a custom extension instead, registering my renderers there instead. For example if I wanna use Flux UI to render the Markdown Hints I can do this

class HintRenderer implements NodeRendererInterface
{
    public function render(Node $node, ChildNodeRendererInterface $childRenderer)
    {
        if (! ($node instanceof Hint)) {
            throw new \InvalidArgumentException('Incompatible node type: '.get_class($node));
        }

        return Blade::render(<<<'HTML'
            <flux:callout :variant="$variant" class="not-prose">
                @if ($title)
                    <flux:heading>{{ $title }}</flux:heading>
                @endif
                <div class="prose prose-sm">
                    <s:markdown>
                        {!! $text !!}
                    </s:markdown>
                </div>
            </flux:callout>
            HTML, [
            'title' => $node->getTitle(),
            'variant' => $node->getType(),
            'text' => $node->getLiteral(),
        ]);
    }
}

And use it like this

final class WikiExtension implements ExtensionInterface
{
    public function register(EnvironmentBuilderInterface $environment): void
    {
        $environment->addExtension(new GithubFlavoredMarkdownExtension);
        $environment->addExtension(new HintExtension);
        $environment->addRenderer(Hint::class, new HintRenderer, 100);
    }
}

Finally, just register the extension as explained in the docs.

Markdown::extend('default', function (Parser $parser) {
    $parser->addExtensions(function () {
        return [new WikiExtension];
    });
                                                        
    return $parser;
});

So yeah, we could keep going with the PR or make it clear in the docs that Renderers should be reigstered through extensions instead? Up to you guys.

@duncanmcclean
Copy link
Member

Keep it open - it's a good pull request. We'll take a look when we're able 👍

@duncanmcclean
Copy link
Member

Thanks for this pull request!

I've added a singular addRenderer method, to keep the API inline with extensions:

Markdown::extend('default', function (Parser $parser) {
    $parser->addRenderer(function () {
        return [Link::class, new LinkRenderer];
    });

    return $parser;
});

@duncanmcclean duncanmcclean merged commit db1db9f into statamic:5.x Jun 4, 2025
24 checks passed
@CapitaineToinon
Copy link
Contributor Author

Awesome, thanks! Glad I could help 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Impossible to set custom markdown renderers
3 participants