---
title: "比较版本"
description: "比较两个 Tiptap Collaboration 版本更新并显示差异。"
canonical_url: "https://tiptap.zhcndoc.com/compare/guides/compare-versions"
---

# 比较版本

比较两个 Tiptap Collaboration 版本更新并显示差异。

使用 [`compareVersions` 命令](https://tiptap.zhcndoc.com/compare/api-reference/commands.md#compareversions) 比较两个 Yjs 版本更新并显示变化。

> **Interactive demo:** [CompareVersions](https://embed-pro.tiptap.dev/preview/Extensions/CompareVersions)

## 获取版本

首先使用 [`getVersions` 方法](https://tiptap.zhcndoc.com/compare/api-reference/utilities.md#getversions)获取版本。如果使用[快照扩展](https://tiptap.zhcndoc.com/collaboration/documents/snapshot.md)，可以从 `editor.storage.snapshot.versions` 获取可用版本。数组中的每个项目都包含保存版本 ID 的 `version` 属性。将这些版本 ID 传给 `getVersions`。

有关如何保存快照和获取版本数据，请参阅[快照指南](https://tiptap.zhcndoc.com/collaboration/documents/snapshot.md#access-version-data-directly-from-storage)。

```ts
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`。

```ts
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` 命令](https://tiptap.zhcndoc.com/compare/api-reference/commands.md#compareversions)会比较两个版本，并立即显示变化。

## 设置变更样式

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

```ts
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`。

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

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

```css
[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%;
}
```
