lexical-link-preview-plugin: Link preview for Lexical
By Kata on Tuesday, August 8, 2023

Introduction

We created a developer friendly plugin for Meta's Lexical editor. The plugin adds a link preview box well known from social media. By default, it displays page's name, description, and image if available.

Features

  • Listens to paste-event, therefore it automatically fetches the metadata from the page
  • Uses your callback function to fetch
  • Pastes inline URL and inline-block preview
  • Can handle displaying both the link and preview, or just the preview
  • Design similar to social media link previews
  • Accepts custom styling with CSS classnames

Story-time: why DecoratorNode?

As you already know, the lexical editor uses one node, LexicalNode, which is internally extended to create five basic nodes: RootNode, LineBreakNode, ElementNode, TextNode, and DecoratorNode - the last three can be further extended. ElementNode would be perfect for our purpose: the createDOM method allows us to create the preview box as it runs when we initialize a new object. ElementNode only accepts JavaScript elements, that's fine if you want to develop framework-agnostic UI stuff. In order to use React components we need a DecoratorNode, that has a similar method, decorate, which we could use conveniently.

Let's create the plugin first

The plugin listens for a paste event, and we found the relevant command among the predefined ones, so we just had to use it. Wrapped in a useEffect, the editor.registerCommand lets us attach an event handler to deal with the "paste command" which is Lexical-speak for the content editable paste event. If the inserted string passes the URL regexp, the result of the custom fetcher function is pushed into an array with the necessary metadata from the web page. This triggers the editor.update function to create a new LinkPreviewNode instance and insert it into the editor. However, there was a problem: the fetch should run asynchronously, but the registerCommand could not handle it. The solution: after checking if the inserted element is a valid URL, an async function waits for the metadata from the page and wraps the whole editor.update.

$createLinkPreviewNode

This is what creates a new LinkPreviewNode object, it's a peculiarity of Lexical. Still, first we needed to create the LinkPreviewNode class, so we could bind this command to it. As I wrote above, the LinkPreviewNode extends the DecoratorNode, specifically the DecoratorBlockNode, because the former creates an inline node, the latter, as you can guess, a block. The node gets a URL and the metadata from the page, and the decorate method passes it - and some other props - to our React component. Here, we can link the classname from the theme to the component and set an onClose functionality, since the decorate method also gets the editor state. All we had to do was pass a function that updates the editor to remove the node itself.

The preview box

The preview box component returns a BlockWithAlignableContents component that wraps the <a/> tag and the preview itself. It handles the classname binding, the nodeKey, and the removal of the node with the backspace or delete key.

…and the product that you can try below:

The plugin requires 3 parameters:

  • showLink <boolean>: when the user pastes the URL, showLink decides if only the preview should be pasted into the editor or also the link. When the plugin checks if the pasted item is a valid URL, it returns true or false depending on the value of showLink.
  • fetchDataForPreview: (link: string) => Promise<{url: string, title: string, description: string, images: string[]}>: this function fetches the metadata from the linked page.
  • showClosePreview <boolean>: if true, the preview displays a close button

Caveat

Because of CORS, you can't get the link preview directly from the client. You need to have a custom backend that fetches the necessary metadata for the preview. You can either use the paid OpenGraph fetcher API or you can use the link-preview-js library on your backend. In this article, we will use link-preview-js with Next.js API to do this.

That's it!

If you have any questions or want to leave feedback, please feel free to contact us!

You can check out the code and find some more info about the usability at https://github.com/emergence-engineering/lexical-link-preview-plugin

Did you like this article? Would you like to learn more?
Write to us at contact@emergence-engineering.com