quill编辑器使用

2023-11-20

  • 官方git: https://github.com/quilljs/quill/
  • 官方文档:https://quilljs.com/
  • 中文文档:https://kang-bing-kui.gitbook.io/quill/

编辑器是个正经编辑器,就是文档太不正经了

基本的一些使用文档还是可以的,基础用例代码如下

import React, { useEffect } from 'react';
import Quill, { QuillOptionsStatic } from 'quill';
import 'quill/dist/quill.snow.css';
import { TranslatorWrapper } from './translator';

const QuillEditor = () => {
  useEffect(() => {
    const toolbarOptions = [
      [{ header: [false, 1, 2, 3, 4, 5, 6] }],
      ['bold', 'italic', 'underline', 'strike'],
      [{ indent: '-1' }, { indent: '+1' }],
      [{ align: '' }, { align: 'center' }, { align: 'right' }],
      ['link'],
      ['image'],
    ];
    const options: QuillOptionsStatic = {
      // boundsDOM元素或者一个DOM元素的css选择器,其中编辑器的UI元素(例如:tooltips)应该被包含其中。目前,只考虑左右边界 
      // 这个属性很管用,特别是在一些布局中,不设置边界的话,ql-tooltip会溢出编辑器,导致被遮挡,并且有些时候,布局已经确定,z-index都不能这解决遮挡问题
      bounds: document.getElementById('quill_editor') as HTMLElement,
      debug: 'false',
      modules: {
        imageUpload: true,
        toolbar: toolbarOptions,
      },
      placeholder: '',
      theme: 'snow',
    };
    const editor = new Quill('#quill_editor', options);
    setQuillEditor(editor);
  }, []);

  return (
    <TranslatorWrapper>
      <div id="quill_editor"></div>
    </TranslatorWrapper>
  );
};

export default QuillEditor;

基本样例展示:

关于quill Toolbar中header中文展示处理

基于styled-components给quill编辑器的主容器增加一个包裹 TranslatorWrapper,通过修改::before属性,达到修改中文的目的;
那如果需要国际化,就可以通过参数处理来完成了

<TranslatorWrapper>
  <div id="quill_editor"></div>
</TranslatorWrapper>

样式代码:translator.js

export const TranslatorWrapper = styled.div`
  .ql-snow .ql-picker.ql-header .ql-picker-label::before,
  .ql-snow .ql-picker.ql-header .ql-picker-item::before {
    content: '正文';
    font-size: 14px;
  }
  .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: '标题 1';
    font-size: 14px;
  }
  .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: '标题 2';
    font-size: 14px;
  }
  .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: '标题 3';
    font-size: 14px;
  }
  .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: '标题 4';
    font-size: 14px;
  }
  .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: '标题 5';
    font-size: 14px;
  }
  .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: '标题 6';
    font-size: 14px;
  }
`;

关于React自定义组件的插入使用

注:这里的场景只是上传图片,所以对原来的上传图片按钮进行了替换,直接替换成一个React组件,其他新增一个按钮的自定义场景这次没有涉及

在前面基础应用代码中新增

import React, { useEffect } from 'react';
...
import ImageUploader from './ImageUploader.tsx';

const Image = Quill.import('formats/image');
Image.className = 'img-fluid';
Quill.register(Image, true);
Quill.register('modules/imageUpload', ImageUploader);

const QuillEditor = () => {
...

图片上传模块 ImageUploader.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import Quill, { QuillOptionsStatic } from 'quill';
import _ from 'lodash';
// 需要注入的React组件
import ImgButton from './ImgButton';

interface ImageUploaderInterface {
  quill: Quill;
  options: QuillOptionsStatic;
}
class ImageUploader implements ImageUploaderInterface {
  quill: Quill;
  options: QuillOptionsStatic;
  constructor(quill: Quill, options: QuillOptionsStatic) {
    this.quill = quill;
    this.options = options;

    // 获取editor的toolbar
    const toolbar = this.quill.getModule('toolbar');

    // 获取toolbar中原来 上传图片按钮
    const imageParent = _.toArray(toolbar.container?.children).find(
      (child) => child?.children[0]?.className === 'ql-image'
    );
    // 覆盖原有按钮 DOM结构
    ReactDOM.render(<ImgButton editor={this.quill} onChange={this.onImageChange} />, imageParent);
  }

  // 传入图片上传ImgButton组件的回调方法,与editor通信
  onImageChange = (src: string) => {
    this.quill.getSelection(true);
    const index = (this.quill.getSelection() || {}).index || this.quill.getLength();
    this.quill.insertEmbed(index, 'image', src);
  };
}

export default ImageUploader;

ql-tooltip被遮挡 z-index设置也无效时

注意检查是否设置了bounds属性

 bounds: document.getElementById('quill_editor') as HTMLElement,

hanlder添加

文档提供方式:

var quill = new Quill('#editor', {
  modules: {
    toolbar: {
      container: '#toolbar',  // Selector for toolbar container
      handlers: {
        'bold': customBoldHandler
      }
    }
  }
});

toolbar使用数组方式设置的话,增加handler方式如下

var toolbar = quill.getModule('toolbar');
toolbar.addHandler('bold', customBoldHandler);

修改添加链接的placeholder

/**
 * 修改添加链接的placeholder
 */
useEffect(() => {
  document
    .querySelector('input[data-link]')
    ?.setAttribute('data-link', '请输入链接 http(s)://...');
}, [quillEditor]);

quill snow link tooltip 中输入框对输入的链接进行校验(validation)

utils.js

import Emitter from 'quill/core/emitter';
import { message } from 'antd';

/**
 * 覆写 Snow 的tooltip的save
 */
export function SnowTooltipSave() {
  const { value } = this.textbox;
  const linkValidityRegex = /^(http|https)/;
  switch (this.root.getAttribute('data-mode')) {
    case 'link': {
      if (!linkValidityRegex.test(value)) {
        message.error('链接格式错误,请输入链接 http(s)://...');
        return;
      }
      const { scrollTop } = this.quill.root;
      if (this.linkRange) {
        this.quill.formatText(this.linkRange, 'link', value, Emitter.sources.USER);
        delete this.linkRange;
      } else {
        this.restoreFocus();
        this.quill.format('link', value, Emitter.sources.USER);
      }
      this.quill.root.scrollTop = scrollTop;
      break;
    }
    default:
  }
  this.textbox.value = '';
  this.hide();
}

export function SnowThemeLinkHandler(value) {
  if (value) {
    const range = this.quill.getSelection();
    if (range == null || range.length === 0) return;
    let preview = this.quill.getText(range);
    if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf('mailto:') !== 0) {
      preview = `mailto:${preview}`;
    }
    const { tooltip } = this.quill.theme;
    tooltip.save = SnowTooltipSave;
    tooltip.edit('link', preview);
  } else {
    this.quill.format('link', false);
  }
}

用例,基于 基础用例 代码

import React, { useEffect } from 'react';
import Quill, { QuillOptionsStatic } from 'quill';
...
const SnowTheme = Quill.import('themes/snow');
SnowTheme.DEFAULTS.modules.toolbar.handlers.link = SnowThemeLinkHandler;
...
const QuillEditor = () => {
...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

quill编辑器使用 的相关文章

  • javascript中怪异模式的元素宽度?

    我一直在浏览所有流行的 js 库 但我找不到一个具有 DOM 元素宽度函数的库 该函数实际上可以解释 Internet Explorer 中的怪异模式 问题是 当启用怪异模式时 填充和边框不会计入宽度 据我所知 当省略 doctype 或将
  • 如果替换路径中的最后一个元素,React Router v6 useNavigate() 不会导航

    我有一个具有以下功能的反应组件 const handleNavigate clientId gt console log clientId navigate dashboard clients clientId console log 显示
  • 未捕获的类型错误:无法读取未定义的属性“toLowerCase”

    我收到此错误 它源自 jquery 框架 当我尝试加载准备好的文档上的选择列表时 出现此错误 我似乎无法找到为什么会出现此错误 它适用于更改事件 但在尝试手动执行该函数时出现错误 未捕获的类型错误 无法读取未定义的属性 toLowerCas
  • 如何使用 JavaScript 将当前页面设置为 about:blank?

    我遇到的情况是服务器可能在当前地址上不可用 因此我想检测到这一点并将页面重定向到 about blank 页面 我该如何使用 JavaScript 来做到这一点 window location href about blank
  • React 不响应按键事件

    我正在尝试实现一些非常基本的按键检测 但我根本无法让它工作 我有一个裸露的组件 应该在onKeyDown事件 但控制台中没有任何内容被注销 class App extends React Component constructor prop
  • JSON对象的长度[重复]

    这个问题在这里已经有答案了 该函数生成一个包含 json 对象的数组 var estoque function unpack estoque tnm total estoque vl id tid st tnm tnm split tota
  • 使用 javascript 禁用按钮:FF 与 IE

    我有一排按钮 它们都会创建一个我想在新选项卡中打开的 pdf 文件 这样按钮页面就会保持在顶部 并且 pdf 会打开以进行打印 为了防止单击按钮两次 我禁用该按钮 如下所示 我使用 python
  • mouseover 和 mouseout 事件在子进程上触发

    代码 div div div div 如果我将鼠标悬停在Navigation the Drop Downdiv 向下滑动 如果我将鼠标移开 它会向上滑动 问题是如果我将鼠标悬停在孩子上Drop Downdiv它也向上滑 动 有谁知道我该如何
  • Backbone 中的加载栏

    我想显示加载消息 图标 直到列表中的所有项目都已呈现 这是我的示例中的 jsfiddle http jsfiddle net 9R9zU 58 http jsfiddle net 9R9zU 58 我尝试在 Feed 部分添加一个带有加载栏
  • 将上部字符转换为下部字符,将下部字符转换为上部字符(反之亦然)[重复]

    这个问题在这里已经有答案了 我需要将某些字符串中的所有较低字符转换为较高字符 并将所有较高字符转换为较低字符 例如 var testString heLLoWorld 应该 HEllOwORLD 转换后 在不保存临时字符串的情况下实现此目的
  • 获取 JSON 中的 HTML 以在 React 组件中呈现为 HTML

    试图找出如何让链接实际呈现为链接 现在 在我从 Json 文件中读取这行文本后 React 将超链接渲染为文字文本 而不将其渲染为链接 一些数据 json about John has a blog you can read a href
  • Angular 2 runOutsideAngular 仍然改变 UI

    从我的理解来看runOutsideAngular https angular io docs ts latest api core index NgZone class html runOutsideAngular anchor 如果我需要
  • 监听浏览器宽度以进行响应式网页设计?

    我正在努力使我的网站适合移动设备 我想知道浏览器窗口的大小 以便当它比 728px 窄时我可以执行某些操作 而当它大于 728px 时我可以执行其他操作 这必须考虑到调整 PC 上的窗口大小以及在手机中从纵向模式更改为横向模式 如何才能做到
  • 如何设置在浏览器的新选项卡(_blank)中打开的pdf文件的标题

    这是我的尝试 是否在新选项卡上打开 但它总是显示test pdf如题 function titlepath path name alert path alert name document title name window open pa
  • 限制 jQuery id 字符串吗?

    简而言之 我的问题是字符串在 jQuery 中作为可搜索 id 或可搜索内容有什么限制 更新 我得到了 ID 部分 但不是为什么我什至无法使用该字符串搜索 html 内容 对于任何愿意告诉我一个正则表达式来将模式从 MM dd yy HH
  • 在角度控制器中监听文档事件

    如何捕获角度控制器中的事件 我有文档级事件 所以我需要在角度控制器中捕获事件 这可能吗 Update 我有独立的 js 文件来处理来自相机的一些操作 document addEventListener myCameraEvent handl
  • 如何按字母顺序排序并先小写排序

    如何获得以下排序的结果Food to Eat然后是 食物123 显然 第二个较低的 o 应该将 要吃的食物 带到排序后的第一个项目中 我很惊讶这个问题不容易通过谷歌找到答案 这个壮举没有包含在 javascript 标准中也让我感到惊讶 F
  • 更改javascript nodejs中所有页面的href url

    我已经实现了具有多种语言下拉菜单的引导导航栏 当我选择语言时 它将翻译页面 如何更改其他页面的 url 和按钮文本 当我选择french 将所有网址更改为 fr about and fr contact 如何使用 JavaScript 进行
  • 使用 javascript Array reduce() 方法有什么真正的好处吗?

    reduce 方法的大多数用例都可以使用 for 循环轻松重写 对 JSPerf 的测试表明 reduce 通常会慢 60 75 具体取决于每次迭代内执行的操作 除了能够以 函数式风格 编写代码之外 还有什么真正的理由使用reduce 吗
  • 如何使用引用该键的变量来获取对象键中的值?

    我有一个对象 我可以引用密钥a如下 var obj a A b B c C console log obj a return string A 我想通过使用变量引用对象键来获取值 如下所示 var name a console log ob

随机推荐

  • css如何实现三角形的方法

    在开发页面的时候 遇到很多的列表都需要用到箭头 可以直接用图片作背景铺垫 纯CSS也能实现 并且没有兼容性顾虑 不用CSS3 相比而言 比图片更好用 原理 一个高宽相等的正方形 选取你所需要的某一边 截取之 就是一个梯形 当高宽都为0 且其
  • springboot日志操作

    springboot日志 日志介绍 1 日志的输出等级 2 运用lombok快速添加日志对象log 3 日志输出格式控制 4 日志文件 4 1 直接指定文件名 4 2 指定最大空间和格式 日志介绍 日志就是记录程序日常运行的信息 sprin
  • Android中图片的裁剪与压缩

    文章目录 一 图片的剪裁 1 属性介绍 二 图片压缩 1 图片质量分类 2 图片默认质量 3 占用内存 4 图片的尺寸压缩或者拉伸 三 Bitmap压缩 1 质量压缩 2 采样率压缩 3 缩放法压缩 一 图片的剪裁 ImageView默认的
  • error: ‘for’ loop initial declarations are only allowed in C99 mode (改Makefile)

    c 强制C99在CMake 使用 for 循环初始声明 2019 04 25 C C 编程之家 https www jb51 cc 编程之家收集整理的这篇文章主要介绍了c 强制C99在CMake 使用 for 循环初始声明 编程之家小编觉得
  • uCOSii中的互斥信号量

    uCOSii中的互斥信号量 一 互斥型信号量项管理 MUTUAL EXCLUSION SEMAPHORE MANAGEMENT OSMutexAccept 无条件等待地获取互斥型信号量 OSMutexCreate 建立并初始化一个互斥型信号
  • Peewee

    Part1前言 在 Python 的 ORM 框架中 比较主流的有 Sqlalchemy peewee pony 等等 但是其中 peewee 和 Django 的 Models 框架很像 如果了解 Django 的同学肯定对 peewee
  • mybatis 返回结果为Map

    表 members 中的gender 列是这样 Female Female Male Female Male Male Male Male Male 现在我们想统计 members 中男女人数分别是多少 很显然返回的结果应该是这样的 Fem
  • No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbalanc

    1 报错原因 启动SpringCloud项目时报 No Feign Client for loadBalancing defined Did you forget to include spring cloud starter loadba
  • hooks实践总结

    何为hooks 在React中hook是指不编写 class 的情况下使用 state 以及其他的 React 特性 而Vue3也推出了具有相同功能的组合式API 如果你用过Vue3就会知道在 setup 中你应该避免使用 this 因为h
  • 汇编语言(第三版)读书笔记 2 - 第2章 寄存器

    第2章 寄存器 前一章所说的总线 相对于CPU内部来说是外部总线 内部总线实现了CPU内部各个器件 运算器 控制器 寄存器 之间的联系 外部总线实现了CPU和主板上其他器件的联系 不同的CPU 寄存器的个数 结构是不相同的 8086 CPU
  • Redis实现定时任务

    Redis定时任务的核心在于 Schedule 注解 Redis Zset List数据结构 Redis管道技术 就从定时任务的执行流程开始写起 1 前端用户发起定时任务创建定时任务任务 像定时任务模块发起定时任务请求并且携带必要参数 首先
  • 【Python】逆向爬虫-----常见的加密方法

    一 MD5加密 MD5加密是一种被广泛使用的线性散列算法 可以产生出一个128位 16字节 的散列值 hash value 用于确保信息传输完整的一致性 且MD5加密之后产生的是一个固定长度 32位或16位 的数据 若要破解MD5加密 需要
  • C++程序的基本组成简介

    C 程序的基本组成简介 C 程序的基本组成 这个C 程序例子 由一个程序单位 程序文件 注 组成 这是一个简单例子未使用类 注 其中包括 1 头文件 可以认为头文件是你在调用函数时的一个桥梁 格式为 include 引用文件名 c 的程序是
  • set容器

    恭喜主教大人完成了自己的目标 set 容器 set容器基本概念 简介 所有元素都会在插入时所有元素都会在插入时自动被排序 自动去重 可重复插不报错但是去重了 默认从小到大排 本质 set multiset属于关联式容器 底层结构是用二叉树实
  • 最新ChatGPT GPT-4 文本生成技术详解(附ipynb与python源码及视频讲解)——开源DataWhale发布入门ChatGPT技术新手从0到1必备使用指南手册(三)

    目录 前言 最新ChatGPT GPT 4 文本生成技术详解 1 引言 2 文本摘要任务 2 1 什么是文本摘要 2 2 常见的文本摘要技术 2 3 基于OpenAI接口的文本摘要实验 2 3 1 简单上手版 调用预训练模型 2 3 2 进
  • 面向对象的单片机编程

    1 在看别人单片机程序时 你也许是奔溃的 因为全局变量满天飞 不知道哪个在哪用了 哪个表示什么 而且编写极其不规范 2 在自己写单片机程序时 也许你也是奔溃的 总感觉重新开启一个项目 之前的写过相似的代码也无法使用 得重新敲 代码重用度不高
  • 关系数据库(数据库原理)

    目录 一 关系数据结构 二 关系的完整性 三 关系运算 四 关系的规范化 一 关系数据结构 1 关系的定义和性质 1 关系的数学定义 域 一组有相同数据类型的值得集合 笛卡尔积 设任意的N个域D1 D2 Dn 定义D1 D2 Dn的笛卡尔积
  • Android热更新之AndFix就是个大坑

    最近一两年Android插件化热更新此起彼伏 也许Android的开发者也希望有朝一日 来颠覆频繁的去更新版本 而像web前端一样 更改了代码立马生效的效果 确实 如果已经上线的版本 突然有了bug 按照现有模式 开发者不得不去解决bug
  • 类中的getInstance()方法的用法和作用

    class rmt dbutil public public static rmt dbutil getInstance if instance NULL instance new rmt dbutil return instance bo
  • quill编辑器使用

    官方git https github com quilljs quill 官方文档 https quilljs com 中文文档 https kang bing kui gitbook io quill 编辑器是个正经编辑器 就是文档太不正