├── .gitignore
├── .prettierrc
├── README.md
├── babel.config.js
├── package.json
├── public
├── favicon.ico
└── index.html
├── src
├── App.vue
├── assets
│ ├── logo.png
│ ├── md-toolbar.scss
│ └── vue3-editor.scss
├── components
│ └── VueEditor.vue
├── helpers
│ ├── axios.js
│ ├── custom-link.js
│ ├── default-toolbar.js
│ ├── fullToolbar.js
│ ├── markdown-shortcuts.js
│ ├── merge-deep.js
│ └── old-api.js
├── index.js
├── main.js
└── plugin.js
├── vue.config.js
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vue3Editor
2 |
3 | > An easy-to-use but yet powerful and customizable rich text editor powered by Quill.js and Vue.js
4 |
5 | > This project is built on `vue2-editor`, please see its doc.
6 |
7 | [vue2-editor](https://github.com/davidroyer/vue2-editor)
8 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3-editor",
3 | "version": "0.1.1",
4 | "description": "HTML editor using Vue.js 3, and Quill.js, an open source editor",
5 | "license": "MIT",
6 | "scripts": {
7 | "serve": "vue-cli-service serve",
8 | "build": "vue-cli-service build --target lib --name vue3-editor ./src/index.js",
9 | "lint": "vue-cli-service lint"
10 | },
11 | "main": "dist/vue3-editor.common.js",
12 | "unpkg": "dist/vue3-editor.umd.min.js",
13 | "files": [
14 | "dist"
15 | ],
16 | "peerDependencies": {
17 | "vue": "^3.0.0"
18 | },
19 | "dependencies": {
20 | "core-js": "^3.6.5",
21 | "quill": "^1.3.7"
22 | },
23 | "devDependencies": {
24 | "@vue/cli-plugin-babel": "~4.5.0",
25 | "@vue/cli-plugin-eslint": "~4.5.0",
26 | "@vue/cli-service": "~4.5.0",
27 | "@vue/compiler-sfc": "^3.0.0-0",
28 | "axios": "^0.20.0",
29 | "babel-eslint": "^10.1.0",
30 | "eslint": "^6.7.2",
31 | "eslint-plugin-vue": "^7.0.0-0",
32 | "sass": "^1.26.11",
33 | "sass-loader": "^10.0.2",
34 | "vue": "^3.0.0"
35 | },
36 | "eslintConfig": {
37 | "root": true,
38 | "env": {
39 | "node": true
40 | },
41 | "extends": [
42 | "plugin:vue/vue3-essential",
43 | "eslint:recommended"
44 | ],
45 | "parserOptions": {
46 | "parser": "babel-eslint"
47 | },
48 | "rules": {}
49 | },
50 | "browserslist": [
51 | "> 1%",
52 | "last 2 versions",
53 | "not dead"
54 | ],
55 | "keywords": [
56 | "vue",
57 | "vue-component",
58 | "quill",
59 | "html editor",
60 | "text editor"
61 | ],
62 | "repository": {
63 | "type": "git",
64 | "url": "https://github.com/sunkint/vue3-editor.git"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunkint/vue3-editor/ed0ef422318a0a8f20ac4f1388054ea265266124/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
101 |
102 |
112 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sunkint/vue3-editor/ed0ef422318a0a8f20ac4f1388054ea265266124/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/md-toolbar.scss:
--------------------------------------------------------------------------------
1 | svg {
2 | width: 24px !important;
3 | height: 24px !important;
4 | fill: rgba(65, 65, 65, 0.9);
5 |
6 | }
7 |
8 | button.ql-active svg {
9 | fill: white;
10 | fill: rgba(97, 97, 97, 0.98) !important;
11 | }
12 |
13 | .quillWrapper .ql-snow.ql-toolbar button {
14 | margin: 1px 2px;
15 | display: flex;
16 | padding: 3px;
17 | justify-content: center;
18 | align-items: center;
19 | }
20 |
21 | .ql-snow.ql-toolbar button, .ql-snow .ql-toolbar button {
22 | display: flex;
23 | padding: 3px;
24 | margin: 1px 3px !important;
25 | justify-content: center;
26 | width: 30px !important;
27 | height: 30px !important;
28 | border-radius: 3px;
29 | }
30 |
31 | .ql-toolbar button.ql-active {
32 | background: rgba(85, 85, 97, 0.9) !important;
33 | background: rgba(203, 201, 201, 0.9) !important;
34 | }
35 |
36 | .quillWrapper .ql-toolbar {
37 | padding-bottom: 4px;
38 | display: flex;
39 | align-items: center;
40 | flex-flow: row wrap;
41 | }
42 |
--------------------------------------------------------------------------------
/src/assets/vue3-editor.scss:
--------------------------------------------------------------------------------
1 | .ql-editor {
2 | min-height: 200px;
3 | font-size: 16px;
4 | }
5 |
6 | .ql-snow .ql-stroke.ql-thin,
7 | .ql-snow .ql-thin {
8 | stroke-width: 1px !important;
9 | }
10 |
11 | .quillWrapper .ql-snow.ql-toolbar {
12 | padding-top: 8px;
13 | padding-bottom: 4px;
14 | }
15 | /* .quillWrapper .ql-snow.ql-toolbar button {
16 | margin: 1px 4px;
17 | } */
18 | .quillWrapper .ql-snow.ql-toolbar .ql-formats {
19 | margin-bottom: 10px;
20 | }
21 |
22 | .ql-snow .ql-toolbar button svg,
23 | .quillWrapper .ql-snow.ql-toolbar button svg {
24 | width: 22px;
25 | height: 22px;
26 | }
27 |
28 | .quillWrapper .ql-editor ul[data-checked=false] > li::before,
29 | .quillWrapper .ql-editor ul[data-checked=true] > li::before {
30 | font-size: 1.35em;
31 | vertical-align: baseline;
32 | bottom: -0.065em;
33 | font-weight: 900;
34 | color: #222;
35 | }
36 |
37 | .quillWrapper .ql-snow .ql-stroke {
38 | stroke: rgba(63, 63, 63, 0.95);
39 | stroke-linecap: square;
40 | stroke-linejoin: initial;
41 | stroke-width: 1.7px;
42 | }
43 |
44 | .quillWrapper .ql-picker-label {
45 | font-size: 15px;
46 | }
47 |
48 | .quillWrapper .ql-snow .ql-active .ql-stroke {
49 | stroke-width: 2.25px;
50 | }
51 |
52 | .quillWrapper .ql-toolbar.ql-snow .ql-formats {
53 | vertical-align: top;
54 | }
55 |
56 | .ql-picker {
57 | &:not(.ql-background) {
58 | position: relative;
59 | top: 2px;
60 | }
61 |
62 | &.ql-color-picker {
63 | svg {
64 | width: 22px !important;
65 | height: 22px !important;
66 | }
67 | }
68 | }
69 |
70 | .quillWrapper {
71 | & .imageResizeActive {
72 | img {
73 | display: block;
74 | cursor: pointer;
75 | }
76 |
77 | & ~ div svg {
78 | cursor: pointer;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/components/VueEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------
/src/helpers/axios.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | const CLIENT_ID = "993793b1d8d3e2e";
3 |
4 | // We create our own axios instance and set a custom base URL.
5 | // Note that if we wouldn't set any config here we do not need
6 | // a named export, as we could just `import axios from 'axios'`
7 | export const axiosInstance = axios.create({
8 | baseURL: `https://api.imgur.com/3/`,
9 | headers: { Authorization: "Client-ID " + CLIENT_ID }
10 | });
11 | // export default ({ Vue }) => {
12 | // Vue.prototype.$axios = axiosInstance;
13 | // };
14 |
15 | // Here we define a named export
16 | // that we can later use inside .js files:
17 | // export default axiosInstance;
18 |
19 | // axios({
20 | // url: "https://api.imgur.com/3/image",
21 | // method: "POST",
22 | // headers: { Authorization: "Client-ID " + CLIENT_ID },
23 |
24 | // });
25 |
--------------------------------------------------------------------------------
/src/helpers/custom-link.js:
--------------------------------------------------------------------------------
1 | import Quill from 'quill';
2 | const Link = Quill.import('formats/link');
3 |
4 | export default class CustomLink extends Link {
5 | static sanitize(url) {
6 | const value = super.sanitize(url);
7 | if (value) {
8 | for (let i = 0; i < this.PROTOCOL_WHITELIST.length; i++)
9 | if (value.startsWith(this.PROTOCOL_WHITELIST[i])) {
10 | return value;
11 | }
12 | return `https://${value}`;
13 | }
14 | return value;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/helpers/default-toolbar.js:
--------------------------------------------------------------------------------
1 | let defaultToolbar = [
2 | [{ header: [false, 1, 2, 3, 4, 5, 6] }],
3 | ["bold", "italic", "underline", "strike"], // toggled buttons
4 | [
5 | { align: "" },
6 | { align: "center" },
7 | { align: "right" },
8 | { align: "justify" }
9 | ],
10 | ["blockquote", "code-block"],
11 | [{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
12 | [{ indent: "-1" }, { indent: "+1" }], // outdent/indent
13 | [{ color: [] }, { background: [] }], // dropdown with defaults from theme
14 | ["link", "image", "video"],
15 | ["clean"] // remove formatting button
16 | ];
17 | export default defaultToolbar;
18 |
--------------------------------------------------------------------------------
/src/helpers/fullToolbar.js:
--------------------------------------------------------------------------------
1 | var fullToolbar = [
2 | [{ font: [] }],
3 |
4 | [{ header: [false, 1, 2, 3, 4, 5, 6] }],
5 |
6 | [{ size: ["small", false, "large", "huge"] }],
7 |
8 | ["bold", "italic", "underline", "strike"],
9 |
10 | [
11 | { align: "" },
12 | { align: "center" },
13 | { align: "right" },
14 | { align: "justify" }
15 | ],
16 |
17 | [{ header: 1 }, { header: 2 }],
18 |
19 | ["blockquote", "code-block"],
20 |
21 | [{ list: "ordered" }, { list: "bullet" }, { list: "check" }],
22 |
23 | [{ script: "sub" }, { script: "super" }],
24 |
25 | [{ indent: "-1" }, { indent: "+1" }],
26 |
27 | [{ color: [] }, { background: [] }],
28 |
29 | ["link", "image", "video", "formula"],
30 |
31 | [{ direction: "rtl" }],
32 | ["clean"]
33 | ];
34 |
35 | export default fullToolbar;
36 |
--------------------------------------------------------------------------------
/src/helpers/markdown-shortcuts.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | import Quill from "quill";
3 | let BlockEmbed = Quill.import("blots/block/embed");
4 | class HorizontalRule extends BlockEmbed {}
5 | HorizontalRule.blotName = "hr";
6 | HorizontalRule.tagName = "hr";
7 | Quill.register("formats/horizontal", HorizontalRule);
8 |
9 | class MarkdownShortcuts {
10 | constructor(quill, options) {
11 | this.quill = quill;
12 | this.options = options;
13 |
14 | this.ignoreTags = ["PRE"];
15 | this.matches = [
16 | {
17 | name: "header",
18 | pattern: /^(#){1,6}\s/g,
19 | action: (text, selection, pattern) => {
20 | var match = pattern.exec(text);
21 | if (!match) return;
22 | const size = match[0].length;
23 | // Need to defer this action https://github.com/quilljs/quill/issues/1134
24 | setTimeout(() => {
25 | this.quill.formatLine(selection.index, 0, "header", size - 1);
26 | this.quill.deleteText(selection.index - size, size);
27 | }, 0);
28 | }
29 | },
30 | {
31 | name: "blockquote",
32 | pattern: /^(>)\s/g,
33 | action: (_text, selection) => {
34 | // Need to defer this action https://github.com/quilljs/quill/issues/1134
35 | setTimeout(() => {
36 | this.quill.formatLine(selection.index, 1, "blockquote", true);
37 | this.quill.deleteText(selection.index - 2, 2);
38 | }, 0);
39 | }
40 | },
41 | {
42 | name: "code-block",
43 | pattern: /^`{3}(?:\s|\n)/g,
44 | action: (_text, selection) => {
45 | // Need to defer this action https://github.com/quilljs/quill/issues/1134
46 | setTimeout(() => {
47 | this.quill.formatLine(selection.index, 1, "code-block", true);
48 | this.quill.deleteText(selection.index - 4, 4);
49 | }, 0);
50 | }
51 | },
52 | {
53 | name: "bolditalic",
54 | pattern: /(?:\*|_){3}(.+?)(?:\*|_){3}/g,
55 | action: (text, _selection, pattern, lineStart) => {
56 | let match = pattern.exec(text);
57 |
58 | const annotatedText = match[0];
59 | const matchedText = match[1];
60 | const startIndex = lineStart + match.index;
61 |
62 | if (text.match(/^([*_ \n]+)$/g)) return;
63 |
64 | setTimeout(() => {
65 | this.quill.deleteText(startIndex, annotatedText.length);
66 | this.quill.insertText(startIndex, matchedText, {
67 | bold: true,
68 | italic: true
69 | });
70 | this.quill.format("bold", false);
71 | }, 0);
72 | }
73 | },
74 | {
75 | name: "bold",
76 | pattern: /(?:\*|_){2}(.+?)(?:\*|_){2}/g,
77 | action: (text, _selection, pattern, lineStart) => {
78 | let match = pattern.exec(text);
79 |
80 | const annotatedText = match[0];
81 | const matchedText = match[1];
82 | const startIndex = lineStart + match.index;
83 |
84 | if (text.match(/^([*_ \n]+)$/g)) return;
85 |
86 | setTimeout(() => {
87 | this.quill.deleteText(startIndex, annotatedText.length);
88 | this.quill.insertText(startIndex, matchedText, { bold: true });
89 | this.quill.format("bold", false);
90 | }, 0);
91 | }
92 | },
93 | {
94 | name: "italic",
95 | pattern: /(?:\*|_){1}(.+?)(?:\*|_){1}/g,
96 | action: (text, _selection, pattern, lineStart) => {
97 | let match = pattern.exec(text);
98 |
99 | const annotatedText = match[0];
100 | const matchedText = match[1];
101 | const startIndex = lineStart + match.index;
102 |
103 | if (text.match(/^([*_ \n]+)$/g)) return;
104 |
105 | setTimeout(() => {
106 | this.quill.deleteText(startIndex, annotatedText.length);
107 | this.quill.insertText(startIndex, matchedText, { italic: true });
108 | this.quill.format("italic", false);
109 | }, 0);
110 | }
111 | },
112 | {
113 | name: "strikethrough",
114 | pattern: /(?:~~)(.+?)(?:~~)/g,
115 | action: (text, _selection, pattern, lineStart) => {
116 | let match = pattern.exec(text);
117 |
118 | const annotatedText = match[0];
119 | const matchedText = match[1];
120 | const startIndex = lineStart + match.index;
121 |
122 | if (text.match(/^([*_ \n]+)$/g)) return;
123 |
124 | setTimeout(() => {
125 | this.quill.deleteText(startIndex, annotatedText.length);
126 | this.quill.insertText(startIndex, matchedText, { strike: true });
127 | this.quill.format("strike", false);
128 | }, 0);
129 | }
130 | },
131 | {
132 | name: "code",
133 | pattern: /(?:`)(.+?)(?:`)/g,
134 | action: (text, _selection, pattern, lineStart) => {
135 | let match = pattern.exec(text);
136 |
137 | const annotatedText = match[0];
138 | const matchedText = match[1];
139 | const startIndex = lineStart + match.index;
140 |
141 | if (text.match(/^([*_ \n]+)$/g)) return;
142 |
143 | setTimeout(() => {
144 | this.quill.deleteText(startIndex, annotatedText.length);
145 | this.quill.insertText(startIndex, matchedText, { code: true });
146 | this.quill.format("code", false);
147 | this.quill.insertText(this.quill.getSelection(), " ");
148 | }, 0);
149 | }
150 | },
151 | {
152 | name: "hr",
153 | pattern: /^([-*]\s?){3}/g,
154 | action: (text, selection) => {
155 | const startIndex = selection.index - text.length;
156 | setTimeout(() => {
157 | this.quill.deleteText(startIndex, text.length);
158 |
159 | this.quill.insertEmbed(
160 | startIndex + 1,
161 | "hr",
162 | true,
163 | Quill.sources.USER
164 | );
165 | this.quill.insertText(startIndex + 2, "\n", Quill.sources.SILENT);
166 | this.quill.setSelection(startIndex + 2, Quill.sources.SILENT);
167 | }, 0);
168 | }
169 | },
170 | {
171 | name: "asterisk-ul",
172 | pattern: /^(\*|\+)\s$/g,
173 | action: (_text, selection, _pattern) => {
174 | setTimeout(() => {
175 | this.quill.formatLine(selection.index, 1, "list", "unordered");
176 | this.quill.deleteText(selection.index - 2, 2);
177 | }, 0);
178 | }
179 | },
180 | {
181 | name: "image",
182 | pattern: /(?:!\[(.+?)\])(?:\((.+?)\))/g,
183 | action: (text, selection, pattern) => {
184 | const startIndex = text.search(pattern);
185 | const matchedText = text.match(pattern)[0];
186 | // const hrefText = text.match(/(?:!\[(.*?)\])/g)[0]
187 | const hrefLink = text.match(/(?:\((.*?)\))/g)[0];
188 | const start = selection.index - matchedText.length - 1;
189 | if (startIndex !== -1) {
190 | setTimeout(() => {
191 | this.quill.deleteText(start, matchedText.length);
192 | this.quill.insertEmbed(
193 | start,
194 | "image",
195 | hrefLink.slice(1, hrefLink.length - 1)
196 | );
197 | }, 0);
198 | }
199 | }
200 | },
201 | {
202 | name: "link",
203 | pattern: /(?:\[(.+?)\])(?:\((.+?)\))/g,
204 | action: (text, selection, pattern) => {
205 | const startIndex = text.search(pattern);
206 | const matchedText = text.match(pattern)[0];
207 | const hrefText = text.match(/(?:\[(.*?)\])/g)[0];
208 | const hrefLink = text.match(/(?:\((.*?)\))/g)[0];
209 | const start = selection.index - matchedText.length - 1;
210 | if (startIndex !== -1) {
211 | setTimeout(() => {
212 | this.quill.deleteText(start, matchedText.length);
213 | this.quill.insertText(
214 | start,
215 | hrefText.slice(1, hrefText.length - 1),
216 | "link",
217 | hrefLink.slice(1, hrefLink.length - 1)
218 | );
219 | }, 0);
220 | }
221 | }
222 | }
223 | ];
224 |
225 | // Handler that looks for insert deltas that match specific characters
226 | this.quill.on("text-change", (delta, _oldContents, _source) => {
227 | for (let i = 0; i < delta.ops.length; i++) {
228 | // eslint-disable-next-line no-prototype-builtins
229 | if (delta.ops[i].hasOwnProperty("insert")) {
230 | if (delta.ops[i].insert === " ") {
231 | this.onSpace();
232 | } else if (delta.ops[i].insert === "\n") {
233 | this.onEnter();
234 | }
235 | }
236 | }
237 | });
238 | }
239 |
240 | isValid(text, tagName) {
241 | return (
242 | typeof text !== "undefined" &&
243 | text &&
244 | this.ignoreTags.indexOf(tagName) === -1
245 | );
246 | }
247 |
248 | onSpace() {
249 | const selection = this.quill.getSelection();
250 | if (!selection) return;
251 | const [line, offset] = this.quill.getLine(selection.index);
252 | const text = line.domNode.textContent;
253 | const lineStart = selection.index - offset;
254 | if (this.isValid(text, line.domNode.tagName)) {
255 | for (let match of this.matches) {
256 | const matchedText = text.match(match.pattern);
257 | if (matchedText) {
258 | // We need to replace only matched text not the whole line
259 | console.log("matched:", match.name, text);
260 | match.action(text, selection, match.pattern, lineStart);
261 | return;
262 | }
263 | }
264 | }
265 | }
266 |
267 | onEnter() {
268 | let selection = this.quill.getSelection();
269 | if (!selection) return;
270 | const [line, offset] = this.quill.getLine(selection.index);
271 | const text = line.domNode.textContent + " ";
272 | const lineStart = selection.index - offset;
273 | selection.length = selection.index++;
274 | if (this.isValid(text, line.domNode.tagName)) {
275 | for (let match of this.matches) {
276 | const matchedText = text.match(match.pattern);
277 | if (matchedText) {
278 | console.log("matched", match.name, text);
279 | match.action(text, selection, match.pattern, lineStart);
280 | return;
281 | }
282 | }
283 | }
284 | }
285 | }
286 |
287 | // module.exports = MarkdownShortcuts;
288 | export default MarkdownShortcuts;
289 |
--------------------------------------------------------------------------------
/src/helpers/merge-deep.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Performs a deep merge of `source` into `target`.
3 | * Mutates `target` only but not its objects and arrays.
4 | *
5 | */
6 |
7 | export default function mergeDeep(target, source) {
8 | const isObject = obj => obj && typeof obj === "object";
9 |
10 | if (!isObject(target) || !isObject(source)) {
11 | return source;
12 | }
13 |
14 | Object.keys(source).forEach(key => {
15 | const targetValue = target[key];
16 | const sourceValue = source[key];
17 |
18 | if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
19 | target[key] = targetValue.concat(sourceValue);
20 | } else if (isObject(targetValue) && isObject(sourceValue)) {
21 | target[key] = mergeDeep(Object.assign({}, targetValue), sourceValue);
22 | } else {
23 | target[key] = sourceValue;
24 | }
25 | });
26 |
27 | return target;
28 | }
29 |
--------------------------------------------------------------------------------
/src/helpers/old-api.js:
--------------------------------------------------------------------------------
1 | export default {
2 | props: {
3 | customModules: Array
4 | },
5 | methods: {
6 | registerCustomModules(Quill) {
7 | if (this.customModules !== undefined) {
8 | this.customModules.forEach(customModule => {
9 | Quill.register("modules/" + customModule.alias, customModule.module);
10 | });
11 | }
12 | }
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import Quill from "quill";
2 | import VueEditor from "./components/VueEditor.vue";
3 |
4 | const version = "0.1.0-alpha.2";
5 |
6 | // Declare install function executed by Vue.use()
7 | export function install(app) {
8 | if (install.installed) return;
9 | install.installed = true;
10 |
11 | app.component("VueEditor", VueEditor);
12 | }
13 |
14 | const VPlugin = {
15 | install,
16 | version,
17 | Quill,
18 | VueEditor,
19 | };
20 |
21 | // Auto-install when vue is found (eg. in browser via