比较版本

使用 compareVersions 命令 比较两个 Yjs 版本更新并显示变化。

获取版本

首先使用 getVersions 方法获取版本。如果使用快照扩展,可以从 editor.storage.snapshot.versions 获取可用版本。数组中的每个项目都包含保存版本 ID 的 version 属性。将这些版本 ID 传给 getVersions。

有关如何保存快照和获取版本数据,请参阅快照指南。

import { getVersions } from '@tiptap-pro/compare'
import { TiptapCollabProvider } from '@tiptap-pro/provider'

const provider = new TiptapCollabProvider()

const { prevSnapshot: versionA, snapshot: versionB } = await getVersions({
  provider,
  // 从快照扩展获取的版本 ID
  fromVersion: 1,
  toVersion: 2,
})

比较版本

将 Compare 扩展添加到要显示变化的编辑器中,然后将两个 Yjs 版本传给 compareVersions。

import { Editor } from '@tiptap/core'
import { Compare } from '@tiptap-pro/compare'

// 显示变化的编辑器
const editor = new Editor({
  extensions: [
    Compare,
    // ...other extensions
  ],
})

editor.commands.compareVersions({
  versionA,
  versionB,
})

compareVersions 命令会比较两个版本,并立即显示变化。

设置变更样式

当用户 ID 可用时,compareVersions 会将其添加到每项变化的元数据中。使用 renderDecorations 为每位用户设置颜色。

const colors = [
  '#FAF594',
  '#958DF1',
  '#F98181',
  '#70CFF8',
  '#FBBC88',
  '#94FADB',
  '#B9F18D',
  '#6EE7B7',
]
const colorMapping = new Map()

const getUserColor = (userId) => {
  if (!colorMapping.has(userId)) {
    colorMapping.set(userId, colors[(userId || '').length % colors.length])
  }

  return colorMapping.get(userId)
}

const getDiffUserColor = ({ userId, diffType }) =>
  `${getUserColor(userId)}${diffType === 'removed' ? '50' : 'B0'}`

const getDecorationAttributes = ({ userId, backgroundColor, textColor }) => {
  if (!userId) {
    return {}
  }

  return {
    'data-diff-user-id': userId,
    style: `--diff-background-color: ${backgroundColor}; --diff-text-color: ${textColor}`,
  }
}

const getAddedDecorationAttributes = (userId) =>
  getDecorationAttributes({
    userId,
    backgroundColor: getDiffUserColor({ userId, diffType: 'added' }),
    textColor: '#000',
  })

const renderDecorations = ({ suggestion, defaultRenderDecorations }) => {
  const userId = suggestion.metadata?.userId
  const removedAttributes = getDecorationAttributes({
    userId,
    backgroundColor: getDiffUserColor({ userId, diffType: 'removed' }),
    textColor: '#777',
  })
  const addedAttributes = getAddedDecorationAttributes(userId)

  return defaultRenderDecorations({
    attributes: addedAttributes,
    subChangeAttributes: addedAttributes,
    replacementAttributes: removedAttributes,
    replacementSubChangeAttributes: removedAttributes,
  })
}

const getNestedChangeAttributes = ({ nestedChange }) =>
  getAddedDecorationAttributes(nestedChange.metadata?.userId)

将自定义渲染器传给 compareVersions。

editor.commands.compareVersions({
  versionA,
  versionB,
  displayOptions: { getNestedChangeAttributes, renderDecorations },
})

将以下样式添加到样式表。这些样式使用 renderDecorations 设置的 CSS 变量,并在悬停时显示用户 ID。

[data-diff-user-id] {
  cursor: pointer;
  display: inline-block;
  position: relative;
}

[data-diff-user-id]::before {
  background-color: #fff;
  border: 1px solid var(--black);
  border-radius: 6px;
  color: var(--black);
  content: attr(data-diff-user-id);
  font-size: 0.75rem;
  font-weight: normal;
  left: 0;
  line-height: normal;
  padding: 3px;
  position: absolute;
  text-align: center;
  top: 105%;
  visibility: hidden;
  white-space: nowrap;
  z-index: 1;
}

[data-diff-user-id]:hover::before {
  visibility: visible;
}

/* Current, added, and nested content */
.diff-suggestion,
.diff-suggestion-nested-change {
  background-color: var(--diff-background-color, rgb(226 255 232 / 69%));
  color: var(--diff-text-color, var(--black));
}

strong .diff-suggestion-diff {
  font-weight: normal;
}

em .diff-suggestion-diff {
  font-style: normal;
}

code:not(pre code) .diff-suggestion-diff {
  font-family: sans-serif;
}

.diff-suggestion-diff strong {
  font-weight: bold;
}

.diff-suggestion-diff em {
  font-style: italic;
}

.diff-suggestion-diff code:not(pre code) {
  font-family: monospace;
}

.diff-suggestion.diff-suggestion--change-group,
.diff-suggestion-nested-change--block {
  background-color: var(--diff-background-color, rgb(226 255 232 / 69%));
}

.diff-suggestion-sub-change,
.diff-suggestion-nested-change--inline {
  background-color: var(--diff-background-color, rgb(226 255 232 / 69%));
}

/* Replacement content from the previous version */
.diff-suggestion-diff {
  background-color: var(--diff-background-color, rgb(255 226 226 / 31%));
  color: var(--diff-text-color, var(--black));
  text-decoration: line-through;
}

.diff-suggestion-diff.diff-suggestion-diff--change-group {
  background-color: var(--diff-background-color, rgb(255 226 226 / 31%));
}

.diff-suggestion-diff-sub-change {
  background-color: var(--diff-background-color, rgb(255 226 226 / 31%));
}

.tiptap li:has(.diff-suggestion, .diff-suggestion-nested-change) {
  display: list-item;
}

.diff-suggestion-diff:has(> :is(blockquote, h1, h2, h3, h4, h5, h6, hr, ol, p, pre, ul)) {
  display: block;
  width: 100%;
}

.diff-suggestion:is(blockquote, h1, h2, h3, h4, h5, h6, hr, ol, p, pre, ul),
.diff-suggestion-nested-change--block:is(blockquote, h1, h2, h3, h4, h5, h6, hr, ol, p, pre, ul) {
  display: block;
  width: 100%;
}