Repository: ueberdosis/tiptap
File: packages/extension-collaboration/src/collaboration.ts (TypeScript)

Code before:
          if (isChangeOrigin(tr)) {

            // When collaboration is disabled, prevent any sync transactions from being applied
            if (this.storage.isCollaborationDisabled) {
              // If collaboration is disabled, unregister the Yjs plugins to prevent further sync transactions
              this.editor.unregisterPlugin(ySyncPluginKey)
              this.editor.unregisterPlugin(yUndoPluginKey)
              return false
            }

            // Did the doc actually change as a result of this transaction?
            if (tr.docChanged) {

              // Attempt to parse the content of the step
              try {
                const content = tr.doc.toJSON()

                this.editor.schema.nodeFromJSON(content)
              } catch (error) {
                this.editor.emit('contentError', {
                  error: error as Error,
                  editor: this.editor,
                  disableCollaboration: () => {
                    this.storage.isCollaborationDisabled = true
                    this.editor.unregisterPlugin(ySyncPluginKey)
                    this.editor.unregisterPlugin(yUndoPluginKey)
                  },
                })
                // If the content is invalid, return false to prevent the transaction from being applied
                return false
              }
            }
          }
          return true
        },
      }),
    ].filter(Boolean)
  },
})


Review comment (suggestion):
I think that the best way to disable collaboration here is actually just to disconnect the provider, we cannot actually disconnect the editor from reading changes in Y.js because it may not be only this plugin that is connected to it.

If we were to provide the provider as an option to the collaboration plugin we could be more certain that the changes CANNOT be synchronized to other users. Of course the caller could also easily implement this too, but it is both consistent with the current API on initialization and makes for an easy-to-use API.

Code after:

    const ySyncPluginInstance = ySyncPlugin(fragment, ySyncPluginOptions)

    if (this.editor.options.enableContentCheck) {
      fragment.doc?.on('beforeTransaction', () => {
        try {
          const jsonContent = (yXmlFragmentToProsemirrorJSON(fragment))

          if (jsonContent.content.length === 0) {
            return
          }

          this.editor.schema.nodeFromJSON(jsonContent).check()
        } catch (error) {
          this.editor.emit('contentError', {
            error: error as Error,
            editor: this.editor,
            disableCollaboration: () => {
              fragment.doc?.destroy()
              this.storage.isDisabled = true
            },
          })
          // If the content is invalid, return false to prevent the transaction from being applied
          return false
        }
      })
    }

    return [
      ySyncPluginInstance,
      yUndoPluginInstance,
      // Only add the filterInvalidContent plugin if content checking is enabled
      this.editor.options.enableContentCheck
        && new Plugin({
          key: new PluginKey('filterInvalidContent'),
          filterTransaction: () => {
            // When collaboration is disabled, prevent any sync transactions from being applied
            if (this.storage.isDisabled) {
              // Destroy the Yjs document to prevent any further sync transactions
              fragment.doc?.destroy()

              return true
            }

            return true
          },
        }),
    ].filter(Boolean)
  },
})