比较文档版本
Snapshot Compare 允许你并排查看两个文档版本并突出显示所有更改。使用它来跟踪编辑,审查贡献,并恢复早期状态。
Snapshot Compare 扩展为 快照 添加了额外的功能,允许你可视化比较两个版本之间的更改,以便跟踪添加、删除或修改的内容。这些比较称为 diffs。
访问私有注册表
Snapshot Compare 扩展已发布在 Tiptap 的私有 npm 注册表中。通过遵循 私有注册表指南 集成该扩展。如果你已经验证了你的 Tiptap 账号,可以直接进入 #安装。
安装
从我们的私有注册表安装扩展:
npm install @tiptap-pro/extension-snapshot-compare设置
你可以使用以下选项配置 SnapshotCompare 扩展:
| 设置 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| provider | TiptapCollabProvider | null | 协作提供者实例 |
| mapDiffToDecorations | function | () => {} | 控制将 diff 映射到装饰以显示其内容 |
const provider = new TiptapCollabProvider({
// ...
})
const editor = new Editor({
// ...
extensions: [
// ...
SnapshotCompare.configure({
provider,
}),
],
})使用 mapDiffToDecorations 进行 diff 装饰
该扩展具有默认映射 (defaultMapDiffToDecorations) 来表示 diffs 作为 ProseMirror 装饰。
对于更复杂的集成和控制,你可以使用 mapDiffToDecorations 选项自定义此映射。
示例: 对内联插入应用自定义预定义背景颜色
SnapshotCompare.configure({
mapDiffToDecorations: ({ diff, tr, editor, defaultMapDiffToDecorations }) => {
if (diff.type === 'inline-insert') {
// 返回 prosemirror 装饰或 null
return Decoration.inline(
diff.from,
diff.to,
{
class: 'diff',
style: {
backgroundColor: diff.attribution.color.backgroundColor,
},
},
// 将 diff 作为装饰的规格传递,这对于 `extractAttributeChanges` 是必需的
{ diff },
)
}
// 回退到默认映射
return defaultMapDiffToDecorations({
diff,
tr,
editor,
attributes: {
// 为装饰添加自定义属性
'data-tiptap-user-id': myUserIdMapping[diff.attribution.userId],
},
})
},
})存储
SnapshotCompare 存储对象包含以下属性:
| 键 | 类型 | 描述 |
|---|---|---|
| isPreviewing | boolean | 指示 diff 视图是否处于激活状态 |
| diffs | Diff[] | 在 diff 视图中显示的 diffs |
| previousContent | JSONContent | null | 应用 diff 视图之前的内容 |
使用 isPreviewing 属性检查 diff 视图是否当前处于激活状态:
if (editor.storage.snapshotCompare.isPreviewing) {
// diff 视图当前处于激活状态
}使用 diffs 属性访问在 diff 视图中显示的 diffs:
editor.storage.snapshotCompare.diffs属性 previousContent 由扩展内部使用,用于在退出 diff 视图时恢复内容。通常,你不需要直接与之交互。
命令
| 命令 | 描述 |
|---|---|
compareVersions | 比较两个版本并在编辑器中渲染 diff |
showDiff | 给定更改跟踪变换,在编辑器中显示 diff |
hideDiff | 隐藏 diff 并恢复之前的内容 |
compareVersions
使用 compareVersions 命令计算并显示两个文档版本之间的差异。
editor.chain().compareVersions({
fromVersion: 1,
})选项
你可以传入额外的选项以更好地控制 diff 过程:
| 键 | 描述 |
|---|---|
fromVersion | 比较的起始版本。fromVersion 和 toVersion 之间的顺序是灵活的 |
toVersion | 要比较的结束版本(默认:最新版本) |
hydrateUserData | 向每个用户的更改添加上下文数据 |
onCompare | 手动处理 diff 结果 |
enableDebugging | 启用详细日志记录以进行故障排查 |
使用 hydrateUserData 添加元数据
每个 diff 具有 attribution 字段,允许你使用 hydrateUserData 回调函数添加附加元数据。
注意
请注意,userId 由 TiptapCollabProvider 填充,应该用于识别进行更改的用户。如果提供者未提供 user 字段,则 userId 将为 null。有关更多信息,请参见 TiptapCollabProvider 文档。
示例: 基于用户为 diffs 着色
const colorMapping = new Map([
['user-1', '#ff0000'],
['user-2', '#00ff00'],
['user-3', '#0000ff'],
])
editor.chain().compareVersions({
fromVersion: 1,
toVersion: 3,
hydrateUserData: ({ userId }) => {
return {
color: {
backgroundColor: colorMapping.get(userId),
},
}
},
})
editor.storage.snapshotCompare.diffs[0].attribution.color.backgroundColor // '#ff0000'使用 onCompare 自定义 diff 过程
如果你需要更好地控制 diff 过程,可以使用 onCompare 选项接收结果并自行处理。
示例: 按用户过滤 diffs
editor.chain().compareVersions({
fromVersion: 1,
toVersion: 3,
onCompare: (ctx) => {
if (ctx.error) {
// 处理 diff 过程中的错误
console.error(ctx.error)
return
}
// 过滤 diffs 以仅显示特定用户的更改
const diffsToDisplay = ctx.diffSet.filter((diff) => diff.attribution.userId === 'user-1')
editor.commands.showDiff(ctx.tr, { diffs: diffsToDisplay })
},
})showDiff
使用 showDiff 命令在编辑器中显示 diff,使用更改跟踪变换 (tr)。这代表自上一个快照以来对文档所做的所有更改。你可以使用此变换在编辑器中显示 diff。
通常,在使用 compareVersions、onCompare 自定义或过滤 diffs 后使用此命令。
showDiff 命令临时替换当前编辑器内容为 diff 视图,显示版本之间的差异。它还会缓存当前在编辑器中显示的内容,以便你稍后可以恢复它。
// 这将显示在编辑器中记录的更改跟踪变换的变更
editor.commands.showDiff(tr)选项
你可以传入额外的选项以控制如何显示 diffs:
| 键 | 描述 |
|---|---|
| diffs | 一个要可视化的 diffs 数组 |
示例: 显示特定的 diffs
// 这将仅显示由 ID 为 'user-1' 的用户所做的 diffs
const diffsToDisplay = tr.toDiff().filter((diff) => diff.attribution.userId === 'user-1')
editor.commands.showDiff(tr, { diffs: diffsToDisplay })hideDiff
使用 hideDiff 命令隐藏 diff 并恢复之前的内容。
// 这将隐藏 diff 视图并恢复之前的内容
editor.commands.hideDiff()使用 NodeView (高级)
在使用 自定义节点视图 时,默认的 diff 映射可能无法按预期工作。你可以自定义映射并直接在自定义节点视图中渲染 diffs。
使用 extractAttributeChanges 助手提取节点中的属性更改。这使你能够访问节点的先前和当前属性,从而可能突出显示自定义节点视图中的属性更改。
注意
当将 diffs 映射到装饰时,你需要将 diff 作为装饰的 spec 传递。这对于 extractAttributeChanges 的正常工作是必需的。
示例: 自定义标题节点视图以显示更改
import { extractAttributeChanges } from '@tiptap-pro/extension-snapshot-compare'
const Heading = BaseHeading.extend({
addNodeView() {
return ReactNodeViewRenderer(({ node, decorations }) => {
const { before, after, isDiffing } = extractAttributeChanges(decorations)
return (
<NodeViewWrapper style={{ position: 'relative' }}>
{isDiffing && before.level !== after.level && (
<span
style={{
position: 'absolute',
right: '100%',
fontSize: '14px',
color: '#999',
backgroundColor: '#f0f0f070',
}}
// 显示级别属性的变化
>
#<s>{before.level}</s>
{after.level}
</span>
)}
<NodeViewContent as={`h${node.attrs.level ?? 1}`} />
</NodeViewWrapper>
)
})
},
})技术细节
Diff
Diff 是一个表示文档中所做更改的对象。它包含以下属性:
| 属性 | 类型 | 描述 |
|---|---|---|
type | 'inline-insert' | 'inline-delete' | 'block-insert' | 'block-delete' | 'inline-update' | 'block-update' | 所做更改的类型 |
from | number | 更改的起始位置 |
to | number | 更改的结束位置 |
content | Fragment | 添加或删除的内容 |
attribution | Attribution | 有关更改的元数据,例如执行更改的用户 |
attributes? | Record<string, any> | 更改 后 的属性;仅对 'inline-update' 和 'block-update' diffs 可用 |
previousAttributes? | Record<string, any> | 更改 前 的属性;仅对 'inline-update' 和 'block-update' diffs 可用 |
DiffSet
DiffSet 是一个 Diff 对象数组,每个对象对应于特定的更改,例如插入、删除或更新。你可以迭代数组以检查单个更改或基于 diff 类型应用自定义逻辑。
const diffsToDisplay = diffSet.filter((diff) => diff.attribution.userId === 'user-1')
// 在编辑器中显示过滤后的 diffs
editor.commands.showDiff(tr, { diffs: diffsToDisplay })Attribution
Attribution 对象包含有关更改的元数据。它包括以下属性:
| 属性 | 类型 | 描述 |
|---|---|---|
type | 'added' | 'removed' | 指示所做更改的类型 |
userId | string | undefined | 执行更改的用户的 ID |
id | Y.ID | undefined | 执行更改的用户的 Y.js 客户端 ID |
你可以扩展 Attribution 接口以包括其他属性:
declare module '@tiptap-pro/extension-snapshot-compare' {
interface Attribution {
userName: string
}
}ChangeTrackingTransform
ChangeTrackingTransform 是一个记录对文档所做更改的类(基于 ProseMirror 的 Transform)。
它表示一个变换,其步骤描述了从文档的一个版本到另一个版本所做的所有更改。它具有以下属性:
| 属性 | 类型 | 描述 |
|---|---|---|
steps | ChangeTrackingStep[] | 表示对文档所做更改的步骤数组 |
doc | Node | 应用更改后文档的状态 |
before | Node | 应用更改前文档的状态 |
ChangeTrackingStep
ChangeTrackingStep 是一个基于 ProseMirror 的 ReplaceStep 类表示的类,代表对文档所做的单个更改。它具有以下属性:
| 属性 | 类型 | 描述 |
|---|---|---|
attribution | Attribution | 有关更改的元数据,例如执行更改的用户 |
类型
以下是 SnapshotCompare 扩展的完整 TypeScript 定义:
declare module '@tiptap/core' {
interface Commands<ReturnType> {
snapshotCompare: {
/**
* 给定更改跟踪变换,在编辑器内显示 diff
*/
showDiff: (tr: ChangeTrackingTransform, options?: { diffs?: DiffSet }) => ReturnType
/**
* 隐藏 diff 并恢复之前的内容
*/
hideDiff: () => ReturnType
/**
* 比较两个版本并将 diff 渲染到编辑器中
*/
compareVersions: <
T extends Pick<Attribution, 'color'> & Record<string, any> = Pick<Attribution, 'color'> &
Record<string, any>,
>(options: {
/**
* 开始 diff 的版本
*/
fromVersion: number
/**
* 结束 diff 的版本
* 如果未提供,将使用最新快照
*/
toVersion?: number
/**
* 允许为每个用户的更改添加上下文数据
*/
hydrateUserData?: (context: {
/**
* 事件的类型
*/
type: 'added' | 'removed'
/**
* 操作此内容的 userId
*/
userId: string | undefined
/**
* 内容的 yjs 标识符
*/
id?: y.ID
}) => T
/**
* 如果提供,允许自定义渲染 diffs 到编辑器的行为。
* @note 默认行为是立即显示 diff。
*/
onCompare?: (
context:
| {
error?: undefined
/**
* 编辑器实例
*/
editor: Editor
/**
* 带有归属元数据的变换所有更改
*/
tr: ChangeTrackingTransform<Attribution & T>
/**
* 作为 diffs 数组表示的更改
*/
diffSet: DiffSet<Attribution & T>
}
| {
error: Error
},
) => void
/**
* 详细记录 diffing 过程以帮助追踪错误
*/
enableDebugging?: boolean
}) => ReturnType
}
}
}
export type SnapshotCompareOptions = {
/**
* tiptap 提供者实例。这对于扩展示计算 diffs 是必需的,但不是必需的以显示它们。
* 也可以传递 TiptapCollabProvider 实例。
*/
provider: TiptapCollabProvider | null
/**
* 这允许你控制将 diff 映射到装饰以显示该 diff 内容的方式
*/
mapDiffToDecorations?: (options: {
/**
* 要映射到装饰的 diff
*/
diff: Diff
/**
* 编辑器实例
*/
editor: Editor
/**
* 更改跟踪变换
*/
tr: ChangeTrackingTransform
/**
* 将 diff 映射到装饰的默认实现
*/
defaultMapDiffToDecorations: typeof defaultMapDiffToDecorations
}) => ReturnType<typeof defaultMapDiffToDecorations>
}
export type SnapshotCompareStorageInactive = {
/**
* diff 视图当前是否处于激活状态
*/
isPreviewing: false
/**
* diff 视图应用之前的内容
*/
previousContent: null
/**
* 已应用的更改跟踪变换
* 因为 diff 视图没有激活,它当前是空的
*/
diffs: DiffSet
/**
* 已应用的更改跟踪变换
*/
tr: null
}
export type SnapshotCompareStorageActive = {
/**
* diff 视图当前是否处于激活状态
*/
isPreviewing: true
/**
* diff 视图应用之前的内容
*/
previousContent: JSONContent
/**
* 已应用的更改跟踪变换
*/
diffs: DiffSet
/**
* 已应用的更改跟踪变换
*/
tr: ChangeTrackingTransform
}
export type SnapshotCompareStorage = SnapshotCompareStorageInactive | SnapshotCompareStorageActive