├── demo ├── react-quill.js ├── quill.snow.css ├── test.html ├── index.html └── index.js ├── .prettierrc ├── cypress.json ├── .gitignore ├── tsconfig.json ├── .travis.yml ├── test ├── polyfills │ ├── getSelection.js │ └── MutationObserver.js ├── setup.js ├── utils.js ├── index.spec.js └── index.js ├── LICENSE ├── webpack.config.js ├── .github └── ISSUE_TEMPLATE.md ├── package.json ├── CHANGELOG.md ├── src └── index.tsx └── README.md /demo/react-quill.js: -------------------------------------------------------------------------------- 1 | ../dist/react-quill.js -------------------------------------------------------------------------------- /demo/quill.snow.css: -------------------------------------------------------------------------------- 1 | ../node_modules/quill/dist/quill.snow.css -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /demo/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "integrationFolder": "test", 3 | "fileServerFolder": "demo", 4 | "testFiles": "*.spec.js", 5 | "videosFolder": ".cypress/videos", 6 | "screenshotsFolder": ".cypress/screenshots", 7 | "fixturesFolder": false, 8 | "pluginsFile": false, 9 | "supportFile": false 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Misc 2 | *~ 3 | ~* 4 | *.tmp 5 | *.orig 6 | *.bak 7 | *.log 8 | *.DS_STORE 9 | /tmp 10 | 11 | # Dependencies 12 | /node_modules 13 | 14 | # Compiled files 15 | /dist 16 | /lib 17 | 18 | # Packaging artifacts 19 | /react-quill-*.tgz 20 | /package 21 | 22 | # Test artifacts 23 | .cypress/screenshots 24 | .cypress/videos 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./src/**/*.ts", 4 | "./src/**/*.tsx", 5 | ], 6 | "compilerOptions": { 7 | "strict": true, 8 | "target": "es5", 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "declaration": true, 13 | "sourceMap": true, 14 | "inlineSources": true, 15 | "outDir": "lib", 16 | "lib": ["dom", "esnext"], 17 | "types": ["node"], 18 | "jsx": "react" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | addons: 5 | apt: 6 | packages: 7 | # Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves 8 | - libgconf-2-4 9 | cache: 10 | # Caches $HOME/.npm when npm ci is default script command 11 | # Caches node_modules in all other cases 12 | npm: true 13 | directories: 14 | # we also need to cache folder with Cypress binary 15 | - ~/.cache 16 | install: 17 | - npm install 18 | script: 19 | - npm run test 20 | -------------------------------------------------------------------------------- /test/polyfills/getSelection.js: -------------------------------------------------------------------------------- 1 | module.exports = function(global) { 2 | global.document = global.document || {}; 3 | global.window = global.window || {}; 4 | document.getSelection = getSelectionShim 5 | document.createRange = document.getSelection; 6 | } 7 | 8 | /** 9 | * DOM Traversal is not implemented in JSDOM 10 | * The best we can do is shim the functions 11 | */ 12 | function getSelectionShim() { 13 | return { 14 | getRangeAt: function() {}, 15 | removeAllRanges: function() {}, 16 | setStart: function() {}, 17 | setEnd: function() {}, 18 | addRange: function() {}, 19 | }; 20 | }; -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/setup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create mock DOM and browser globals 3 | * 4 | * See Enzyme docs: 5 | * https://github.com/airbnb/enzyme/blob/master/docs/guides/jsdom.md 6 | * https://github.com/airbnb/enzyme/blob/master/docs/api/mount.md 7 | */ 8 | 9 | require('jsdom-global')(); 10 | require('./polyfills/MutationObserver.js')(global); 11 | require('./polyfills/getSelection.js')(global); 12 | 13 | // Configure the Enzyme adapter 14 | const Enzyme = require('enzyme'); 15 | const EnzymeAdapter = require('enzyme-adapter-react-16'); 16 | Enzyme.configure({ adapter: new EnzymeAdapter() }); 17 | 18 | // Setup Chai to use Enzyme 19 | const chai = require('chai'); 20 | const chaiEnzyme = require('chai-enzyme'); 21 | chai.use(chaiEnzyme()); 22 | 23 | global.expect = chai.expect; 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020, zenoamaro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var { mount } = require('enzyme'); 3 | var ReactQuill = require('../lib/index'); 4 | 5 | function ReactQuillNode(props, children) { 6 | props = Object.assign( 7 | { 8 | modules: { toolbar: ['underline', 'bold', 'italic'] }, 9 | formats: ['underline', 'bold', 'italic'], 10 | }, 11 | props 12 | ); 13 | 14 | return React.createElement(ReactQuill, props, children); 15 | } 16 | 17 | function mountReactQuill(props, node) { 18 | return mount(ReactQuillNode(props, node)); 19 | } 20 | 21 | function getQuillInstance(wrapper) { 22 | return wrapper.instance().getEditor(); 23 | } 24 | 25 | function getQuillDOMNode(wrapper) { 26 | return wrapper.getDOMNode().querySelector('.ql-editor'); 27 | } 28 | 29 | function getQuillContentsAsHTML(wrapper) { 30 | return getQuillDOMNode(wrapper).innerHTML; 31 | } 32 | 33 | function setQuillContentsFromHTML(wrapper, html) { 34 | const editor = getQuillInstance(wrapper); 35 | return editor.clipboard.dangerouslyPasteHTML(html); 36 | } 37 | 38 | module.exports = { 39 | mountReactQuill, 40 | getQuillInstance, 41 | getQuillDOMNode, 42 | getQuillContentsAsHTML, 43 | setQuillContentsFromHTML, 44 | }; 45 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const Path = require('path'); 2 | const dir = (...args) => Path.resolve(__dirname, ...args); 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: dir('src/index.tsx'), 7 | 8 | resolve: { 9 | extensions: ['.js', '.ts', '.tsx'], 10 | }, 11 | 12 | module: { 13 | rules: [ 14 | {test:/\.tsx?$/, loader:'ts-loader', exclude:/node_modules/}, 15 | ], 16 | }, 17 | 18 | externals: { 19 | 'react': { 20 | 'commonjs': 'react', 21 | 'commonjs2': 'react', 22 | 'amd': 'react', 23 | 'root': 'React' 24 | }, 25 | 'react-dom': { 26 | 'commonjs': 'react-dom', 27 | 'commonjs2': 'react-dom', 28 | 'amd': 'react-dom', 29 | 'root': 'ReactDOM' 30 | }, 31 | 'react-dom/server': { 32 | 'commonjs': 'react-dom/server', 33 | 'commonjs2': 'react-dom/server', 34 | 'amd': 'react-dom/server', 35 | 'root': 'ReactDOMServer' 36 | } 37 | }, 38 | 39 | output: { 40 | path: dir('dist'), 41 | filename: 'react-quill.js', 42 | library: 'ReactQuill', 43 | libraryTarget: 'umd', 44 | }, 45 | 46 | devServer: { 47 | contentBase: dir('dist'), 48 | stats: 'errors-only', 49 | }, 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thanks for filing an issue! 2 | 3 | If at all possible, please provide a Codesandbox or Codepen to demonstrate the problem you're having with React Quill. Here's a [template] to get you started. 4 | 5 | ⚠️ Make sure that your bug hasn't already been fixed by ReactQuill `v2.0.0-beta.2`. See the homepage for instructions on how to upgrade. 6 | 7 | [template]: 8 | https://codesandbox.io/s/react-quill-template-u9c9c 9 | 10 | 11 | ### Ticket due diligence 12 | 13 | - [ ] I have verified that the issue persists under ReactQuill `v2.0.0-beta.2` 14 | - [ ] I can't use the beta version for other reasons 15 | 16 | 17 | ### ReactQuill version 18 | 19 | - [ ] master 20 | - [ ] v2.0.0-beta.2 21 | - [ ] v2.0.0-beta.1 22 | - [ ] 1.3.5 23 | - [ ] 1.3.4 or older 24 | - [ ] Other (fork) 25 | 26 | 27 | ### FAQ 28 | 29 | **Is this a bug in Quill or ReactQuill?** 30 | 31 | ReactQuill is just a ~thin~ wrapper on top of the Quill editor. Often, what looks like a bug in ReactQuill, is actually a bug in the Quill editor itself. Before opening a ticket, please check the [Quill documentation], and the [issues page], and see if that answers your question first. 32 | 33 | [Quill documentation]: 34 | https://quilljs.com/docs 35 | 36 | [issues page]: 37 | https://github.com/quilljs/quill/issues 38 | 39 | **How do I access the wrapped Quill instance?** 40 | 41 | See the [instance methods] and [API] documentation. 42 | 43 | [instance methods]: 44 | https://github.com/zenoamaro/react-quill#methods 45 | 46 | [API]: 47 | https://github.com/zenoamaro/react-quill#api-reference 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-quill", 3 | "version": "2.0.0", 4 | "description": "The Quill rich-text editor as a React component.", 5 | "author": "zenoamaro ", 6 | "license": "MIT", 7 | "homepage": "https://github.com/zenoamaro/react-quill", 8 | "bugs": "https://github.com/zenoamaro/react-quill/issues", 9 | "repository": "https://github.com/zenoamaro/react-quill.git", 10 | "main": "lib/index.js", 11 | "types": "lib/index.d.ts", 12 | "scripts": { 13 | "build": "npm run build:lib && npm run build:dist && npm run build:css", 14 | "build:lib": "tsc", 15 | "build:dist": "webpack", 16 | "build:css": "cpx 'node_modules/quill/dist/quill.*.css' dist", 17 | "watch": "tsc --watch", 18 | "pretest": "npm run build", 19 | "test": "npm run test:unit && npm run test:coverage && npm run test:browser", 20 | "test:unit": "mocha --recursive --require=./test/setup.js -R spec test/index", 21 | "test:coverage": "mocha --recursive --require=./test/setup.js -R mocha-text-cov test/index", 22 | "test:browser": "cypress run", 23 | "test:browser:interactive": "cypress open", 24 | "demo": "superstatic demo", 25 | "clean": "rimraf lib dist", 26 | "prepublishOnly": "npm run build" 27 | }, 28 | "keywords": [ 29 | "react", 30 | "react-component", 31 | "rich", 32 | "text", 33 | "rich-text", 34 | "textarea", 35 | "quill" 36 | ], 37 | "files": [ 38 | "dist/", 39 | "lib/", 40 | "types.d.ts", 41 | "README.md", 42 | "CHANGELOG.md", 43 | "LICENSE" 44 | ], 45 | "dependencies": { 46 | "@types/quill": "^1.3.10", 47 | "lodash": "^4.17.4", 48 | "quill": "^1.3.7" 49 | }, 50 | "peerDependencies": { 51 | "react": "^16 || ^17 || ^18", 52 | "react-dom": "^16 || ^17 || ^18" 53 | }, 54 | "devDependencies": { 55 | "@types/chai": "^4.2.11", 56 | "@types/lodash": "^4.14.146", 57 | "@types/mocha": "^7.0.2", 58 | "@types/react": "^16.9.11", 59 | "@types/react-dom": "^16.9.4", 60 | "@types/sinon": "^7.5.2", 61 | "blanket": "^1.2.3", 62 | "chai": "^4.2.0", 63 | "chai-enzyme": "^1.0.0-beta.1", 64 | "cheerio": "^1.0.0-rc.3", 65 | "cpx": "^1.5.0", 66 | "cypress": "^4.3.0", 67 | "enzyme": "^3.10.0", 68 | "enzyme-adapter-react-16": "^1.15.1", 69 | "jsdom": "^11.0.0", 70 | "jsdom-global": "^3.0.2", 71 | "mocha": "^6.2.2", 72 | "mocha-text-cov": "^0.1.1", 73 | "react": "^16.11.0", 74 | "react-dom": "^16.11.0", 75 | "react-test-renderer": "^16.11.0", 76 | "rimraf": "^3.0.0", 77 | "should": "^13.2.3", 78 | "sinon": "^7.5.0", 79 | "superstatic": "^6.0.4", 80 | "travis-cov": "^0.2.5", 81 | "ts-loader": "^6.2.1", 82 | "typescript": "^3.7.2", 83 | "webpack": "^4.41.2", 84 | "webpack-cli": "^3.3.10" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | /* global React */ 2 | /* global ReactQuill */ 3 | 'use strict'; 4 | 5 | if (typeof React !== 'object') { 6 | alert('React not found. Did you run "npm install"?'); 7 | } 8 | 9 | if (typeof ReactQuill !== 'function') { 10 | alert('ReactQuill not found. Did you run "make build"?') 11 | } 12 | 13 | var EMPTY_DELTA = {ops: []}; 14 | 15 | class Editor extends React.Component { 16 | 17 | constructor(props) { 18 | super(props); 19 | this.state = { 20 | theme: 'snow', 21 | enabled: true, 22 | readOnly: false, 23 | value: EMPTY_DELTA, 24 | events: [] 25 | }; 26 | } 27 | 28 | formatRange(range) { 29 | return range 30 | ? [range.index, range.index + range.length].join(',') 31 | : 'none'; 32 | } 33 | 34 | onEditorChange = (value, delta, source, editor) => { 35 | this.setState({ 36 | value: editor.getContents(), 37 | events: [`[${source}] text-change`, ...this.state.events], 38 | }); 39 | } 40 | 41 | onEditorChangeSelection = (range, source) => { 42 | this.setState({ 43 | selection: range, 44 | events: [ 45 | `[${source}] selection-change(${this.formatRange(this.state.selection)} -> ${this.formatRange(range)})`, 46 | ...this.state.events, 47 | ] 48 | }); 49 | } 50 | 51 | onEditorFocus = (range, source) => { 52 | this.setState({ 53 | events: [ 54 | `[${source}] focus(${this.formatRange(range)})` 55 | ].concat(this.state.events) 56 | }); 57 | } 58 | 59 | onEditorBlur = (previousRange, source) => { 60 | this.setState({ 61 | events: [ 62 | `[${source}] blur(${this.formatRange(previousRange)})` 63 | ].concat(this.state.events) 64 | }); 65 | } 66 | 67 | onToggle = () => { 68 | this.setState({ enabled: !this.state.enabled }); 69 | } 70 | 71 | onToggleReadOnly = () => { 72 | this.setState({ readOnly: !this.state.readOnly }); 73 | } 74 | 75 | onSetContents = () => { 76 | this.setState({ value: 'This is some fine example content' }); 77 | } 78 | 79 | render() { 80 | return ( 81 |
82 | {this.renderToolbar()} 83 |
84 | {this.renderSidebar()} 85 | {this.state.enabled && } 94 |
95 | ); 96 | } 97 | 98 | renderToolbar() { 99 | var state = this.state; 100 | var enabled = state.enabled; 101 | var readOnly = state.readOnly; 102 | var selection = this.formatRange(state.selection); 103 | return ( 104 |
105 | 108 | 111 | 114 | 117 |
118 | ); 119 | } 120 | 121 | renderSidebar() { 122 | return ( 123 |
124 |