jacksleight / statamic-bard-mutator

This Statamic addon allows you to modify the data and tags rendered by the Bard fieldtype, giving you full control over the final HTML.
https://statamic.com/addons/jacksleight/bard-mutator
MIT License
19 stars 3 forks source link

Provide contextual metadata to mutators #2

Closed jacksleight closed 2 years ago

jacksleight commented 2 years ago

This adds contextual metadata such as parent and previous/next sibling information to mutators.

jacksleight commented 2 years ago

@el-schneider

Using this feature you can now use Bard Texstyle's span node to apply classes to links. To do this first ensure you're using the new Bard Mutator tag (necessary to add the context data):

{{ bmu:bard_field }}

Then add the following mutator:

Mutator::tag('link', function ($tag, $data, $meta) {
    if (($meta['next']->type ?? null) === 'bts_span') {
        $tag[0]['attrs']['class'] = $meta['next']->attrs->class ?? null;
    }
    return $tag;
});

This will run on all link marks, checking if the following mark is a bts_span, and if so copy the class value from that mark to the tag. This is clean and simple and works, and it may be fine for your purposes, but it does have a couple of issues that you should be aware of:

  1. This assumes that the bts_span mark will directly follow the link mark. While it will always come after, it may not be next if other marks are being used as well.
  2. The <span> tag will still be rendered, which you may not want.

These issues can be resolved within tag mutators, but it's not recommended. Instead you can use another new feature included in this PR, root mutators.

Root mutators run before any rendering starts, and give you the opportunity to manipulate the raw Bard data. They're an advanced feature that give you access to the entire ProseMirror document. Using a root mutator you can traverse through all text nodes, check if they contain link and bts_span marks, and if so alter the data accordingly. You can then use a simple link tag mutator to apply that class to the HTML tag.

use JackSleight\StatamicBardMutator\Facades\Mutator;

Mutator::data('text', function ($data) {
    $marks = collect($data->marks ?? []);
    $link = $marks->search(fn ($m) => $m->type === 'link');
    $span = $marks->search(fn ($m) => $m->type === 'bts_span');
    if ($link !== false && $span !== false) {
        $data->marks[$link]->attrs->class = $data->marks[$span]->attrs->class ?? null;
        unset($data->marks[$span]);
    }
});
Mutator::tag('link', function ($tag, $data) {
    $tag[0]['attrs']['class'] = $data->attrs->class ?? null;
    return $tag;
});

While not as simple, this is more robust and ensures you're only rendering what you need.

I've not yet finalised the root mutator API design so it may change a little before release.

jacksleight commented 2 years ago

When using GraphQL you can override the field with a custom version that augments the value via Bard Mutator, see https://statamic.dev/graphql#custom-fields:

use Statamic\Facades\GraphQL;

GraphQL::addField('Entry_Pages_Pages', 'my_content', function () {
    return [
        'type' => GraphQL::string(),
        'resolve' => function ($entry) {
            return Mutator::render($entry->augmentedValue('my_content'));
        },
    ];
});

Again, still figuring out the final details on this so it may change slightly.