# 复制与粘贴

Blockly 对多种内置组件提供了复制、剪切和粘贴能力,例如块、工作区注释和气泡。

# 默认剪贴板行为

Blockly Core 内置了一组键盘快捷键,可对块、工作区注释以及任何实现了 ICopyableIDraggableIDeletable 的组件执行剪切、复制和粘贴。
它也内置了块“复制副本”的上下文菜单项。你可以按需修改或移除这些默认快捷键和菜单项。

默认剪贴板实现下,可复制对象只能粘贴回它原本所在工作区,或者在对象来自 flyout 时粘贴到该 flyout 的目标工作区。
这意味着你可以把 flyout 里的块复制到其目标工作区,但不能把一个主工作区里的块直接复制到另一个主工作区。

# 跨工作区和标签页复制粘贴

如果你希望用户把块从一个工作区复制到另一个工作区,甚至复制到另一个浏览器标签页中的同应用实例,可安装插件:

# 自定义剪贴板行为

如果你需要不同的行为,可以卸载 Blockly 自带的快捷键并改装自己的快捷键逻辑。
Blockly 提供了 Blockly.clipboard 命名空间 方法来执行复制、粘贴,或做更细粒度控制,例如设置对象来源工作区。

# 自定义可复制对象

任意对象要接入复制粘贴系统,需要用到 5 个接口:

其中前 3 个用于描述“可被复制”的对象,ICopyData 用于描述复制后的数据,IPaster 用于把复制数据还原为可复制对象。
每一种 ICopyable 都必须有一个对应的 IPaster 来粘贴该数据。

只要对象实现了 ICopyableIDraggableIDeletable,并且有匹配的 ICopyDataIPaster,就能自动接入默认剪贴板系统。

需要强调的是,真正需要“自定义 copyable 或 paster”的场景很少(例如 multiselect plugin (opens new window))。
因为 copyable 对象是可渲染对象,而 Blockly 工作区可新增的渲染对象类型本身很有限,主要只有块、气泡和工作区注释。

# 实现可复制对象

要创建 copyable 对象,需实现 ICopyableIDraggableIDeletable。后两者用于保证粘贴后的对象可被操作和删除。

# 可选中

ICopyable 继承了 ISelectable,所以也需要实现其属性和方法。
可选中是必须的,因为快捷键复制逻辑会根据“当前选中对象”判断复制目标。

class MyCopyable implements ISelectable {
  constructor(workspace) {
    this.id = Blockly.utils.idGenerator.genUid();
    this.workspace = workspace;
  }

  select() {
    // 在视觉上标记为已选中。
  }

  unselect() {
    // 在视觉上标记为未选中。
  }
}

# 可复制

ICopyable 只有一个必需方法 toCopyData,返回可 JSON 序列化的状态,用于重建对象。

复制数据还必须包含 paster 字段,用于标识应该由哪个已注册 paster 来处理该数据。

class MyCopyable implements ICopyable {
  constructor(workspace, state) {
    this.workspace = workspace;
    this.myState = state;
  }

  toCopyData() {
    return {
      // 这个字符串要与 paster 注册时使用的名称一致。
      paster: 'MY_PASTER',
      state: this.myState,
    };
  }
}

ICopyable 还有可选方法 isCopyable,用于返回对象当前是否可复制。

# 可拖拽与可删除

关于 IDraggableIDeletable 的实现,可参考自定义可拖拽对象

# 实现粘贴器

要创建 paster,需要实现 IPaster 接口。它只有一个 paste 方法,接收:

  • 被粘贴对象的 copy data
  • 粘贴目标工作区
  • 可选坐标(粘贴位置)
class MyPaster implements IPaster {
  paste(copyData, workspace, coordinate) {
    return new MyCopyable(workspace, copyData.state);
    // 可选:把对象放到 coordinate 指定位置。
    // 可选:粘贴后自动选中该对象。
  }
}

# 注册

实现 paster 后,需要注册,才能通过 copy data 里的 paster 字段找到对应处理器:

// 该字符串需与 copy data 中的 'paster' 字段一致。
Blockly.clipboard.registry.register('MY_PASTER', new MyPaster());