Autolinker

I am trying to implement an input rule that automatically replaces typed links with anchor tags. However, my function cuts off the last character in the string and does not correctly move the cursor forward. I have tried adding offsets to the start and end parameters but that does not fix these issues.

My input rule is:

function linkRule(markType) {
    const urlRegEx = ... //Work in progress
    return InputRule(urlRegEx, (state, match, start, end) => {
        return state.tr.addMark(start, end, markType.create({href: match[0]}).insert(match[1], start);
    });
}

What am I missing or not understand?

The function given to the rule replaces the input’s regular effect, so you’ll add a mark, but cancel the input that caused the match to happen.

This was intended as an optimization (so that such rules only have to make a single change, rather than first letting the input go through and then overwriting it), and for the rules I’ve implemented it worked fine, but I guess for this one it’s quite awkward – especially since you don’t know in advance whether the rule was triggered by typing a single character or pasting a bigger chunk of text. You can look at start and end to figure that out, and insert the text manually, but I agree that’s not a great interface.

1 Like

I believe I got this working with the following:

  let tr = state.tr.insertText(match[0], start, end) // Replace existing text with entire match
  var mark = schema.marks.link.create({href: match[0]});
  return tr.addMark(start, start + match[0].length, mark)
1 Like

@marijn

Thank you for the explanation! It really helped me understand how the input rules work.

@wes-r

Thank you for the code! I modified it slightly because I want to display a shorter version of the link to the user. For example, https://discuss.prosemirror.net becomes discuss.prosemirror.net.

Here is my modified version:

function linkRule(markType) {
    const urlRegEx = ... //Work in progress
    return new InputRule(urlRegEx, (state, match, start, end) => {
    	const link = markType.create({href: match[0]});
    	const displayUrl = ... //Part of the url to display to the user
    	const tr = state.tr
            .insertText(displayUrl, start, start + displayUrl.length)
            .delete(start + displayUrl.length - 1, end - 1)
            .insertText(' ', start + displayUrl.length);
        
    	return tr.addMark(start, start + displayUrl.length, link);
    });
}
1 Like

Maybe like this:

function linkRule(markType) {
  return MarkInputRule(/(?:(?:(https|http|ftp)+):\/\/)?(?:\S+(?::\S*)?(@))?(?:(?:([a-z0-9][a-z0-9\-]*)?[a-z0-9]+)(?:\.(?:[a-z0-9\-])*[a-z0-9]+)*(?:\.(?:[a-z]{2,})(:\d{1,5})?))(?:\/[^\s]*)?\s$/i, markType, match => ({type: ((match[2]=='@')?"email":"uri") }));
}

function MarkInputRule(regexp, markType, getAttrs) {
  return new InputRule(regexp, (state, match, start, end) => {
    let $start = state.doc.resolve(start);
    let attrs = getAttrs instanceof Function ? getAttrs(match) : getAttrs;
    if (!$start.parent.type.allowsMarkType(markType)) return null
    const oLinkString = match[0].substring(0,match[0].length-1);
    const oAttr = (attrs.type=="email") ? { href: "mailto:"+oLinkString } : { href: oLinkString, target: "_blank"};
    const oLink = markType.create(oAttr);
    const oPos = { from: start, to: end };
    const tr =  state.tr
                .removeMark(oPos.from, oPos.to, markType)
                .addMark(oPos.from, oPos.to, oLink)
                .insertText(match[5],oPos.to);
    return tr;
  })
}

I still couldn’t figure the last character missing issue.

I would like to make url link marked and still editable, so that additional character can be appended to the link while editing. Adding space charact wouldn’t in this case.

Is there any other way of doing this? Or InputRule is not appropriate for this case?

Thanks

I was able to resolve it by creating a new plugin easily.

We added autolink support for tiptap with a custom plugin (appendTransaction) instead of an input rule. Code can be found here: tiptap/autolink.ts at main · ueberdosis/tiptap · GitHub

2 Likes

@philippkuehn I tried out the plugin and when typing a url (e.g https://google.com) it ends up with this html which seem way verbose? Was this intended?

image

Reviving this thread, just to check with @marijn if there’s any news since the original answer. To clarify, there’s usually a demand to convert url-looking things to links all over the place:

  1. When users are typing, and most of the time when they have finished typing (e.g. press space, enter, a dot etc). Input Rules don’t always work for this, since most URL regexes will match twice: once with foo.co, then again with foo.com. Trying to counter this with a different regex (e.g. a URL, then another character) doesn’t work when the text is right at the end of a node.

  2. When users are pasting plain text containing URLs, currently done with transformPasted.

  3. When users are selecting text, and pasting a URL over it, currently done with handlePaste, and replacing a selection with a new node + mark.

I feel like there could be opportunities for unification, but I’m not sure. Cases 2+3 are straightforward because you operate only at certain events, but case 1 runs all the time during typing and can be really tough to figure out.

1 Like