ueberdosis / tiptap

The headless rich text editor framework for web artisans.
https://tiptap.dev
MIT License
26.73k stars 2.23k forks source link

Allow handling invalid content errors #3437

Closed ruipserra closed 3 months ago

ruipserra commented 1 year ago

What problem are you facing?

As a developer building an application with TipTap at its core, I would like a way to handle invalid content errors caused by unknown nodes or mark types.

Rationale

Long lived production systems will eventually hit a point where the editor schema evolves in non-backwards-compatible ways. An application that stores documents in persistent storage will later be unable to load them, since the editor will error out and instead render an empty document. For end users, this is equivalent to full data loss.

Some possible solutions to this:

In my view, these strategies highlight a gap in TipTap's API. I believe TipTap itself can help solve this in a more robust way.

What’s the solution you would like to see?

At a high-level, I like the approach taken by remirror: https://remirror.io/docs/concepts/error-handling/

Another benefit to this approach is that the onError callback can be used to capture the error in an error monitoring system. Currently it's not obvious how to do so because TipTap is catching the error.

That said, this is only one way, and maybe there are simpler alternatives that would be a better fit for TipTap.

What alternatives did you consider?

https://github.com/ueberdosis/tiptap/issues/2283#issuecomment-995526706 suggests storing and loading HTML instead:

If it’s an option you can use the HTML content instead because an HTML string is parsed each time and unsupported nodes are converted if necessary.

In my opinion this is not feasible for a lot of apps. Processing the HTML in a backend would involve reimplementing a lot of ProseMirror's code and editor schema.

Anything to add? (optional)

Thanks for developing TipTap! It's been a very positive experience so far 💞

Are you sponsoring us?

bdbch commented 1 year ago

Cool idea! I added it to our tracking board. Since it'll be of higher complexity we'll have to see when we can tackle this feature.

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 7 days

bdbch commented 1 year ago

bump

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 7 days

kongweiying2 commented 1 year ago

@bdbch

Better error handling seems quite important. I've run into an issue where I had a lot of list items, and while modifying the List extension the document got into a bad state and all the data stopped being displayed and was unrecoverable 😱

Since I was using the Collab extension, there was literally no way to get it back because the error had propagated to the server and also overwrote the local IndexedDB copy too. I've been trying to figure out how to be able to roll back to an earlier version of the Collab stored document but there doesn't seem to be a way to do this right now.

@ruipserra

Did you manage to find a solution to handling invalid content in the interim?

alvintheodora commented 9 months ago

Hi @ruipserra Thanks for the elaborate issue explanation, that I am currently facing too. So do we have any news update for the solution so far? Thanks in advance. cc: @bdbch

nadar commented 8 months ago

We are also looking for a solution on how to solve this problem, does anyone have an idea? So much content is lost due to this "error handling".

nadar commented 8 months ago

Maybe it helps someone, we have added this function before the json is loaded into tiptap:

function validateNodeType(jsonDoc, availableNodes) {
    // Check if the current node is valid
    if (!availableNodes.includes(jsonDoc.type)) {
        return null;
    }

    // If the node has content, apply validation to each child
    if (jsonDoc.content) {
        // Filter the content array, removing invalid nodes
        jsonDoc.content = jsonDoc.content
            .map(child => validateNodeType(child, availableNodes))
            .filter(child => child !== null);
    }

    return jsonDoc;
}

const nodes = [
      'doc',
      'paragraph',
      'text',
      'heading',
      'bullet_list',
      'ordered_list',
      'list_item',
      'image',
      'hard_break',
      'blockquote',
      // Add any additional custom nodes here
]

validateNodeType(this.value.json, nodes)
jbhaywood commented 4 months ago

This feels like a huge oversight with the system. I have an editor that implements an auto-save when content changes, and I've tried hacking in ways to prevent the save from kicking in when invalid content is passed in. If I don't catch it, it results in lost data because it'll then save out a blank document. It would be much easier and make the app more stable if there were an onError callback we could hook into.

xwiz commented 3 months ago

Is there someone from the team that can provide a minimal guide on where to start or how to fix this? I'm willing to commit some time to resolve

Nantris commented 3 months ago

I'm no expert but this seems difficult to implement based on my understanding of ProseMirror.

nperez0111 commented 3 months ago

We are working on a fix for this with: https://github.com/ueberdosis/tiptap/pull/5178

nperez0111 commented 3 months ago

Addressed with: https://github.com/ueberdosis/tiptap/pull/5178

nadar commented 3 months ago

@nperez0111 great! how to enable that feature? or is a try/catch enough?

nperez0111 commented 3 months ago

Not yet released yet, and we are working on a big docs overhaul so I have written my documentation in a separate place.

But I'll copy it here for you, hasn't gone through review yet from others:


To track and respond to content errors, Tiptap supports checking that the content provided matches the schema derived from the registered extensions. To use this, set the enableContentCheck option to true, which activates checking the content and emitting contentError events. These events can be listened to with the onContentError callback. By default, this flag is set to false to maintain compatibility with previous versions.

The contentError event

The contentError event is emitted when the initial content provided during editor setup is incompatible with the schema.

This event can be handled either directly as an option through onContentError like:

new Editor({
    content: invalidContent,
    onContentError() {
        // your handler here
    }
})

Or by attaching a listener to the contentError event on the editor instance.

const editor = new Editor({ ...options })

editor.on('contentError', ()=> {
    // your handler here
})

For more implementation examples, refer to the [events] section.

Suggested Usage

How you handle schema errors may be specific to your application but here are our suggestions:

Single user edits at a time (i.e. no collaborative editing)

Depending on your use case, the default behavior of stripping unknown content keeps your content in a known valid state for future editing.

Collaborative editing (i.e. syncs with other users in realtime)

Depending on your use case, you may want to set the enableContentCheck flag and listen to contentError events. When this event is received, you may want to do things like this example:

onContentError({ editor, error, disableCollaboration }) {
  // This callback is only available on editor initialization.
  if (disableCollaboration) {
    // Removes the collaboration extension.
    disableCollaboration()
  }

  // Since the content is invalid, we don't want to emit an update
  // Preventing synchronization with other editors or to a server
  const emitUpdate = false

  // Disable the editor to prevent further user input
  editor.setEditable(false, emitUpdate)

  // Maybe show a notification to the user that they need to refresh the app
}