quill 富文本编辑器

Quill 是一种现代的 WYSIWYG 编辑器,具备兼容性和可扩展性。

https://github.com/quilljs/quill

安装

React 包装器:

npm i -S react-quill

图片上传

使用一个 type=file 的 input ,完整自定义图片上传的逻辑

<ReactQuill
  theme="snow"
  value={value || ''}
  onChange={onChange}
  onFocus={() => setFocus(true)}
  onBlur={() => setFocus(false)}
  modules={modules}
  ref={reactQuillRef}
/>
<input
  ref={uploadInputRef}
  type="file"
  accept="image/*"
  style={{ display: 'none' }}
  onChange={uploadInputChange}
/>

自己写 image 的 addHandler

const reactQuillRef = useRef<ReactQuill>(null);

//组件中定义选择图片的方法
const selectImage = () => {
  if (uploadInputRef && uploadInputRef.current) {
    uploadInputRef.current.click(); //点击modal的html结构中的input标签
  }
};

useEffect(() => {
  if (reactQuillRef.current) {
    const quill = reactQuillRef.current.getEditor();
    quill.getModule('toolbar').addHandler('image', selectImage);
  }
});
  • input 被模拟点击后,选择了图片,触发change,拿到上传的图
  • 上传后,拿到服务器返回的地址。再反写到 quill 中
const uploadInputChange = async (e: any) => {
  const url = await uploadFile(e.target.files[0]);

  if (reactQuillRef.current) {
    const quill = reactQuillRef.current.getEditor();
    const range = quill.getSelection();
    if (range && url) {
      const cursorPosition = range.index; //获取当前光标位置
      quill.insertEmbed(cursorPosition, 'image', url); //插入图片
      quill.setSelection(cursorPosition + 1, 1); //光标位置加1
    }
  }
};

自定义按钮

在 modules - toolbar - container 中写一个自定义名称的按钮

export const modules = {
  toolbar: {
    container: [
      // ... toolbar 各按钮
      ['hcustom'], // 自定义按钮
    ],
  },
};

相当于给 quill 工具栏增加了一个 .ql-hcustom 按钮,给这个按钮绑定点击事件即可:

useEffect(() => {
  let hCustomButton: HTMLButtonElement | null = null;
  if (wrapperRef.current) {
    hCustomButton = wrapperRef.current.querySelector('.ql-hcustom');
    if (hCustomButton) {
      hCustomButton.addEventListener('click', handleHCustomButtonClick);
    }
  }
  return () => {
    if (hCustomButton) {
      hCustomButton.removeEventListener('click', handleHCustomButtonClick);
    }
  };
});

给这个按钮 :after 设置图标

.ql-hcustom:after {
  content: 'Ω';
}

汉化

汉化: https://www.jianshu.com/p/e0469b50f2b3

.ql-snow .ql-tooltip[data-mode='link']::before {
  content: '\94FE\63A5';
}
.ql-snow .ql-tooltip.ql-editing a.ql-action::after {
  content: '\786E\5B9A';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='small']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='small']::before {
  content: '\5C0F\53F7\5B57\4F53';
}
.ql-snow .ql-picker.ql-size .ql-picker-label::before,
.ql-snow .ql-picker.ql-size .ql-picker-item::before {
  content: '\6B63\5E38\5927\5C0F';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='large']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='large']::before {
  content: '\5927\53F7\5B57\4F53';
}
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value='huge']::before,
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value='huge']::before {
  content: '\8D85\5927\5B57\4F53';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='1']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='1']::before {
  content: '\6807\9898\0020\0031';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='2']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='2']::before {
  content: '\6807\9898\0020\0032';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='3']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='3']::before {
  content: '\6807\9898\0020\0033';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='4']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='4']::before {
  content: '\6807\9898\0020\0034';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='5']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='5']::before {
  content: '\6807\9898\0020\0035';
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value='6']::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value='6']::before {
  content: '\6807\9898\0020\0036';
}
.ql-snow .ql-picker.ql-header .ql-picker-label::before,
.ql-snow .ql-picker.ql-header .ql-picker-item::before {
  content: '\666E\901A\6587\672C';
}
.ql-snow .ql-tooltip::before {
  content: '\8BBF\95EE\94FE\63A5';
}
.ql-snow .ql-tooltip a.ql-action::after {
  content: '\7F16\8F91';
}
.ql-snow .ql-tooltip a.ql-remove::before {
  content: '\79FB\9664\000A';
}
.ql-snow .ql-tooltip[data-mode='video']::before {
  content: '\89C6\9891\94FE\63A5';
}

css 渲染

渲染时也使用同一套 css ,保持管理端和用户端一致:

<link href="/static/libs/quill/quill.snow.css" rel="stylesheet">

渲染时,使用 ql-snow 和 ql-editor 包裹:

<div class="post-content ql-snow">
    <div class="ql-editor">
        <?= HtmlPurifier::process($post->content); ?>
    </div>
</div>
本文收录于专栏
收集一些好用的前端开源库,主要是 npm 包