Integrate document history
This extension introduces a document version history feature, allowing you to manually or automatically generate document versions.
You not only can restore previous iterations but also create new versions from older ones.
Public Demo
The content of this editor is shared with other users.
Install
Set up access to Tiptap’s private repository
Gain access to this pro extension by registering for a free Tiptap account and following our access guide to Tiptap’s private repository.
npm install @tiptap-pro/extension-collaboration-history @hocuspocus/transformer
Note:
The @hocuspocus/transformer
package is required for transforming Y.js binary into Tiptap JSON. It also requires Y.js installed for collaboration.
If you don't have it installed, run npm install yjs
in your project. This should happen automatically if you use NPM (as it automatically resolves peer dependencies).
Settings
Setting | Type | Default |
---|---|---|
provider | TiptapCollabProvider | null |
onUpdate | function | () => {} |
Auto versioning
Auto versioning is a feature that automatically creates new versions of your document at regular intervals. This ensures that you have a comprehensive history of changes without manual intervention.
You can toggle this feature using the toggleVersioning
command (default: disabled).
When auto versioning is enabled, new versions are created at regular intervals (30 seconds by default, only if the document has changed). This can create quite a lot of versions, so you may want to increase the interval. To customize the interval, you can do the following:
// Set the interval (in seconds) between auto versions
const ydoc = provider.configuration.document
ydoc.getMap<number>('__tiptapcollab__config').set('intervalSeconds', 900)
Revert to a version
Upon reverting to a version, a new version is generated at the top of the version history. This new version contains the content of the version you reverted to and becomes the latest version from which all users will continue working. If any unversioned changes were made before the revert action, an additional version will be created before the new one, preserving those changes and ensuring no data is lost.
Note that reverting only affects the default
fragment in the ydoc, so while the Tiptap content reverts (unless you've specified a different field
in the TiptapCollabProvider), comments will remain unchanged.
You can integrate the compare snapshots extension to highlight differences between versions, ensuring you choose the right version to restore.
Storage
Key | Type | Description |
---|---|---|
currentVersion | number | The current version. |
lastSaved | Date | The last saved timestamp |
latestVersion | number | The latest version. |
provider | TiptapCollabProvider | The Collaboration provider instance |
status | string | The status of the provider - can be connecting , connected or disconnected |
synced | boolean | Is the version history synced with the server |
versioningEnabled | boolean | Is versioning enabled |
versions | array<Version> | The array of versions that are stored in the history. |
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,
}),
],
})
Store version updates
In this example we retrieve the data of a version update 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.version
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 with version id
editor.commands.revertToVersion(4)
Revert with version id with custom name
In this example, the editor command helps you go back to version 4. When you use this command, it not only takes you back to how things were in version 4, but it also saves this old version as a new version, called 'Revert to version'. This way, you continue working from the point of version 4, but it's now saved as the latest version.
editor.commands.revertToVersion(4, 'Revert to version')
Revert, name and backup
In this example, when you decide to revert to version 4 of your document, the editor automatically creates two new versions. The first new version captures and saves your document’s state just before the revert, serving as a backup. The second new version restores the document to the state of version 4, allowing you to continue from this older, preferred point as your new starting point.
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 { watchPreviewContent } from '@tiptap-pro/extension-collaboration-history'
// Configure the provider
const provider = new TiptapCollabProvider({ ... })
// use the watchPreviewContent util function to watch for content changes on the provider
const unbindWatchContent = watchPreviewContent(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
If you want to go beyond previews and compare different versions visually, the compare snapshots extension provides an easy way to see the changes between any two versions within the editor.
Utility Functions
getPreviewContentFromVersionPayload
This function will turn the payload received from the Collaboration 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 set up 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 Collaboration 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 newly 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
}
})