Autolinker

#1

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?

0 Likes

#2

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.

0 Likes

#3

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

#4

@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

#5

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;
  })
}
0 Likes