npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@tiptap/draftjs-to-tiptap

v0.0.11

Published

Convert Draft.js content to Tiptap-compatible content.

Downloads

2,901

Readme

draftjs-to-tiptap

Convert Draft.js content to Tiptap-compatible content.

The best place to try this library is in the CodeSandbox.

Why?

Draft.js and Tiptap are both rich text editors, but they have different data structures. Draft.js preferring a flat structure of blocks with inline styles and entities, while Tiptap uses a nested structure of nodes and marks.

Draft.js has been archived, and Tiptap is a popular alternative for building rich text editors in multiple frameworks. This library aims to make it easier to migrate from Draft.js to Tiptap by providing a conversion tool.

Installation

npm install @tiptap/draftjs-to-tiptap

yarn add @tiptap/draftjs-to-tiptap

pnpm add @tiptap/draftjs-to-tiptap

bun add @tiptap/draftjs-to-tiptap

Getting Started

Unfortunately, there is no one-size-fits-all solution for converting Draft.js content to Tiptap content. The conversion process depends on the specific use case and the customizations made to the Draft.js content. Draft.js did not come with very many built-in features, so many developers added their own customizations to the content structure. So, the conversion process will likely need to be customized to fit your specific use case.

The conversion process involves mapping:

  • Draft.js blocks to Tiptap nodes
  • Draft.js entities to Tiptap nodes
  • Draft.js inline styles to Tiptap marks
  • Draft.js entities to Tiptap marks

What are blocks, entities, inline styles, nodes, and marks?

  • Blocks are the main structural units of content in Draft.js. They are the paragraphs, headings, lists, etc.
  • Entities are the annotations on the content, like links, mentions, etc.
  • Inline styles are the formatting applied to the text, like bold, italic, etc.

Here is an example of all the types of content Draft.js supports in a ContentState:

{
  "blocks": [
    {
      "key": "1",
      "text": "Hello, world!", // block text
      "type": "unstyled", // block type
      "depth": 0,
      "inlineStyleRanges": [],
      "entityRanges": [ // entities applied to the block text
        {
          "offset": 0, // start index of the entity
          "length": 12, // length of the entity
          "key": 0 // entity key in the entityMap below
        }
      ],
      "data": {}
    }
  ],
  "entityMap": {
    "0": { // entity key
      "type": "LINK", // entity type
      "mutability": "MUTABLE",
      "data": { // entity data
        "url": "https://example.com"
      }
    }
  }
}

In Tiptap:

  • Nodes are the main structural units of content in Tiptap. They are the paragraphs, headings, lists, etc.
  • Marks are the formatting applied to the text, like bold, italic, etc.

Here is an example of all the types of content Tiptap supports in a ContentNode:

{
  "type": "paragraph", // node type
  "content": [ // node content
    {
      "type": "text", // node type
      "text": "Hello, world!",
      "marks": [ // marks applied to the text
        {
          "type": "link", // mark type
          "attrs": { // mark attributes
            "href": "https://example.com"
          }
        }
      ]
    }
  ]
}

As you can see, the structure of the content is quite different between Draft.js and Tiptap. This library aims to make it easier to convert Draft.js content to Tiptap content.

Usage

If you are using the default Draft.js content structure, you can use the default converter provided by this library. If you have a custom Draft.js content structure, you will need to add custom mappings to the converter.

Using the default converter is simple. Here is an example of how to convert Draft.js content to Tiptap content:

import { DraftConverter } from '@tiptap/draftjs-to-tiptap';

const draftContent = {
  blocks: [
    {
      key: '1',
      text: 'Hello, world!',
      type: 'unstyled',
      depth: 0,
      inlineStyleRanges: [],
      entityRanges: [],
      data: {},
    },
  ],
  entityMap: {},
};

const convertDraftToTiptap = new DraftConverter();
const tiptapContent = convertDraftToTiptap.convert(draftContent);

Demo Visualizer

It can be helpful to visualize the conversion process. You can use the visualizer web app to see the conversion process in action. The visualizer allows you to input Draft.js content and see the Tiptap content that is generated, inside a live Tiptap editor.

You can access the visualizer in CodeSandbox.

It's source is available in the visualizer directory in this repository.

It looks like this:

Visualizer Demo 1 Visualizer Demo 2

Custom Mappings

You can configure the converter to use custom mappings for:

  • Draft.js blocks to Tiptap nodes
  • Draft.js inline styles to Tiptap marks
  • Draft.js entities to Tiptap marks
  • Draft.js entities to Tiptap nodes

Here is an example of how to use custom mappings:

import { DraftConverter } from '@tiptap/draftjs-to-tiptap';

const draftContent = {
  blocks: [
    {
      key: '1',
      text: 'Hello, world!',
      type: 'unstyled',
      depth: 0,
      inlineStyleRanges: [],
      entityRanges: [],
      data: {},
    },
  ],
  entityMap: {},
};

const convertDraftToTiptap = new DraftConverter({
  mapBlockToNode({ block, converter }) {
    if (block.type === 'unstyled') {
      return {
        type: 'paragraph',
        content: [
          {
            type: 'text',
            text: block.text,
            marks: [],
          },
        ],
      };
    }
  }
});

const tiptapContent = convertDraftToTiptap.convert(draftContent);

Advanced Custom Mappings

The custom mappings can be as simple or as complex as needed. You can use the converter object to access the default mappings and helper functions provided by the library.

The library essentially iterates over each Draft.js block and applies the custom mappings to convert the block to a Tiptap node. It completely gives you control over the conversion process, even allowing you to control iteration over the blocks, which can be useful for more complex content structures.

Here is an example of a more complex custom mapping:

import { DraftConverter } from '@tiptap/draftjs-to-tiptap';

const draftContent = {
  blocks: [
    {
      key: "1",
      text: "Item 1",
      type: "unordered-list-item",
      depth: 0,
      inlineStyleRanges: [],
      entityRanges: [],
      data: {},
    },
    {
      key: "2",
      text: "Item 2",
      type: "unordered-list-item",
      depth: 0,
      inlineStyleRanges: [],
      entityRanges: [],
      data: {},
    },
  ],
  entityMap: {},
};

const convertDraftToTiptap = new DraftConverter({
  // Mapping a block does not have to be a one-to-one mapping, it can be a many-to-one mapping
  mapBlockToNode({ block, next, peek, converter, entityMap }) {
    if (block.type === "unordered-list-item") {
      // We need to check if the next block is also an unordered-list-item
      const listNode = converter.createNode("bulletList");
      do {
        // For each unordered-list-item block, we create a listItem node
        const itemNode = converter.createNode("listItem", {
          content: [
            // We create a paragraph node for the text content
            converter.createNode("paragraph", {
              content: [
                converter.splitTextByEntityRangesAndInlineStyleRanges({
                  block,
                  entityMap,
                }),
              ],
            }),
          ],
        });
        // We add the listItem node to the bulletList node
        converter.addChild(listNode, itemNode);

        // The next item is not an unordered-list-item, so we break out of the loop
        if (peek()?.type !== "unordered-list-item") {
          break;
        }
        // `next` iterates to the next block
      } while (next());

      // We return the bulletList we've created
      return listNode;
    }
  },
});

const tiptapContent = convertDraftToTiptap.convert(draftContent);

API

DraftConverter

The DraftConverter class is the main class provided by this library. It is used to convert Draft.js content to Tiptap content.

new DraftConverter(options?: ConverterOptions)

  • options (optional): An object containing options for the converter.
ConverterOptions
  • mapBlockToNode?: (params: MapBlockToNodeContext) => NodeType | null | undefined: A function that maps a Draft.js block to a Tiptap node.
  • mapInlineStyleToMark?: (params: MapInlineStyleToMarkContext) => MarkType | null | undefined: A function that maps a Draft.js inline style to a Tiptap mark.
  • mapEntityToMark?: (params: MapEntityToMarkContext) => MarkType | null | undefined: A function that maps a Draft.js entity to a Tiptap mark.
  • mapEntityToNode?: (params: MapEntityToNodeContext) => NodeType | null | undefined: A function that maps a Draft.js entity to a Tiptap node.
MapBlockToNodeContext

Available properties:

  • converter: The draft converter instance.
  • entityMap: The entity map of the Draft.js content.
  • doc: The current document tree.
  • getCurrentBlock: A function that returns the current block.
  • block: The current block to convert.
  • index: The index of the current block.
  • setIndex: A function that sets the index of the current block.
  • allBlocks: All blocks in the content being converted.
  • peek: A function that peeks at the next block in the content without iterating.
  • peekPrev: A function that peeks at the previous block in the content without iterating.
  • next: A function that gets the next block in the content and iterates forward.
  • prev: A function that gets the previous block in the content and iterates backward.
MapInlineStyleToMarkContext

Available properties:

  • converter: The draft converter instance.
  • range: The inline style range to convert.
  • entityMap: The entity map of the Draft.js content.
  • doc: The current document tree.
  • block: The current block to convert.
MapEntityToMarkContext

Available properties:

  • converter: The draft converter instance.
  • range: The entity range to convert.
  • entityMap: The entity map of the Draft.js content.
  • doc: The current document tree.
  • block: The current block to convert.
MapEntityToNodeContext

Available properties:

  • converter: The draft converter instance.
  • range: The entity range to convert.
  • entityMap: The entity map of the Draft.js content.
  • doc: The current document tree.
  • block: The current block to convert.

convert(draftContent: DraftContent): TiptapContent

  • draftContent: The Draft.js content to convert.
  • Returns: The Tiptap content.

Utility Functions

This library also provides utility functions for creating and manipulating Tiptap content. These functions can be used to create and manipulate Tiptap content directly without using the converter.

import { createNode, addChild } from '@tiptap/draftjs-to-tiptap';

const node = createNode('paragraph');
// { "type": "paragraph", "content": [] }
const child = createNode('text', { text: 'Hello, world!' });
// => { "type": "text", "text": "Hello, world!" }

const tree = addChild(node, child);
// => { "type": "paragraph", "content": [{ "type": "text", "text": "Hello, world!" }] }

Using TypeScript

This library is written in TypeScript and provides type definitions for the converter. It includes types for tiptap content, but any custom types will need to be defined by the user.

Here is an example of how to use TypeScript with the converter:

import { type NodeType } from '@tiptap/draftjs-to-tiptap';

declare module '@tiptap/draftjs-to-tiptap' {
  // This extends the NodeMapping interface to include a custom node types
  interface NodeMapping {
    // This adds a custom node type to the NodeMapping allowing for correct typing in `createNode` and `addChild` methods
    custom: NodeType<'custom'>;
  }
}

const node = createNode('custom');
// => { "type": "custom", "content": [] }

This is not strictly necessary, but it can help with type checking and code completion in an IDE. As well as enforcing the correct structure of the content. For example, you can restrict the child nodes that can be added to a parent node, though this is only enforced on the type level and not at runtime.

import { type NodeType } from '@tiptap/draftjs-to-tiptap';

declare module '@tiptap/draftjs-to-tiptap' {
  // This extends the NodeMapping interface to include a custom node types
  interface NodeMapping {
    // An image node can only have a src attribute and no content
    image: NodeType<'image', { src: string }, never, never>;
    // A figcaption node can only have text content
    figcaption: NodeType<'figcaption', {}, MarkType, NodeMapping['text']>;
    // A figure node can only have an image and a figcaption as children
    figure: NodeType<
    "figure",
    {},
    MarkType,
    Array<NodeMapping['image'] | NodeMapping["figcaption"]>
    >;
  }
}