statamic / cms

The core Laravel CMS Composer package
https://statamic.com
Other
3.71k stars 508 forks source link

Custom Bard extension isn't recognized #2000

Closed wiebkevogel closed 1 year ago

wiebkevogel commented 4 years ago

Bug Description

We've added a custom Bard Button that works similar to your Link Button. The only difference is that it builds an internal link to an entry.

    schema() {
        return {
            attrs: {
                href: { default: null },
            },
            inclusive: false,
            parseDOM: [
                {
                    tag: 'a[href^="cs://"]',
                    getAttrs: (dom) => ({
                        href: dom.getAttribute('href'),
                    }),
                },
            ],
            toDOM: (node) => ['a', node.attrs, 0],
        };
    }

Our problem is that our button for an entry is no longer recognized after the page is reloaded. We suspect that it is related to the fact that your link is first checked by prosemirror and your parseDOM regex also matches our link to an entry. Is it possible that the customized extensions are submitted to prosemirror first? With this our parseDOM regex should be recognized first.

How to Reproduce

Extra Detail

Environment

Statamic version: 3.0.0.-beta-{?}

PHP version: 7.{?}

Install method (choose one):

jasonvarga commented 4 years ago

Can you provide your entire extension? Are you still using the native link button or is this meant to replace it entirely?

sauerbraten commented 4 years ago

This is on top of the native link button, our editors need both.

We register the extension in our app.js like this (not shown: some code for other extensions and buttons):

// booting
Statamic.booting(() => {

    // bard extensions
    Statamic.$bard.extend(({ bard, mark }) => mark(new EntryLink(bard)));

    Statamic.$components.register('EntryLinkToolbarButton', require('./components/bard/EntryLinkToolbarButton.vue').default);

    Statamic.$bard.buttons(() => ({
        name: 'entrylink',
        text: __('Link to Entry'),
        command: 'entrylink',
        html: '<i class="icon icon-forward"></i>',
        component: 'EntryLinkToolbarButton',
    }));
});
edalzell commented 4 years ago

@sauerbraten did you ever get this working? We need something like this too.

sauerbraten commented 3 years ago

@edalzell: yes, sort of, very recently. For us, the problem was that we stored text paragraphs as HTML, i.e. the Statamic v2 legacy format. And while we had a custom ProseMirror node registered when converting from JSON to HTML, we didn't have those for the conversion in the other direction, i.e. our custom entrylink was just translated into ProseMirror's built-in link node.

For it to work, I had to first patch the html-to-prosemirror dependency (https://github.com/ueberdosis/html-to-prosemirror/pull/37) and then override some methods of the built-in Bard fieldtype:

protected function getRenderer()
{
    return (new Renderer())
        ->replaceMark(Link::class, NormalLink::class)
        ->addMark(EntryLink::class);
}

public function preProcess($value)
{
    if (is_string($value)) {
        $value = trim($value);
    }

    if (empty($value) || $value === '[]') {
        return '[]';
    }

    if (is_string($value)) {
        $doc = $this->getRenderer()->render($value);
        $value = $doc['content'];
    } else {
        $value = $this->convertLegacyData($value);
    }

    return collect($value)->map(function ($row, $i) {
        if ($row['type'] !== 'set') {
            return $row;
        }

        return $this->preProcessRow($row, $i);
    })->toJson();
}

protected function convertLegacyData($value)
{
    return collect($value)->flatMap(function ($set, $i) {
        if ($set['type'] === 'text') {
            if (empty($set['text'])) {
                return;
            }
            $doc = $this->getRenderer()->render($set['text']);
            return $doc['content'];
        }

        if (!array_key_exists($set['type'], $this->config('sets'))) {
            \Log::warning('Bard set type "'.$set['type'].'" does not exist in config.');

            return;
        }

        return [
            [
                'type' => 'set',
                'attrs' => [
                    'id' => "set-$i",
                    'values' => $set,
                ]
            ]
        ];
    })->all();
}

public function preProcessIndex($data)
{
    if (empty($data)) {
        return null;
    }

    if (is_string($data)) {
        $doc = $this->getRenderer()->render($data);
        $data = $doc['content'];
    } else {
        try {
            $isLegacyData = $this->isLegacyData($data);
        } catch (\Exception $e) {
            // we get ErrorExceptions from missing 'type' property in a Bard block
            \Log::error('Bard::isLegacyData() call failed', ['exception' => $e, 'bard value' => $data]);
            // we just don't render the content in the index for now
            return null;
        }
        if ($isLegacyData) {
            $data = $this->convertLegacyData($data);
        }
    }

    return parent::preProcessIndex($data);
}

We introduced getRender() and for our use case, override the built-in link node with a node that only matches a tags that do not start with our custom cs:// protocol. Then we register our entrylink node that matches only those custom cs:// links. We then use getRenderer() wherever a html -> ProseMirror JSON conversion takes place.

When https://github.com/ueberdosis/html-to-prosemirror/pull/37 is merged, Statamic could expose an API to register custom nodes/marks for Bard, or load them by convention from a certain directory. (Same for the JS side maybe?) Or at least they could have this getRenderer() method in their Bard fieldtype so it's enough to override that one in a custom fieldtype instead of all the methods where conversion happens.

github-actions[bot] commented 3 years ago

This issue has not had recent activity and has been marked as stale — by me, a robot. Simply reply to keep it open and send me away. If you do nothing, I will close it in a week. I have no feelings, so whatever you do is fine by me.

wiebkevogel commented 3 years ago

Oh, I think this issue shouldn't be closed yet.

jasonvarga commented 1 year ago

I'm assuming this is no longer an issue in 3.4 with the update to tiptap and bard.