slab / quill

Quill is a modern WYSIWYG editor built for compatibility and extensibility
https://quilljs.com
BSD 3-Clause "New" or "Revised" License
43.16k stars 3.35k forks source link

Questions about custom blots #4404

Open code997 opened 1 week ago

code997 commented 1 week ago

At first, I discovered the problem when I used blots/embed custom components in my project. At first, I thought it was a problem with my code. But then I looked at the more famous plug-in quill-mention and found that it also had the same problem.

Steps to reproduce quill-mention: (Website: https://quill-mention.com/)

  1. Enter @ in the editor and select an option.
  2. Manually delete the space after @ and adjust the input method to Chinese.
  3. Enter, before hitting the space to confirm the input content, you will find that the temporary English in the input box will cause the style to be disordered.
  4. After tapping the space to confirm the input content, the cursor moves to the front of the input content.

Below is my screen recording, please help me take a look at it, thank you

https://github.com/user-attachments/assets/bbf8509e-6136-4c6c-a381-302ebd048b21

code997 commented 1 week ago

You can see the source code of quill-memtion on github. The code for the custom emoji expression in my project is as follows:

import { Emoji } from '@/interface.d';
import './index.css';

const Embed:any = Quill.import('blots/embed');

// 定义 EmojiBlot 类,继承自 Embed
class EmojiBlot extends Embed {
  // 构造函数
  constructor(scroll: any, node: HTMLElement) {
    super(scroll, node);
    this.clickHandler = this.clickHandler.bind(this);
  }

  // 静态方法,创建 emoji 节点
  static create(data: Emoji): HTMLElement {
    const node = super.create();
    node.classList.add('emoji-wrap');
    this.createEmojiNode(node, data);
    return node;
  }

  // 静态方法,创建 emoji 的图片节点
  static createEmojiNode(domNode: HTMLElement, data: Emoji): HTMLImageElement {
    const { path, name } = data;
    const emojiDom = document.createElement('img');
    emojiDom.className = 'emoji';
    emojiDom.src = path;
    this.setDataValues(domNode, { name, path });
    domNode.appendChild(emojiDom);
    return emojiDom;
  }

  // 静态方法,设置数据属性
  static setDataValues(element: HTMLElement, data: { [key: string]: string }): void {
    const domNode = element;
    Object.keys(data).forEach((key) => {
      domNode.dataset[key] = data[key];
    });
  }

  // 静态方法,移除数据属性
  static removeDataValues(element: HTMLElement, datas: string[]): void {
    const domNode = element;
    datas.forEach((key) => {
      delete domNode.dataset[key];
    });
  }

  // 空的 clickHandler 方法
  clickHandler(): void {}

  // 静态方法,获取节点的数据属性
  static value(domNode: HTMLElement): DOMStringMap {
    return domNode.dataset;
  }
}

// 设置静态属性
EmojiBlot.blotName = 'emoji';
EmojiBlot.tagName = 'span';

// 注册 EmojiBlot
Quill.register(EmojiBlot);