npm package: @knotx/plugins-history
Description: History Plugin for Knotx
Author: boenfu
Latest version: 0.5.10

# @knotx/plugins-history

历史记录插件，为 KnotX 提供撤销/重做功能。

## 安装

```bash
npm install @knotx/plugins-history
```

## 概述

History 插件为 KnotX 提供了完整的历史记录管理功能，包括撤销、重做、标记状态等。该插件能够跟踪所有数据变化，并提供便捷的历史记录操作接口。

## 实现原理

History 插件的核心实现原理：

1. **操作拦截**：拦截所有数据管理器的操作，记录变化前后的状态
2. **缓冲机制**：使用防抖机制批量处理操作，避免频繁的历史记录创建
3. **反向操作**：为每个操作生成反向操作，实现撤销功能
4. **状态标记**：支持标记特定状态，快速回到指定的历史点

## 依赖关系

### 核心依赖
- `@knotx/core`：提供基础插件架构和数据管理
- `@knotx/decorators`：提供装饰器支持
- `rxjs`：提供响应式编程支持

### 自动依赖
- `@knotx/core` 中的 `nodesManager` 和 `edgesManager`：自动注册到历史记录系统

## API 文档

### 主要类

#### History

历史记录插件的主要类，继承自 `BasePlugin`。

```typescript
export class History<T extends Record<string, IData> = Record<string, any>> extends BasePlugin<'history', HistoryConfig> {
  name = 'history' as const
}
```

### 配置选项

#### HistoryConfig

```typescript
export interface HistoryConfig {
  /** 最大历史记录数量 */
  maxHistory?: number
  /** 防抖时间（毫秒） */
  debounceTime?: number
}
```

### 插件数据 (PluginData)

History 插件提供以下数据：

```typescript
interface PluginData {
  history: {
    ref: IHistory
    canUndo: boolean
    canRedo: boolean
    registerDataManager: (dataManager: DataManager<any>) => (() => void)
  }
}
```

### 接口定义

#### IHistory

```typescript
export interface IHistory {
  isUndoRedo: boolean
  undo: () => void
  redo: () => void
  addTag: (tag: string) => void
  removeTag: (tag: string) => void
  toTag: (tag: string) => void
  clearTags: () => void
}
```

#### HistoryState

```typescript
export interface HistoryState<T extends Record<string, IData> = Record<string, any>> {
  value: {
    [key: string]: HistoryOperation<T>[]
  }
  timestamp: number
}
```

## 使用示例

### 基本用法

```typescript
import { History } from '@knotx/plugins-history'

const engine = new Engine({
  plugins: [History],
  pluginConfig: {
    history: {
      maxHistory: 50,
      debounceTime: 100,
    },
  },
})
```

### 撤销和重做操作

```typescript
const engine = new Engine({
  plugins: [History],
})

const historyData = engine.getPluginData('history')

// 撤销操作
if (historyData.canUndo) {
  historyData.ref.undo()
}

// 重做操作
if (historyData.canRedo) {
  historyData.ref.redo()
}
```

### 监听历史记录状态

```tsx
class HistoryUIPlugin extends BasePlugin {
  @inject.history.canUndo()
  canUndo!: boolean

  @inject.history.canRedo()
  canRedo!: boolean

  @inject.history.ref()
  history!: IHistory

  @panel('bottom')
  render() {
    return (
      <div>
        <button
          disabled={!this.canUndo}
          onClick={() => this.history.undo()}
        >
          撤销
        </button>
        <button
          disabled={!this.canRedo}
          onClick={() => this.history.redo()}
        >
          重做
        </button>
      </div>
    )
  }
}
```

### 状态标记功能

```typescript
const engine = new Engine({
  plugins: [History],
})

const historyData = engine.getPluginData('history')

// 标记当前状态
historyData.ref.addTag('checkpoint-1')

// 在进行一系列操作后...
// 回到标记的状态
historyData.ref.toTag('checkpoint-1')

// 移除标记
historyData.ref.removeTag('checkpoint-1')

// 清空所有标记
historyData.ref.clearTags()
```

### 自定义数据管理器集成

```typescript
class CustomDataManager extends DataManager<MyData> {
  constructor() {
    super('custom')
  }
}

const engine = new Engine({
  plugins: [History],
})

const historyData = engine.getPluginData('history')
const customManager = new CustomDataManager()

// 注册自定义数据管理器
const unregister = historyData.registerDataManager(customManager)

// 取消注册
unregister()
```

## 高级功能

### 批量操作优化

```typescript
class BatchHistoryPlugin extends BasePlugin {
  @inject.history.ref()
  history!: IHistory

  performBatchOperation() {
    // 批量操作会自动被防抖处理
    engine.dispatchNodeOperation({
      type: 'add',
      data: { id: 'node1', /* ... */ },
    })

    engine.dispatchNodeOperation({
      type: 'add',
      data: { id: 'node2', /* ... */ },
    })

    // 这些操作会被合并为一个历史记录条目
  }
}
```

### 条件性历史记录

```typescript
class ConditionalHistoryPlugin extends BasePlugin {
  @inject.history.ref()
  history!: IHistory

  @inject.nodesManager()
  nodesManager!: DataManager<Node>

  performTemporaryOperation() {
    // 检查是否在撤销/重做过程中
    if (this.history.isUndoRedo) {
      // 在撤销/重做过程中，避免创建新的历史记录
      return
    }

    // 正常操作
    this.nodesManager.dispatch({
      type: 'update',
      data: { id: 'node1', /* ... */ },
    })
  }
}
```

### 历史记录状态监听

```typescript
class HistoryMonitorPlugin extends BasePlugin {
  @inject.history.ref()
  history!: IHistory

  @subscribe.history.canUndo()
  onCanUndoChange(canUndo: boolean) {
    console.log('Can undo:', canUndo)
  }

  @subscribe.history.canRedo()
  onCanRedoChange(canRedo: boolean) {
    console.log('Can redo:', canRedo)
  }

  // 自定义历史记录监听
  @OnInit
  init() {
    // 监听历史记录变化
    this.history.onHistoryChange?.subscribe((state) => {
      console.log('History changed:', state)
    })
  }
}
```

## 性能优化

### 防抖配置

```typescript
const engine = new Engine({
  plugins: [History],
  pluginConfig: {
    history: {
      debounceTime: 200, // 增加防抖时间以减少历史记录条目
    },
  },
})
```

### 历史记录限制

```typescript
const engine = new Engine({
  plugins: [History],
  pluginConfig: {
    history: {
      maxHistory: 20, // 减少最大历史记录数量以节省内存
    },
  },
})
```

## 文件目录结构

```
packages/plugins-history/
├── src/
│   ├── history.ts              # 主要实现文件
│   └── index.ts               # 导出文件
├── dist/                      # 构建输出目录
├── package.json              # 包配置文件
├── build.config.ts           # 构建配置
├── tsconfig.json             # TypeScript 配置
├── eslint.config.mjs         # ESLint 配置
└── CHANGELOG.md              # 更新日志
```

### 核心文件说明

- **history.ts**：包含 History 插件的主要实现，包括操作拦截、历史记录管理和状态标记功能
- **index.ts**：导出 History 类和相关类型定义

## 最佳实践

### 内存管理

1. **合理设置历史记录数量**：根据应用需求设置合适的 `maxHistory` 值
2. **及时清理标记**：不再需要的状态标记应及时清理
3. **避免过度防抖**：过长的防抖时间可能影响用户体验

### 用户体验

1. **提供撤销/重做按钮**：在 UI 中提供清晰的撤销/重做操作入口
2. **显示操作状态**：根据 `canUndo` 和 `canRedo` 状态禁用/启用按钮
3. **键盘快捷键**：支持 `Ctrl+Z` 和 `Ctrl+Y` 快捷键

### 开发调试

1. **监听历史记录状态**：在开发阶段监听历史记录变化以便调试
2. **使用状态标记**：在关键操作点设置状态标记，方便测试和调试

## 注意事项

1. **大量数据性能**：历史记录会占用内存，大量数据时注意性能优化
2. **异步操作**：异步操作可能导致历史记录顺序混乱，需要特别注意
3. **数据一致性**：确保撤销/重做操作不会破坏数据的一致性
4. **循环引用**：避免在历史记录处理过程中创建循环引用

## 许可证

MIT