Custom Links in tiptap
Today I came across a scenario where I wanted to modify the default behavior of some <a> links in tiptap editor.
The requirement was if the <a> link has an href attribute starting with /map clicking the link should not open the a new tab.  Opening a new tab loads the entire app again in another tab and breaks the user’s context. 
The Link extension in tiptap let’s you set target to null. However, doing that didn’t work for me. I read that browsers do not let editor links open in the same tab. Read more here Default prevented on <a> click?. 
So I extended the Link extension by creating the CustomLink extension. I took help from this github comment.
Since tiptap is built on top of ProseMirror API, you can use the addProseMirrorPlugins in your extensions. When a link is clicked,
Identify the correct <a> tag
if (link && link.attributes.getNamedItem("href")?.value.startsWith("/map"))stop its propagation.
event.preventDefault(); event.stopPropagation();Do what needs to be done instead.
 extensionOptions.showMap(true);
 extensionOptions.setMapQuery(link.text || "");
Here is the complete code.
import Link from "@tiptap/extension-link";
import { Plugin, PluginKey } from "@tiptap/pm/state";
import { getAttributes } from "@tiptap/core";
interface CustomLinkOptions {
  showMap: (val: boolean) => void;
  setMapQuery: (val: string) => void;
}
export const CustomLink = Link.extend<CustomLinkOptions>({
  addOptions() {
    return {
      ...this.parent?.(),
      openOnClick: false,
      showMap: () => {},
      setMapQuery: () => {},
    };
  },
  addProseMirrorPlugins() {
    const plugins: Plugin[] = this.parent?.() || [];
    const extensionOptions = this.options;
    const clickHandler = new Plugin({
      key: new PluginKey("handleControlClick"),
      props: {
        handleClick(view, pos, event) {
          const attrs = getAttributes(view.state, "link");
          const link = (event.target as HTMLElement)?.closest("a");
          if (
            link &&
            link.attributes.getNamedItem("href")?.value.startsWith("/map")
          ) {
            event.preventDefault();
            event.stopPropagation();
            extensionOptions.showMap(true);
            extensionOptions.setMapQuery(link.text || "");
            return true;
          } else if (link && attrs.href) {
            window.open(attrs.href, attrs.target);
            return true;
          }
          return false;
        },
      },
    });
    plugins.push(ctrlClickHandler);
    return plugins;
  },
});
Here is how you can use the extension in Tiptap.
const editor = useEditor(
    {
      extensions: [
        CustomLink.configure({
          showMap: () => {
            showMap(true);
          },
          setMapQuery: (val: string) => {
            setMapQuery(val);
          },
        }),
       ]
   }
//..rest of your code
});
Hope this helps.
2215 views
Comments ( 2 )
Matthew Bub
a year ago
Thanks for the post, I'm building a feature that tracks links throughout an app and TipTap was the edge case. This is like the perfect template for getting started. Cheers!
Satyajeet Jadhav
a year ago
I’m glad you found it useful! Cheers!
Participate in the conversation.
Never miss a post from
Satyajeet Jadhav
Get notified when Satyajeet Jadhav publishes a new post.