Collaboration History

This extension enhances your collaborative editor experience by introducing a version history feature. With it, you have the flexibility to manually or automatically generate document versions. Not only can you restore previous iterations, but you can also derive new versions from older ones.

Installation

Pro Extension

This extension requires a valid subscription in an eligible plan and a running Tiptap Cloud instance. To install the extension you need access to our private registry, set this up first.

Once done, you can install the extension from our private registry:


npm install @tiptap-pro/extension-collaboration-history @hocuspocus/transformer

Note: The @hocuspocus/transformer package is required for transforming Y.js binary to Tiptap JSON content. The package also requires a Y.js installation which is required for collaboration. If you don't have it installed, run npm install yjs in your project. This should automatically happen if you are using NPM (as it automatically resolves peer dependencies).

Settings

Setting Type Default
provider TiptapCollabProvider null
onUpdate function () => {}

Storage

Key Type Description
versions array<Version> The array of versions that are stored in the history.
currentVersion number The current version.
latestVersion number The latest version.
versioningEnabled boolean Is versioning enabled

Commands

Command Description
saveVersion Creates a new version with a given title
toggleVersioning Toggles auto versioning for this document
revertToVersion Revert to a specific version, can create a new revert version with optional title

Examples

Basic Setup

const provider = new TiptapCollabProvider({
  // ...
})

const editor = new Editor({
  // ...
  extensions: [
	  // ...
	  CollabHistory.configure({
	    provider,
	  })
  ],
})

Get Version Update Data and save it into a variable

let currentVersion = 0
let latestVersion = 0
let autoversioningEnabled = false
let versions = []

const provider = new TiptapCollabProvider({
  // ...
})

const editor = new Editor({
  // ...
  extensions: [
	  // ...
	  CollabHistory.configure({
	    provider,
	    onUpdate(payload) {
		    currentVersion = payload.currentVersion
		    latestVersion = payload.latestVersion
		    versions = payload.versions
		    autoversioningEnabled = payload.autoVersioning
	    }
	  })
  ],
})

Access version data directly from storage

const provider = new TiptapCollabProvider({
  // ...
})

const editor = new Editor({
  // ...
  extensions: [
	  // ...
	  CollabHistory.configure({
	    provider,
	  })
  ],
})

const latestVersion = editor.storage.collabHistory.latestVersion
const currentVersion = editor.storage.collabHistory.currentVersion
const versions = editor.storage.collabHistory.versions
const autoversioningEnabled = editor.storage.collabHistory.versioningEnabled

Create a new version manually

editor.commands.saveVersion('My new custom version')

Toggle autoversioning on document

editor.commands.toggleVersioning()

Revert to a specific version

editor.commands.revertToVersion(4)

Revert to a specific version with a custom name

editor.commands.revertToVersion(4, 'Revert to version')

Revert to a specific version with a custom name and a custom name for the previous version

editor.commands.revertToVersion(4, 'Revert to version', 'Unversioned changes before revert')

Implementing version previews for your editor

The examples discussed above will directly modify the document and do not provide local-only previews of a version. Therefore, it is necessary to create your own frontend solution for this requirement. You can leverage the stateless messaging system of the TiptapCloudProvider to request a specific version from the server.

Start by attaching a listener to the provider:


// Import the getPreviewContentFromVersionPayload helper function (refer to details below)
import { watchContent } from '@tiptap-pro/extension-collaboration-history'

// Configure the provider
const provider = new TiptapCollabProvider({ ... })

// use the watchContent util function to watch for content changes on the provider
const unbindWatchContent = watchContent(provider, content => {
  // set your editors content
  editor.commands.setContent(content)
})

If you want to unbind the watcher, you can call the returned unbindWatchContent function like this:

const unbindWatchContent = watchPreviewContent(provider, content => {
  // set your editors content
  editor.commands.setContent(content)
})

// unwatch
unbindWatchContent()

Following this setup, you can trigger version.preview requests like so:


// Define a function that sends a version.preview request to the provider
const requestVersion = version => {
	provider.sendStateless(JSON.stringify({
		action: 'version.preview',
		// Include your version number here
		version,
	}))
}

// Trigger the request
requestVersion(1)

// This function can then be linked to button clicks or other UI elements to trigger the request

How version reverting works

Upon reverting a version, a new version is generated at the top of the version history. This new version will house the content of the reverted version and will stand as the latest version from which all users will proceed. If there were any unversioned changes before the revert action, an additional version will be created prior to the new one. This additional version will preserve the unaltered changes, thereby ensuring no data is lost.

Utility Functions

getPreviewContentFromVersionPayload

This function will turn the payload received from the Tiptap collab provider into Tiptap JSON content.

Argument Description
payload The Hocuspocus payload for the version preview event
field The field you want to parse. Default: default
const myContent = getPreviewContentFromVersionPayload(payload, 'default')

watchPreviewContent

This function will setup a watcher on your Provider that watches the necessary events to react to version content changes. It also returns a new function that can be used to unwatch those events.

Argument Description
provider The Tiptap collab provider
callback The callback function that is called, the argument is the Tiptap JSON content
field The watched field - defaults to default
const unwatchContent = watchPreviewContent(provider, editor.commands.setContent, 'default')

// unwatch the version preview content
unwatchContent()

Possible provider payloads

Here is a list of payloads that can be sent or received from the provider:

Outgoing

document.revert

Request a document revert to a given version with optional title settings.

provider.sendStateless(JSON.stringify({
  action: 'document.revert',
  version: 1,
  currentVersionName: 'Before reverting to version 1',
  newVersionName: 'Revert to version 1',
}))

version.create

Creates a new version with an optional title.

this.options.provider.sendStateless(JSON.stringify({ action: 'version.create', name: 'My custom version' }))

Incoming

saved

This stateless message can be used to retrieve the last saved timestamp.

provider.on('stateless', (data) => {
  const payload = JSON.parse(data.payload)

  if (payload.action === 'saved') {
    const lastSaved = new Date()
  }
})

version.created

This stateless message includes information about newely created versions.

provider.on('stateless', (data) => {
  const payload = JSON.parse(data.payload)

  if (payload.action === 'version.created') {
    const latestVersion = payload.version
    const currentVersion = payload.version
  }
})

document.reverted

This stateless message includes information about a document revert.

provider.on('stateless', (data) => {
  const payload = JSON.parse(data.payload)

  if (payload.action === 'document.reverted') {
    const currentVersion = payload.version
  }
})

Usage