MUKI AI Summary
為了整合公司內部系統,決定使用 Quill 編輯器重寫文章管理系統,因為 Markdown 無法直接調整文字顏色,轉而選擇 WYSIWYG 編輯器。Quill React 因功能齊全且客製化選項多,最終被選為編輯器。
Quill React 安裝簡單,透過 npm 指令即可完成,並內建兩種主題:snow 和 bubble。可自訂工具列,並加入自訂按鈕,如 undo、redo。使用 HTML 可完全客製化工具列內容,但需謹慎考慮。完整程式碼可在 GitHub 和 CodeSandbox 上獲取。...
緣起
因應公司整合內部系統,所以要重寫文章管理系統,原本是想挑選喜歡的 Markdown 編輯器,但考量到原本存在資料庫的就是 HTML 語法,而且 Markdown 本身無法調整文字顏色,一樣要轉成 HTML 再處理,所以最後還是選擇沿用所見即所得編輯器,也就是我們熟知的 WYSIWYG Rich Editor。
而我花了一些時間,翻了各大 React Editor,最後選擇 Quill React。
Quill 的 Github Star 很高,而且還有給 React 專用的 Quill React。此外我看了網站文件,功能蠻齊全,客製化的東西也多,版面也蠻好看的,所以就決定是它了 XXD。
安裝與使用 Quill React 編輯器
Quill 有專門給 React 用的編輯器套件,但有特別強調,必須要有 react 以及 react-dom,而且要有類似 style loader 這種可以讀取 CSS 檔案的 loader
▼ 使用 npm 安裝 Quill React
$ npm install react-quill --save
▼ 使用上也非常簡單,載入元件與設定 value 就能用了。Quill 內建兩種 Theme:snow
以及 bubble
,載入對應的 css 檔案,並將 theme 設定為 snow
/ bubble
即可。
import React, { useState } from 'react' import ReactQuill from 'react-quill' // import 'react-quill/dist/quill.bubble.css' import 'react-quill/dist/quill.snow.css' function MyComponent() { const [value, setValue] = useState('') // theme="bubble" return <ReactQuill theme="snow" value={value} onChange={setValue} /> }
▼ theme snow
示意圖,是一般常見的所見即所得編輯器版面,將工具列固定在上面
▼ theme bubble
示意圖,跟現在新的筆記軟體編輯器類似,反白文字後才會跳出工具列
自訂工具列項目
Quill 的 API 直接提供了自訂工具列的方法,可以任意排列組合,非常方便!
function App() { const [value, setValue] = useState('') // 宣告 modules 以自訂工具列 const modules = { toolbar: [ ['bold', 'italic', 'underline', 'strike'], // 粗體, 斜體, 底線, 刪除線 [{ header: 1 }, { header: 2 }], // 標題 [{ 'size': ['small', false, 'large', 'huge'] }] // 內文尺寸,false 表示預設值 ] } return ( <div className="w-full"> <ReactQuill theme="snow" value={value} modules={modules} onChange={setValue} /> </div> ) }
▼ 這是我自訂的工具列
用 HTML 客製化你的工具列
因為 Quill 的方便,讓我異想天開的思考,能不能在工具列上加入我自訂的按鈕或其他設定?
答案是可以的!我們可以改用 HTML 完全修改與調整工具列的內容,只是如果改用 HTML,等於整個要重寫 XXD....,建議大家三思而後行。
我客製化的工具列有:
- 參考 React Quill Editor with full toolbar options and custom buttons (undo & redo) 加入 undo 以及 redo 按鈕
- 修改內文的尺寸,改用 px 顯示
- 修改文字顏色
- 增加「編輯器使用說明」的自訂按鈕
因為內容不難,這邊就直接上 code,另外我使用的是 ant design 框架,大家可以照自己的框架需求調整 import
內容
▼ 把工具列拉成一個元件,只放工具列的內容
import { useState } from "react" import { Quill } from "react-quill" import { Button, Modal } from 'antd' import { InfoCircleOutlined } from '@ant-design/icons' const CustomUndo = () => ( <svg viewBox="0 0 18 18"> <polygon className="ql-fill ql-stroke" points="6 10 4 12 2 10 6 10" /> <path className="ql-stroke" d="M8.09,13.91A4.6,4.6,0,0,0,9,14,5,5,0,1,0,4,9" /> </svg> ) const CustomRedo = () => ( <svg viewBox="0 0 18 18"> <polygon className="ql-fill ql-stroke" points="12 10 14 12 16 10 12 10" /> <path className="ql-stroke" d="M9.91,13.91A4.6,4.6,0,0,1,9,14a5,5,0,1,1,5-5" /> </svg> ) function undoChange() { this.quill.history.undo() } function redoChange() { this.quill.history.undo() } // 調整字體尺寸,改用 px 表示 const Size = Quill.import("formats/size") Size.whitelist = ["15px", "18px", '24px', '32px', '48px'] Quill.register(Size, true) export const modules = { toolbar: { container: "#toolbar", handlers: { undo: undoChange, redo: redoChange } }, history: { delay: 500, maxStack: 100, userOnly: true } } // 每新增或移除 Quill Editor 內建的工具,記得要在 formats 做相應的調整 export const formats = [ "header", "size", "bold", "italic", "underline", "align", "strike", "script", "blockquote", "background", "list", "bullet", "indent", "link", "image", "video", "color", "code-block" ] export const QuillToolbar = () => { const [isModalOpen, setIsModalOpen] = useState(false) return ( <div id="toolbar"> <span className="ql-formats"> <select className="ql-size" defaultValue="medium"> {/* 對應前面設定的字體尺寸 */} <option value="15px">15px</option> <option value="18px">18px</option> <option value="24px">24px</option> <option value="32px">32px</option> <option value="48px">48px</option> </select> <select className="ql-header" defaultValue=""> <option value="2">大標題</option> <option value="3">子標題</option> <option value="">內文</option> </select> </span> <span className="ql-formats"> <button className="ql-bold" /> <button className="ql-italic" /> <button className="ql-underline" /> <button className="ql-blockquote" /> </span> <span className="ql-formats"> <select className="ql-align" /> {/* 可以客製化自己喜歡的顏色 */} <select className="ql-color"> <option value="#000000" /> <option value="#F44336" /> <option value="#E91E63" /> </select> <select className="ql-background" /> </span> <span className="ql-formats"> <button className="ql-list" value="ordered" /> <button className="ql-list" value="bullet" /> <button className="ql-indent" value="-1" /> <button className="ql-indent" value="+1" /> </span> <span className="ql-formats"> <button className="ql-link" /> <button className="ql-image" /> <button className="ql-video" /> </span> <span className="ql-formats"> <button className="ql-clean" /> <button className="ql-undo"> <CustomUndo /> </button> <button className="ql-redo"> <CustomRedo /> </button> </span> {/* 我加了一個自訂按鈕 */} <span className="ql-formats ql-formats--custom"> <Button type="primary" onClick={() => { setIsModalOpen(true) }} icon={<InfoCircleOutlined rev={undefined} />}>編輯器使用說明</Button> </span> <Modal title="編輯器使用說明" width={600} open={isModalOpen} onCancel={() => { setIsModalOpen(false) }} footer={[ <Button key="submit" type="primary" onClick={() => { setIsModalOpen(false) }}>確認</Button> ]}> 編輯器使用說明 內文 </Modal> </div> ) } export default QuillToolbar
▼ 增加對應的 CSS 樣式
/* ================= @ react-quill ==================== */ .ql-editor h2 { font-size: 1.5rem !important; } .ql-editor h3 { font-size: 1.3rem !important; } .ql-size-15px { font-size: 15px; } .ql-size-18px { font-size: 18px; } .ql-size-24px { font-size: 24px; } .ql-size-32px { font-size: 32px; } .ql-size-48px { font-size: 48px; } .ql-formats--custom button { background: #1677FF !important; width: 100% !important; height: 32px !important; padding: 4px 15px !important; } .ql-formats--custom button:hover, .ql-formats--custom button:focus { color: #FFF !important; background-color: #4096FF !important; }
▼ 匯入元件
import React, { useState } from 'react' import ReactQuill from 'react-quill' import EditorToolbar, { modules, formats } from './components/EditorToolbar' import 'react-quill/dist/quill.snow.css' function App() { const [value, setValue] = useState('') // 記得把剛剛宣告的 modules 註解或是刪除 // const modules = { // toolbar: [ // ['bold', 'italic', 'underline', 'strike'], // [{ header: 1 }, { header: 2 }], // [{ 'size': ['small', false, 'large', 'huge'] }] // ] // } return ( <div className="w-full"> <EditorToolbar /> <ReactQuill theme="snow" value={value} modules={modules} formats={formats} onChange={setValue} /> </div> ) } export default App
▼ 最後就可以看到我們客製化的工具列了!
線上範例與下載
我把上面這段程式放在 github 與 codesandbox,有興趣的朋友可以拉下來參考
- github:https://github.com/mukiwu/quill-react-editor-with-ant-design
- codesandbox 線上範例:https://63cdr7-5173.csb.app/