首页
/ 使用Slab/Quill的Parchment模块克隆Medium编辑器体验

使用Slab/Quill的Parchment模块克隆Medium编辑器体验

2025-07-05 01:50:28作者:秋泉律Samson

前言

在现代富文本编辑器的开发中,保持一致的编辑体验面临两大挑战:数据一致性和行为可预测性。DOM本身无法满足这两点要求,因此现代编辑器通常需要维护自己的文档模型来表示内容。在Slab/Quill项目中,Parchment模块就是这样的解决方案。

Parchment基础架构

Parchment是Quill的文档模型实现,它具有以下核心特点:

  1. 树形结构:与DOM类似,Parchment文档也是树形结构
  2. Blot节点:Parchment中的节点称为Blot,是对DOM节点的抽象
  3. 预定义Blot类型:包括Scroll、Block、Inline、Text和Break等基础类型

当用户输入时,Text blot会自动与对应的DOM Text节点同步,回车操作会创建新的Block blot。Parchment要求能够包含子节点的Blot必须至少有一个子节点,因此空的Block会被填充Break blot。

实现基本文本格式

加粗和斜体实现

要实现基本的文本格式如加粗和斜体,我们需要继承Inline基类:

const Inline = Quill.import('blots/inline');

class BoldBlot extends Inline {
  static blotName = 'bold';
  static tagName = 'strong';
}

class ItalicBlot extends Inline {
  static blotName = 'italic';
  static tagName = 'em';
}

Quill.register(BoldBlot);
Quill.register(ItalicBlot);

注册后,我们就可以使用Quill的完整API来操作这些格式:

quill.insertText(0, '测试文本', { bold: true });
quill.formatText(0, 4, 'italic', true);

链接实现

链接的实现比简单的布尔格式更复杂,因为需要存储URL信息:

class LinkBlot extends Inline {
  static blotName = 'link';
  static tagName = 'a';

  static create(value) {
    const node = super.create();
    node.setAttribute('href', value);
    node.setAttribute('target', '_blank');
    return node;
  }

  static formats(node) {
    return node.getAttribute('href');
  }
}

块级元素实现

引用块和标题

块级元素的实现方式与行内元素类似,但继承的是Block基类:

const Block = Quill.import('blots/block');

class BlockquoteBlot extends Block {
  static blotName = 'blockquote';
  static tagName = 'blockquote';
}

class HeaderBlot extends Block {
  static blotName = 'header';
  static tagName = ['H1', 'H2'];
  
  static formats(node) {
    return HeaderBlot.tagName.indexOf(node.tagName) + 1;
  }
}

分隔线实现

分隔线是我们的第一个叶子节点Blot实现:

const BlockEmbed = Quill.import('blots/block/embed');

class DividerBlot extends BlockEmbed {
  static blotName = 'divider';
  static tagName = 'hr';
}

插入分隔线时需要特别注意保持用户选择:

onClick('#divider-button', () => {
  const range = quill.getSelection(true);
  quill.insertText(range.index, '\n', Quill.sources.USER);
  quill.insertEmbed(range.index + 1, 'divider', true, Quill.sources.USER);
  quill.setSelection(range.index + 2, Quill.sources.SILENT);
});

多媒体内容实现

图片实现

图片使用对象作为值,可以存储更多元信息:

class ImageBlot extends BlockEmbed {
  static blotName = 'image';
  static tagName = 'img';

  static create(value) {
    const node = super.create();
    node.setAttribute('alt', value.alt);
    node.setAttribute('src', value.url);
    return node;
  }

  static value(node) {
    return {
      alt: node.getAttribute('alt'),
      url: node.getAttribute('src')
    };
  }
}

视频实现

视频的实现方式与图片类似:

class VideoBlot extends BlockEmbed {
  static blotName = 'video';
  static tagName = 'iframe';

  static create(url) {
    const node = super.create();
    node.setAttribute('src', url);
    node.setAttribute('frameborder', '0');
    node.setAttribute('allowfullscreen', true);
    return node;
  }

  static value(node) {
    return node.getAttribute('src');
  }
}

高级主题:自定义内容类型

通过Parchment的强大扩展能力,我们可以实现更复杂的内容类型,如嵌入式推文:

class TweetBlot extends BlockEmbed {
  static blotName = 'tweet';
  static tagName = 'div';

  static create(id) {
    const node = super.create();
    node.dataset.id = id;
    // 这里可以添加实际的推文嵌入逻辑
    return node;
  }

  static value(node) {
    return node.dataset.id;
  }
}

总结

通过Parchment模块,Slab/Quill提供了强大的文档模型抽象能力,使开发者能够:

  1. 精确控制编辑器支持的内容类型
  2. 确保文档结构的规范性和一致性
  3. 实现复杂的富文本编辑功能
  4. 扩展编辑器支持的自定义内容类型

这种架构使得构建类似Medium的富文本编辑体验变得可行且可维护,同时保证了编辑体验的一致性和数据结构的规范性。