Avoid replacing match in MatchDecorator (decorator to add link icon after URLs)

I’m trying to make a MatchDecorator that will either make URLs clickable (ideally) or add a link icon after URLs that is clickable.

This will replace the URL with a link icon:

const linkDecorator = new MatchDecorator({
    regexp: /https?:\/\/[a-z0-9\._/~%\-\+&\#\?!=\(\)@]*/ig,
    decoration: (match, view) => {
        const url = match[0];
        return Decoration.widget({widget: new HyperLink({from: start, to: end, url})});
    },
});

And this will make a link that isn’t clickable (due to links not being clickable in contenteditable):

const linkDecorator = new MatchDecorator({
    regexp: /https?:\/\/[a-z0-9\._/~%\-\+&\#\?!=\(\)@]*/ig,
    decoration: (match, view) => {
        const url = match[0];
        return Decoration.mark({
            tagName: 'a',
            attributes: {
                href: url,
                rel: "nofollow",
                class: "cm-link",
                target: "_blank",
            }
        });
    },
});

With a MatchDecorator is there a way to avoid replacing the match and instead add a widget just after the match?

Thanks!

Does this patch look like it would solve this?

1 Like

That worked great. Thanks @marijn!

image

For looking to add a link icon after each URL, here’s what I did:

class HyperLink extends WidgetType {
    constructor(state) {
        super();
        this.state = state;
    }
    eq(other) {
        return (
            this.state.url === other.state.url
            && this.state.at === other.state.at
        );
    }
    toDOM() {
        const wrapper = document.createElement('a');
        wrapper.href = this.state.url;
        wrapper.target = "_blank";
        wrapper.innerHTML = `<i class="fas fa-link mx-1"></i>`;
        wrapper.className = "cm-link";
        wrapper.rel = "nofollow";
        return wrapper;
    }
};

const linkDecorator = new MatchDecorator({
    regexp: /https?:\/\/[a-z0-9\._/~%\-\+&\#\?!=\(\)@]*/ig,
    decorate: (add, from, to, match, view) => {
        const url = match[0];
        const start = to, end = to;
        const linkIcon = new HyperLink({at: start, url});
        add(start, end, Decoration.widget({widget: linkIcon, side: 1}));
    },
});

const urlPlugin = ViewPlugin.fromClass(
    class URLView {
        constructor(view) {
            this.decorator = linkDecorator;
            this.decorations = this.decorator.createDeco(view);
        }
        update(update) {
            if (update.docChanged || update.viewportChanged) {
                this.decorations = this.decorator.updateDeco(update, this.decorations);
            }
        }
    }, {decorations: v => v.decorations}
);

Example here and here