├── .editorconfig
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── README.md
├── demo
├── .eslintignore
├── .eslintrc
├── package.json
├── src
│ ├── index.html
│ └── index.js
└── webpack.config.js
├── package.json
└── src
├── ImageUploadPlaceholder.js
├── constant.js
├── imageIdManger.js
├── index.js
└── style.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | end_of_line = lf
7 | indent_size = 2
8 | indent_style = space
9 | insert_final_newline = true
10 | trim_trailing_whitespace = true
11 |
12 | [*.md]
13 | indent_size = 4
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # system ignore
2 | .DS_Store
3 | Thumbs.db
4 |
5 | # npm ignore
6 | node_modules/
7 | npm-debug.log
8 |
9 | # webpack ignore
10 | dist
11 |
12 | # eslint-cache
13 | .eslintcache
14 | .vscode
15 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | npm-debug.log*
4 | yarn-debug.log*
5 | yarn-error.log*
6 |
7 | demo/
8 | node_modules/
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | ## 0.0.5
4 |
5 | - rename className
6 |
7 | ## 0.0.4
8 |
9 | - update the way to import styles
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # quill-plugin-image-upload
2 |
3 | A plugin for uploading image in Quill 🌇
4 |
5 | - 🌟 upload a image when it is inserted, and then replace the base64-url with a http-url
6 | - 🌟 preview the image which is uploading with a loading animation
7 | - 🌟 when the image is uploading, we can keep editing the content including changing the image's position or even delete the image.
8 |
9 | 
10 |
11 | ## Install
12 |
13 | ```bash
14 | npm install quill-plugin-image-upload --save
15 | ```
16 |
17 | ## Start
18 |
19 | ```js
20 | import Quill from 'quill';
21 | import 'quill/dist/quill.snow.css';
22 | import imageUpload from 'quill-plugin-image-upload';
23 |
24 | // register quill-plugin-image-upload
25 | Quill.register('modules/imageUpload', imageUpload);
26 |
27 | new Quill('#editor', {
28 | theme: 'snow',
29 | modules: {
30 | toolbar: [
31 | 'image'
32 | ],
33 | imageUpload: {
34 | upload: file => {
35 | // return a Promise that resolves in a link to the uploaded image
36 | return new Promise((resolve, reject) => {
37 | ajax().then(data => resolve(data.imageUrl));
38 | });
39 | }
40 | },
41 | },
42 | });
43 | ```
44 |
45 | ## Demo
46 |
47 | ```bash
48 | cd demo
49 | npm install
50 | npm start
51 | ```
52 |
--------------------------------------------------------------------------------
/demo/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | webpack.config.js
--------------------------------------------------------------------------------
/demo/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "browser": true
5 | },
6 | "parserOptions": {
7 | "ecmaVersion": 7,
8 | "sourceType": "module"
9 | },
10 | "extends": "eslint:recommended",
11 | "globals": {
12 | "require": false,
13 | "module": false
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quill-plugin-image-upload-demo",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "build": "webpack",
8 | "watch": "webpack --progress --watch",
9 | "start": "webpack-dev-server --open --host 0.0.0.0",
10 | "eslint": "echo \"Checking code style, please wait ...\" && eslint ./src *.js --cache",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "pre-commit": [
17 | "eslint"
18 | ],
19 | "dependencies": {
20 | "babel-core": "^6.26.0",
21 | "babel-loader": "^7.1.2",
22 | "babel-preset-es2015": "^6.24.1",
23 | "clean-webpack-plugin": "^0.1.17",
24 | "css-loader": "^0.28.7",
25 | "eslint": "^4.10.0",
26 | "eslint-loader": "^1.9.0",
27 | "html-webpack-plugin": "^2.30.1",
28 | "pre-commit": "^1.2.2",
29 | "quill": "^1.3.6",
30 | "quill-plugin-image-upload": "0.0.6",
31 | "style-loader": "^0.19.0",
32 | "uglifyjs-webpack-plugin": "^1.0.1",
33 | "webpack": "^3.8.1",
34 | "webpack-dev-server": "^2.9.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/demo/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | demo | quill-plugin-image-upload
7 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/demo/src/index.js:
--------------------------------------------------------------------------------
1 | const Quill = require('quill');
2 | require('quill/dist/quill.snow.css');
3 | const imageUpload = require('quill-plugin-image-upload');
4 |
5 | Quill.register('modules/imageUpload', imageUpload);
6 |
7 | const MOCK_IMG_SRC = 'http://tva1.sinaimg.cn/crop.0.0.217.217.180/4c8b519djw8fa45br0vpxj2062062q33.jpg';
8 | const quill = new Quill('#editor', {
9 | theme: 'snow',
10 | modules: {
11 | toolbar: [
12 | [{
13 | 'header': [1, 2, 3, 4, 5, false]
14 | }, {
15 | 'size': ['small', false, 'large', 'huge']
16 | }],
17 | [{
18 | 'color': []
19 | }, {
20 | 'background': []
21 | }, 'bold', 'italic', 'underline', 'strike'],
22 | ['link', 'blockquote', 'code-block', 'image'],
23 | [{
24 | 'align': []
25 | }, {
26 | 'indent': '-1'
27 | }, {
28 | 'indent': '+1'
29 | }, {
30 | list: 'ordered'
31 | }, {
32 | list: 'bullet'
33 | }],
34 | ['clean'] // outdent/indent
35 | ],
36 | imageUpload: {
37 | upload: file => {
38 | // return a Promise that resolves in a link to the uploaded image
39 | return new Promise((resolve, reject) => {
40 | setTimeout(() => {
41 | resolve(MOCK_IMG_SRC); // Must resolve as a link to the image
42 | }, 1000);
43 | // const fd = new FormData();
44 | // fd.append("upload_file", file);
45 |
46 | // const xhr = new XMLHttpRequest();
47 | // xhr.open("POST", `${window.location.pathname}/api/files/add`, true);
48 | // xhr.onload = () => {
49 | // if (xhr.status === 200) {
50 | // const response = JSON.parse(xhr.responseText);
51 | // resolve(response.file_path); // Must resolve as a link to the image
52 | // }
53 | // };
54 | // xhr.send(fd);
55 | });
56 | }
57 | },
58 | },
59 | placeholder: 'please write something...',
60 | });
61 |
62 | document.getElementById('output').onclick = function() {
63 | console.log(quill.root.innerHTML);
64 | }
65 |
--------------------------------------------------------------------------------
/demo/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CleanWebpackPlugin = require('clean-webpack-plugin');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
5 |
6 | module.exports = {
7 | entry: {
8 | workboard: './src/index.js'
9 | },
10 | output: {
11 | filename: 'index.[chunkhash].js',
12 | path: path.resolve(__dirname, 'dist')
13 | },
14 | module: {
15 | rules: [
16 | // {
17 | // enforce: "pre",
18 | // test: /\.js$/,
19 | // exclude: /node_modules/,
20 | // loader: "eslint-loader",
21 | // },
22 | {
23 | test: /\.css$/,
24 | use: [
25 | 'style-loader',
26 | 'css-loader'
27 | ]
28 | },
29 | {
30 | test: /\.js$/,
31 | loader: 'babel-loader',
32 | query: {
33 | presets: ['es2015'],
34 | },
35 | },
36 | ]
37 | },
38 | devServer: {
39 | contentBase: './dist'
40 | },
41 | plugins: [
42 | new CleanWebpackPlugin(['dist']),
43 | new HtmlWebpackPlugin({
44 | template: './src/index.html',
45 | // chunks: ['workboard'],
46 | filename: 'index.html'
47 | }),
48 | new UglifyJSPlugin()
49 | ]
50 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quill-plugin-image-upload",
3 | "version": "0.0.6",
4 | "description": "a plugin for uploading image in Quill",
5 | "main": "./src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/dragonwong/quill-plugin-image-upload.git"
12 | },
13 | "keywords": [
14 | "quill",
15 | "image",
16 | "upload",
17 | "uploader",
18 | "src"
19 | ],
20 | "author": "",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/dragonwong/quill-plugin-image-upload/issues"
24 | },
25 | "homepage": "https://github.com/dragonwong/quill-plugin-image-upload#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/src/ImageUploadPlaceholder.js:
--------------------------------------------------------------------------------
1 | const Quill = require('quill');
2 | const constant = require('./constant');
3 |
4 | const Image = Quill.import('formats/image');
5 |
6 | class ImageUploadPlaceholder extends Image {
7 | static create(value) {
8 | let id;
9 | let src;
10 |
11 | const arr = value.split(constant.ID_SPLIT_FLAG);
12 | if (arr.length > 1) {
13 | id = arr[0];
14 | src = arr[1];
15 | } else {
16 | src = value;
17 | }
18 |
19 | let node = super.create(src);
20 | if (typeof src === 'string') {
21 | node.setAttribute('src', this.sanitize(src));
22 | }
23 |
24 | if (id) {
25 | node.setAttribute('id', id);
26 | }
27 | return node;
28 | }
29 | }
30 |
31 | ImageUploadPlaceholder.blotName = 'imageUpload';
32 | ImageUploadPlaceholder.className = constant.IMAGE_UPLOAD_PLACEHOLDER_CLASS_NAME;
33 |
34 | Quill.register({
35 | 'formats/imageUploadPlaceholder': ImageUploadPlaceholder
36 | });
37 |
--------------------------------------------------------------------------------
/src/constant.js:
--------------------------------------------------------------------------------
1 | const constant = {
2 | ID_SPLIT_FLAG: '__ID_SPLIT__',
3 | IMAGE_UPLOAD_PLACEHOLDER_CLASS_NAME: 'quill-plugin-image-upload-placeholder',
4 | };
5 |
6 | module.exports = constant;
7 |
--------------------------------------------------------------------------------
/src/imageIdManger.js:
--------------------------------------------------------------------------------
1 | const imageIdManger = {
2 | id: 0,
3 | name: 'QUILL_IMAGE_PLUS',
4 | generate() {
5 | const id = this.id;
6 | this.id = id + 1;
7 | return `${this.name}_${id}`;
8 | },
9 | }
10 |
11 | module.exports = imageIdManger;
12 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | require('./ImageUploadPlaceholder.js');
2 | require('./style.js');
3 | const imageIdManger = require('./imageIdManger');
4 | const constant = require('./constant');
5 |
6 | class ImageUpload {
7 | constructor(quill, options) {
8 | this.quill = quill;
9 | this.options = options;
10 | this.range = null;
11 |
12 | if (typeof (this.options.upload) !== "function")
13 | console.warn('[Missing config] upload function that returns a promise is required');
14 |
15 | var toolbar = this.quill.getModule("toolbar");
16 | toolbar.addHandler("image", this.selectLocalImage.bind(this));
17 | }
18 |
19 | selectLocalImage() {
20 | this.range = this.quill.getSelection();
21 | this.fileHolder = document.createElement("input");
22 | this.fileHolder.setAttribute("type", "file");
23 | this.fileHolder.setAttribute('accept', 'image/*');
24 | this.fileHolder.onchange = this.fileChanged.bind(this);
25 | this.fileHolder.click();
26 | }
27 |
28 | fileChanged() {
29 | const file = this.fileHolder.files[0];
30 | const imageId = imageIdManger.generate();
31 |
32 | const fileReader = new FileReader();
33 | fileReader.addEventListener("load", () => {
34 | let base64ImageSrc = fileReader.result;
35 | this.insertBase64Image(base64ImageSrc, imageId);
36 | }, false);
37 | if (file) {
38 | fileReader.readAsDataURL(file);
39 | }
40 |
41 | this.options.upload(file)
42 | .then((imageUrl) => {
43 | this.insertToEditor(imageUrl, imageId);
44 | },
45 | (error) => {
46 | console.warn(error.message);
47 | }
48 | )
49 | }
50 |
51 | insertBase64Image(url, imageId) {
52 | const range = this.range;
53 | this.quill.insertEmbed(range.index, "imageUpload", `${imageId}${constant.ID_SPLIT_FLAG}${url}`);
54 | }
55 |
56 | insertToEditor(url, imageId) {
57 | const imageElement = document.getElementById(imageId);
58 | if (imageElement) {
59 | imageElement.setAttribute('src', url);
60 | imageElement.removeAttribute('id');
61 | imageElement.classList.remove(constant.IMAGE_UPLOAD_PLACEHOLDER_CLASS_NAME);
62 | }
63 | }
64 | }
65 |
66 | module.exports = ImageUpload;
67 |
--------------------------------------------------------------------------------
/src/style.js:
--------------------------------------------------------------------------------
1 | const constant = require('./constant');
2 |
3 | const ANIMATION_NAME = 'quill-plugin-image-upload-spinner';
4 |
5 | const styleElement = document.createElement('style');
6 | styleElement.type = 'text/css';
7 | document.getElementsByTagName('head')[0].appendChild(styleElement);
8 |
9 | styleElement.appendChild(document.createTextNode(`
10 | .${constant.IMAGE_UPLOAD_PLACEHOLDER_CLASS_NAME} {
11 | display: inline-block;
12 | width: 30px;
13 | height: 30px;
14 | border-radius: 50%;
15 | border: 3px solid #ccc;
16 | border-top-color: #1e986c;
17 | animation: ${ANIMATION_NAME} 0.6s linear infinite;
18 | }
19 | @keyframes ${ANIMATION_NAME} {
20 | to {
21 | transform: rotate(360deg);
22 | }
23 | }
24 | `));
25 |
--------------------------------------------------------------------------------