├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── LICENSE.txt
├── README.md
├── dist
└── vue-html5-editor.js
├── example
├── basic.html
├── custom-color.html
├── custom-icon.html
├── custom-icons
│ ├── align.png
│ ├── color.gif
│ ├── eraser.gif
│ ├── font.png
│ ├── full-screen.gif
│ ├── hr.gif
│ ├── image.gif
│ ├── info.png
│ ├── link.gif
│ ├── list.png
│ ├── table.gif
│ ├── text.png
│ ├── undo.gif
│ └── unlink.gif
├── custom-modules.html
├── extended-module.html
└── i18n.html
├── package.json
├── rollup.config.js
└── src
├── editor.html
├── editor.js
├── i18n
├── en-us.js
└── zh-cn.js
├── index.js
├── modules
├── align
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── color
│ ├── dashboard.html
│ ├── dashboard.js
│ ├── index.js
│ └── style.css
├── eraser
│ └── index.js
├── font
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── full-screen
│ └── index.js
├── hr
│ └── index.js
├── image
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── index.js
├── info
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── link
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── list
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── table
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── text
│ ├── dashboard.html
│ ├── dashboard.js
│ └── index.js
├── undo
│ └── index.js
└── unlink
│ └── index.js
├── polyfill-ie.js
├── range
├── README.md
├── command.js
├── handler.js
└── util.js
├── style.css
└── util
└── mixin.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "es2015" ]
3 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.less
2 | *.sass
3 | *.pcss
4 | *.css
5 | *.html
6 | src/polyfill-ie.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "node": true,
5 | "browser": true
6 | },
7 | "parserOptions": {
8 | "ecmaVersion": 6,
9 | "ecmaFeatures": {
10 | "experimentalObjectRestSpread": true,
11 | },
12 | "sourceType": "module"
13 | },
14 | "globals": {
15 | "VERSION": false
16 | },
17 | "plugins": [
18 | "import"
19 | ],
20 | "rules": {
21 | "indent": "off",
22 | "semi": [
23 | "error",
24 | "never"
25 | ],
26 | "comma-dangle": [
27 | "error",
28 | "never"
29 | ],
30 | "comma-spacing": "off",
31 | "import/no-extraneous-dependencies": "off",
32 | "no-param-reassign": [
33 | "error",
34 | {
35 | "props": false
36 | }
37 | ],
38 | "no-plusplus": "off",
39 | "eol-last": "off",
40 | "class-methods-use-this": "off",
41 | "object-curly-spacing": "off",
42 | "new-cap": [
43 | "error",
44 | {
45 | "newIsCap": true,
46 | "capIsNew": false,
47 | "properties": true
48 | }
49 | ],
50 | "space-before-blocks": "off",
51 | "radix": [
52 | "error",
53 | "as-needed"
54 | ],
55 | "import/no-unresolved": "off",
56 | "import/extensions": [
57 | "error",
58 | {
59 | "js": "never",
60 | "json": "always"
61 | }
62 | ],
63 | "no-console": [
64 | "error",
65 | {
66 | "allow": [
67 | "warn",
68 | "error"
69 | ]
70 | }
71 | ]
72 | },
73 | "extends": "eslint-config-airbnb"
74 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # idea config
30 | .idea
31 | *.iml
32 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "{}" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright 2015 台俊峰
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 简介 Intro
2 |
3 | Vue-html5-editor是一个Vue的富文本编辑器插件,简洁灵活可扩展,适用于vue2.0以上版本,支持IE11.
4 |
5 | Vue-html5-editor is an html5 wysiwyg editor for vue,easy and flexible,compatible with Vue.js 2.0+,support IE11.
6 |
7 | 
8 |
9 | [点击查看演示效果 Demo is here](http://tai.coding.me/vue-html5-editor)
10 |
11 | # 安装 Installation
12 |
13 | ### Npm
14 |
15 |
16 | ```bash
17 | npm install vue-html5-editor --save-dev
18 | ```
19 |
20 | 引入并安装作为全局组件
21 |
22 | import and install as global component
23 |
24 | ```js
25 | import Vue from 'vue'
26 | import VueHtml5Editor from 'vue-html5-editor'
27 | Vue.use(VueHtml5Editor,options);
28 | ```
29 |
30 | 同样你也可以作为局部组件使用,方便在不同的场景里使用不同的配置.
31 |
32 | ```js
33 | const editor1 = new VueHtml5Editor(options1)
34 | const app1 = new Vue({
35 | components:{
36 | editor1
37 | }
38 | })
39 | const editor2 = new VueHtml5Editor(options2)
40 | const app2 = new Vue({
41 | components:{
42 | editor2
43 | }
44 | })
45 | ```
46 |
47 |
48 | ### 浏览器直接使用 browser
49 |
50 | ```html
51 |
52 |
53 | ```
54 | 通过全局变量`VueHtml5Editor`来安装.
55 |
56 | Install using global variable `VueHtml5Editor`.
57 | ```js
58 | Vue.use(VueHtml5Editor, options)
59 | ```
60 |
61 |
62 | # 使用说明 Usage
63 |
64 | 模板代码如下:
65 |
66 | template code as follows:
67 |
68 | ```html
69 |
70 | ```
71 |
72 | # options
73 |
74 | ```js
75 | Vue.use(VueHtml5Editor, {
76 | // 全局组件名称,使用new VueHtml5Editor(options)时该选项无效
77 | // global component name
78 | name: "vue-html5-editor",
79 | // 是否显示模块名称,开启的话会在工具栏的图标后台直接显示名称
80 | // if set true,will append module name to toolbar after icon
81 | showModuleName: false,
82 | // 自定义各个图标的class,默认使用的是font-awesome提供的图标
83 | // custom icon class of built-in modules,default using font-awesome
84 | icons: {
85 | text: "fa fa-pencil",
86 | color: "fa fa-paint-brush",
87 | font: "fa fa-font",
88 | align: "fa fa-align-justify",
89 | list: "fa fa-list",
90 | link: "fa fa-chain",
91 | unlink: "fa fa-chain-broken",
92 | tabulation: "fa fa-table",
93 | image: "fa fa-file-image-o",
94 | hr: "fa fa-minus",
95 | eraser: "fa fa-eraser",
96 | undo: "fa-undo fa",
97 | "full-screen": "fa fa-arrows-alt",
98 | info: "fa fa-info",
99 | },
100 | // 配置图片模块
101 | // config image module
102 | image: {
103 | // 文件最大体积,单位字节 max file size
104 | sizeLimit: 512 * 1024,
105 | // 上传参数,默认把图片转为base64而不上传
106 | // upload config,default null and convert image to base64
107 | upload: {
108 | url: null,
109 | headers: {},
110 | params: {},
111 | fieldName: {}
112 | },
113 | // 压缩参数,默认使用localResizeIMG进行压缩,设置为null禁止压缩
114 | // compression config,default resize image by localResizeIMG (https://github.com/think2011/localResizeIMG)
115 | // set null to disable compression
116 | compress: {
117 | width: 1600,
118 | height: 1600,
119 | quality: 80
120 | },
121 | // 响应数据处理,最终返回图片链接
122 | // handle response data,return image url
123 | uploadHandler(responseText){
124 | //default accept json data like {ok:false,msg:"unexpected"} or {ok:true,data:"image url"}
125 | var json = JSON.parse(responseText)
126 | if (!json.ok) {
127 | alert(json.msg)
128 | } else {
129 | return json.data
130 | }
131 | }
132 | },
133 | // 语言,内建的有英文(en-us)和中文(zh-cn)
134 | //default en-us, en-us and zh-cn are built-in
135 | language: "zh-cn",
136 | // 自定义语言
137 | i18n: {
138 | //specify your language here
139 | "zh-cn": {
140 | "align": "对齐方式",
141 | "image": "图片",
142 | "list": "列表",
143 | "link": "链接",
144 | "unlink": "去除链接",
145 | "table": "表格",
146 | "font": "文字",
147 | "full screen": "全屏",
148 | "text": "排版",
149 | "eraser": "格式清除",
150 | "info": "关于",
151 | "color": "颜色",
152 | "please enter a url": "请输入地址",
153 | "create link": "创建链接",
154 | "bold": "加粗",
155 | "italic": "倾斜",
156 | "underline": "下划线",
157 | "strike through": "删除线",
158 | "subscript": "上标",
159 | "superscript": "下标",
160 | "heading": "标题",
161 | "font name": "字体",
162 | "font size": "文字大小",
163 | "left justify": "左对齐",
164 | "center justify": "居中",
165 | "right justify": "右对齐",
166 | "ordered list": "有序列表",
167 | "unordered list": "无序列表",
168 | "fore color": "前景色",
169 | "background color": "背景色",
170 | "row count": "行数",
171 | "column count": "列数",
172 | "save": "确定",
173 | "upload": "上传",
174 | "progress": "进度",
175 | "unknown": "未知",
176 | "please wait": "请稍等",
177 | "error": "错误",
178 | "abort": "中断",
179 | "reset": "重置"
180 | }
181 | },
182 | // 隐藏不想要显示出来的模块
183 | // the modules you don't want
184 | hiddenModules: [],
185 | // 自定义要显示的模块,并控制顺序
186 | // keep only the modules you want and customize the order.
187 | // can be used with hiddenModules together
188 | visibleModules: [
189 | "text",
190 | "color",
191 | "font",
192 | "align",
193 | "list",
194 | "link",
195 | "unlink",
196 | "tabulation",
197 | "image",
198 | "hr",
199 | "eraser",
200 | "undo",
201 | "full-screen",
202 | "info",
203 | ],
204 | // 扩展模块,具体可以参考examples或查看源码
205 | // extended modules
206 | modules: {
207 | //omit,reference to source code of build-in modules
208 | }
209 | })
210 | ```
211 |
212 | # 组件属性 attributes
213 |
214 | ```html
215 |
216 | ```
217 |
218 | ### content
219 |
220 | 编辑内容
221 |
222 | The html content to edit
223 |
224 | ### height
225 |
226 | 编辑器高度,如果设置了`auto-height`为true,将设置为编辑器的最小高度.
227 |
228 | The height or min-height ( if auto-height is true ) of editor.
229 |
230 | ### z-index
231 |
232 | 层级,将会设置编辑器容量的`z-index`样式属性,默认为1000.
233 |
234 | Sets z-index style property of editor,default 1000.
235 |
236 | ### auto-height
237 |
238 | 是否自动根据内容控制编辑器高度,默认为true.
239 |
240 | Resize editor height automatically,default true.
241 |
242 | ### show-module-name
243 |
244 | 局部设置是否显示模块名称,会覆盖全局的设定.
245 |
246 | Set `showModuleName` locally.
247 |
248 | # 事件
249 | ```html
250 |
251 | ```
252 |
253 | ### change
254 |
255 | 每次内容有变动时触发,回传改变后的内容.
256 |
257 | Emited when the content changes,and pass the current content as event data.
258 |
259 | # License
260 | [Apache-2.0](http://opensource.org/licenses/Apache-2.0)
261 |
--------------------------------------------------------------------------------
/dist/vue-html5-editor.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Vue-html5-editor 1.1.0
3 | * https://github.com/PeakTai/vue-html5-editor
4 | * build at Thu Apr 13 2017 15:51:01 GMT+0800 (CST)
5 | */
6 |
7 | (function (global, factory) {
8 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
9 | typeof define === 'function' && define.amd ? define(factory) :
10 | (global.VueHtml5Editor = factory());
11 | }(this, (function () { 'use strict';
12 |
13 | function __$styleInject(css, returnValue) {
14 | if (typeof document === 'undefined') {
15 | return returnValue;
16 | }
17 | css = css || '';
18 | var head = document.head || document.getElementsByTagName('head')[0];
19 | var style = document.createElement('style');
20 | style.type = 'text/css';
21 | if (style.styleSheet){
22 | style.styleSheet.cssText = css;
23 | } else {
24 | style.appendChild(document.createTextNode(css));
25 | }
26 | head.appendChild(style);
27 | return returnValue;
28 | }
29 |
30 | var polyfill = function () {
31 | // https://tc39.github.io/ecma262/#sec-array.prototype.includes
32 | if (!Array.prototype.includes) {
33 | Object.defineProperty(Array.prototype, 'includes', {
34 | value: function value(searchElement, fromIndex) {
35 | // 1. Let O be ? ToObject(this value).
36 | if (this == null) {
37 | throw new TypeError('"this" is null or not defined')
38 | }
39 |
40 | var o = Object(this);
41 |
42 | // 2. Let len be ? ToLength(? Get(O, "length")).
43 | var len = o.length >>> 0;
44 |
45 | // 3. If len is 0, return false.
46 | if (len === 0) {
47 | return false
48 | }
49 |
50 | // 4. Let n be ? ToInteger(fromIndex).
51 | // (If fromIndex is undefined, this step produces the value 0.)
52 | var n = fromIndex | 0;
53 |
54 | // 5. If n ≥ 0, then
55 | // a. Let k be n.
56 | // 6. Else n < 0,
57 | // a. Let k be len + n.
58 | // b. If k < 0, let k be 0.
59 | var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
60 |
61 | // 7. Repeat, while k < len
62 | while (k < len) {
63 | // a. Let elementK be the result of ? Get(O, ! ToString(k)).
64 | // b. If SameValueZero(searchElement, elementK) is true, return true.
65 | // c. Increase k by 1.
66 | // NOTE: === provides the correct "SameValueZero" comparison needed here.
67 | if (o[k] === searchElement) {
68 | return true
69 | }
70 | k++;
71 | }
72 |
73 | // 8. Return false
74 | return false
75 | }
76 | });
77 | }
78 | // text.contains()
79 | if (!Text.prototype.contains) {
80 | Text.prototype.contains = function contains(node) {
81 | return this === node
82 | };
83 | }
84 | };
85 |
86 | var template = "
{{$parent.locale[\"left justify\"]}} {{$parent.locale[\"center justify\"]}} {{$parent.locale[\"right justify\"]}}
";
87 |
88 | /**
89 | * Created by peak on 2017/2/10.
90 | */
91 | var dashboard = {
92 | template: template
93 | };
94 |
95 | /**
96 | * text align
97 | * Created by peak on 16/8/18.
98 | */
99 | var align = {
100 | name: 'align',
101 | icon: 'fa fa-align-center',
102 | i18n: 'align',
103 | dashboard: dashboard
104 | };
105 |
106 | var template$1 = " ";
107 |
108 | __$styleInject(".vue-html5-editor .color-card{margin:2px;width:30px;height:30px;float:left;cursor:pointer}",undefined);
109 |
110 | /**
111 | * Created by peak on 2017/2/10.
112 | */
113 | var dashboard$1 = {
114 | template: template$1,
115 | data: function data(){
116 | return {
117 | // foreColor,backColor
118 | command: 'foreColor',
119 | colors: [
120 | '#000000', '#000033', '#000066', '#000099', '#003300', '#003333', '#003366',
121 | '#003399', '#006600', '#006633', '#009900', '#330000', '#330033', '#330066',
122 | '#333300', '#333366', '#660000', '#660033', '#663300', '#666600', '#666633',
123 | '#666666', '#666699', '#990000', '#990033', '#9900CC', '#996600', '#FFCC00',
124 | '#FFCCCC', '#FFCC99', '#FFFF00', '#FF9900', '#CCFFCC', '#CCFFFF', '#CCFF99'
125 | ]
126 | }
127 | },
128 | methods: {
129 | changeColor: function changeColor(color){
130 | this.$parent.execCommand(this.command, color);
131 | }
132 | }
133 | };
134 |
135 | /**
136 | * fore color and back color
137 | * Created by peak on 16/8/18.
138 | */
139 | var color = {
140 | name: 'color',
141 | icon: 'fa fa-paint-brush',
142 | i18n: 'color',
143 | dashboard: dashboard$1
144 | };
145 |
146 | /**
147 | * remove format of selection
148 | * Created by peak on 16/8/18.
149 | */
150 | var eraser = {
151 | name: 'eraser',
152 | icon: 'fa fa-eraser',
153 | i18n: 'eraser',
154 | handler: function handler(editor) {
155 | editor.execCommand('removeFormat');
156 | }
157 | };
158 |
159 | var template$2 = " {{$parent.locale[\"heading\"]}}: H{{h}}
{{$parent.locale[\"font name\"]}}: {{name}}
{{$parent.locale[\"font size\"]}}: {{size}}
{{$parent.locale[\"line height\"]}}: {{lh}}
";
160 |
161 | /**
162 | * Created by peak on 2017/2/14.
163 | */
164 | var Command = {
165 | JUSTIFY_LEFT: 'justifyLeft',
166 | JUSTIFY_CENTER: 'justifyCenter',
167 | JUSTIFY_RIGHT: 'justifyRight',
168 | FORE_COLOR: 'foreColor',
169 | BACK_COLOR: 'backColor',
170 | REMOVE_FORMAT: 'removeFormat',
171 | FONT_NAME: 'fontName',
172 | FONT_SIZE: 'fontSize',
173 | FORMAT_BLOCK: 'formatBlock',
174 | LINE_HEIGHT: 'lineHeight',
175 | INSERT_HORIZONTAL_RULE: 'insertHorizontalRule',
176 | INSERT_IMAGE: 'insertImage',
177 | CREATE_LINK: 'createLink',
178 | INSERT_ORDERED_LIST: 'insertOrderedList',
179 | INSERT_UNORDERED_LIST: 'insertUnorderedList',
180 | INSERT_HTML: 'insertHTML',
181 | BOLD: 'bold',
182 | ITALIC: 'italic',
183 | UNDERLINE: 'underline',
184 | STRIKE_THROUGH: 'strikeThrough',
185 | SUBSCRIPT: 'subscript',
186 | SUPERSCRIPT: 'superscript',
187 | UNDO: 'undo',
188 | UNLINK: 'unlink'
189 | };
190 |
191 | /**
192 | * Created by peak on 2017/2/10.
193 | */
194 | var dashboard$2 = {
195 | template: template$2,
196 | data: function data(){
197 | return {
198 | nameList: [
199 | 'Microsoft YaHei',
200 | 'Helvetica Neue',
201 | 'Helvetica',
202 | 'Arial',
203 | 'sans-serif',
204 | 'Verdana',
205 | 'Georgia',
206 | 'Times New Roman',
207 | 'Trebuchet MS',
208 | 'Microsoft JhengHei',
209 | 'Courier New',
210 | 'Impact',
211 | 'Comic Sans MS',
212 | 'Consolas'
213 | ],
214 | lineHeightList: [
215 | '1.0', '1.2', '1.5', '1.8', '2.0', '2.5', '3.0'
216 | ],
217 | fontSizeList: [
218 | '12px', '14px', '16px', '18px', '20px', '22px', '24px'
219 | ]
220 | }
221 | },
222 | methods: {
223 | setFontName: function setFontName(name){
224 | this.$parent.execCommand('fontName', name);
225 | },
226 | setFontSize: function setFontSize(size){
227 | this.$parent.execCommand('fontSize', size);
228 | },
229 | setHeading: function setHeading(heading){
230 | this.$parent.execCommand('formatBlock', ("h" + heading));
231 | },
232 | setLineHeight: function setLineHeight(lh){
233 | this.$parent.execCommand(Command.LINE_HEIGHT, lh);
234 | }
235 | },
236 | created: function created(){
237 | var config = this.$options.module.config;
238 | // font name
239 | if (!config) {
240 | return
241 | }
242 | if (Array.isArray(config.fontNames)) {
243 | this.nameList = config.fontNames;
244 | }
245 | }
246 | };
247 |
248 | /**
249 | * font name and font size
250 | * Created by peak on 16/8/18.
251 | */
252 | var font = {
253 | name: 'font',
254 | icon: 'fa fa-font',
255 | i18n: 'font',
256 | dashboard: dashboard$2
257 | };
258 |
259 | /**
260 | * toggle full screen mode
261 | * Created by peak on 16/8/18.
262 | */
263 | var fullScreen$1 = {
264 | name: 'full-screen',
265 | icon: 'fa fa-arrows-alt',
266 | i18n: 'full screen',
267 | handler: function handler(editor) {
268 | editor.toggleFullScreen();
269 | }
270 | };
271 |
272 | /**
273 | * hr
274 | * Created by peak on 16/8/20.
275 | */
276 | var hr = {
277 | name: 'hr',
278 | icon: 'fa fa-minus',
279 | i18n: 'hr',
280 | handler: function handler(editor) {
281 | editor.execCommand('insertHorizontalRule');
282 | }
283 | // init (editor) {
284 | //
285 | // },
286 | // destroyed(editor){
287 | //
288 | // },
289 | };
290 |
291 | var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
292 |
293 |
294 |
295 |
296 |
297 | function createCommonjsModule(fn, module) {
298 | return module = { exports: {} }, fn(module, module.exports), module.exports;
299 | }
300 |
301 | var lrz_all_bundle = createCommonjsModule(function (module, exports) {
302 | !function(e,t){if("object"==typeof exports&&"object"==typeof module){ module.exports=t(); }else if("function"==typeof undefined&&undefined.amd){ undefined([],t); }else{var n=t();for(var r in n){ ("object"==typeof exports?exports:e)[r]=n[r]; }}}(commonjsGlobal,function(){return function(e){function t(r){if(n[r]){ return n[r].exports; }var i=n[r]={exports:{},id:r,loaded:!1};return e[r].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){n(6),n(7),e.exports=n(8);},function(e,t,n){(function(t){!function(n){function r(e,t){return function(){e.apply(t,arguments);}}function i(e){if("object"!=typeof this){ throw new TypeError("Promises must be constructed via new"); }if("function"!=typeof e){ throw new TypeError("not a function"); }this._state=null,this._value=null,this._deferreds=[],l(e,r(a,this),r(s,this));}function o(e){var t=this;return null===this._state?void this._deferreds.push(e):void f(function(){var n=t._state?e.onFulfilled:e.onRejected;if(null===n){ return void(t._state?e.resolve:e.reject)(t._value); }var r;try{r=n(t._value);}catch(i){return void e.reject(i)}e.resolve(r);})}function a(e){try{if(e===this){ throw new TypeError("A promise cannot be resolved with itself."); }if(e&&("object"==typeof e||"function"==typeof e)){var t=e.then;if("function"==typeof t){ return void l(r(t,e),r(a,this),r(s,this)) }}this._state=!0,this._value=e,u.call(this);}catch(n){s.call(this,n);}}function s(e){this._state=!1,this._value=e,u.call(this);}function u(){
303 | var this$1 = this;
304 | for(var e=0,t=this._deferreds.length;t>e;e++){ o.call(this$1,this$1._deferreds[e]); }this._deferreds=null;}function c(e,t,n,r){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.resolve=n,this.reject=r;}function l(e,t,n){var r=!1;try{e(function(e){r||(r=!0,t(e));},function(e){r||(r=!0,n(e));});}catch(i){if(r){ return; }r=!0,n(i);}}var f="function"==typeof t&&t||function(e){setTimeout(e,1);},d=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};i.prototype["catch"]=function(e){return this.then(null,e)},i.prototype.then=function(e,t){var n=this;return new i(function(r,i){o.call(n,new c(e,t,r,i));})},i.all=function(){var e=Array.prototype.slice.call(1===arguments.length&&d(arguments[0])?arguments[0]:arguments);return new i(function(t,n){function r(o,a){try{if(a&&("object"==typeof a||"function"==typeof a)){var s=a.then;if("function"==typeof s){ return void s.call(a,function(e){r(o,e);},n) }}e[o]=a,0===--i&&t(e);}catch(u){n(u);}}if(0===e.length){ return t([]); }for(var i=e.length,o=0;or;r++){ e[r].then(t,n); }})},i._setImmediateFn=function(e){f=e;},i.prototype.always=function(e){var t=this.constructor;return this.then(function(n){return t.resolve(e()).then(function(){return n})},function(n){return t.resolve(e()).then(function(){throw n})})},"undefined"!=typeof e&&e.exports?e.exports=i:n.Promise||(n.Promise=i);}(this);}).call(t,n(2).setImmediate);},function(e,t,n){(function(e,r){function i(e,t){this._id=e,this._clearFn=t;}var o=n(3).nextTick,a=Function.prototype.apply,s=Array.prototype.slice,u={},c=0;t.setTimeout=function(){return new i(a.call(setTimeout,window,arguments),clearTimeout)},t.setInterval=function(){return new i(a.call(setInterval,window,arguments),clearInterval)},t.clearTimeout=t.clearInterval=function(e){e.close();},i.prototype.unref=i.prototype.ref=function(){},i.prototype.close=function(){this._clearFn.call(window,this._id);},t.enroll=function(e,t){clearTimeout(e._idleTimeoutId),e._idleTimeout=t;},t.unenroll=function(e){clearTimeout(e._idleTimeoutId),e._idleTimeout=-1;},t._unrefActive=t.active=function(e){clearTimeout(e._idleTimeoutId);var t=e._idleTimeout;t>=0&&(e._idleTimeoutId=setTimeout(function(){e._onTimeout&&e._onTimeout();},t));},t.setImmediate="function"==typeof e?e:function(e){var n=c++,r=arguments.length<2?!1:s.call(arguments,1);return u[n]=!0,o(function(){u[n]&&(r?e.apply(null,r):e.call(null),t.clearImmediate(n));}),n},t.clearImmediate="function"==typeof r?r:function(e){delete u[e];};}).call(t,n(2).setImmediate,n(2).clearImmediate);},function(e,t){function n(){c=!1,a.length?u=a.concat(u):l=-1,u.length&&r();}function r(){if(!c){var e=setTimeout(n);c=!0;for(var t=u.length;t;){for(a=u,u=[];++l1){ for(var n=1;na;a++){ o[a]=n.charCodeAt(a); }return i}function a(e,t){var n=new XMLHttpRequest;n.open("GET",e,!0),n.responseType="blob",n.onload=function(e){(200==this.status||0===this.status)&&t(this.response);},n.send();}function s(e,t){function n(n){var r=u(n),i=c(n);e.exifdata=r||{},e.iptcdata=i||{},t&&t.call(e);}if(e.src){ if(/^data\:/i.test(e.src)){var r=o(e.src);n(r);}else if(/^blob\:/i.test(e.src)){var i=new FileReader;i.onload=function(e){n(e.target.result);},a(e.src,function(e){i.readAsArrayBuffer(e);});}else{var s=new XMLHttpRequest;s.onload=function(){200==this.status||0===this.status?n(s.response):t(new Error("Could not load image")),s=null;},s.open("GET",e.src,!0),s.responseType="arraybuffer",s.send(null);} }else if(window.FileReader&&(e instanceof window.Blob||e instanceof window.File)){var i=new FileReader;i.onload=function(e){p&&console.log("Got file of length "+e.target.result.byteLength),n(e.target.result);},i.readAsArrayBuffer(e);}}function u(e){var t=new DataView(e);if(p&&console.log("Got file of length "+e.byteLength),255!=t.getUint8(0)||216!=t.getUint8(1)){ return p&&console.log("Not a valid JPEG"),!1; }for(var n,r=2,i=e.byteLength;i>r;){if(255!=t.getUint8(r)){ return p&&console.log("Not a valid marker at offset "+r+", found: "+t.getUint8(r)),!1; }if(n=t.getUint8(r+1),p&&console.log(n),225==n){ return p&&console.log("Found 0xFFE1 marker"),g(t,r+4,t.getUint16(r+2)-2); }r+=2+t.getUint16(r+2);}}function c(e){var t=new DataView(e);if(p&&console.log("Got file of length "+e.byteLength),255!=t.getUint8(0)||216!=t.getUint8(1)){ return p&&console.log("Not a valid JPEG"),!1; }for(var n=2,r=e.byteLength,i=function(e,t){return 56===e.getUint8(t)&&66===e.getUint8(t+1)&&73===e.getUint8(t+2)&&77===e.getUint8(t+3)&&4===e.getUint8(t+4)&&4===e.getUint8(t+5)};r>n;){if(i(t,n)){var o=t.getUint8(n+7);o%2!==0&&(o+=1),0===o&&(o=4);var a=n+8+o,s=t.getUint16(n+6+o);return l(e,a,s)}n++;}}function l(e,t,n){for(var r,i,o,a,s,u=new DataView(e),c={},l=t;t+n>l;){ 28===u.getUint8(l)&&2===u.getUint8(l+1)&&(a=u.getUint8(l+2),a in S&&(o=u.getInt16(l+3),s=o+5,i=S[a],r=h(u,l+5,o),c.hasOwnProperty(i)?c[i]instanceof Array?c[i].push(r):c[i]=[c[i],r]:c[i]=r)),l++; }return c}function f(e,t,n,r,i){var o,a,s,u=e.getUint16(n,!i),c={};for(s=0;u>s;s++){ o=n+12*s+2,a=r[e.getUint16(o,!i)],!a&&p&&console.log("Unknown tag: "+e.getUint16(o,!i)),c[a]=d(e,o,t,n,i); }return c}function d(e,t,n,r,i){var o,a,s,u,c,l,f=e.getUint16(t+2,!i),d=e.getUint32(t+4,!i),g=e.getUint32(t+8,!i)+n;switch(f){case 1:case 7:if(1==d){ return e.getUint8(t+8,!i); }for(o=d>4?g:t+8,a=[],u=0;d>u;u++){ a[u]=e.getUint8(o+u); }return a;case 2:return o=d>4?g:t+8,h(e,o,d-1);case 3:if(1==d){ return e.getUint16(t+8,!i); }for(o=d>2?g:t+8,a=[],u=0;d>u;u++){ a[u]=e.getUint16(o+2*u,!i); }return a;case 4:if(1==d){ return e.getUint32(t+8,!i); }for(a=[],u=0;d>u;u++){ a[u]=e.getUint32(g+4*u,!i); }return a;case 5:if(1==d){ return c=e.getUint32(g,!i),l=e.getUint32(g+4,!i),s=new Number(c/l),s.numerator=c,s.denominator=l,s; }for(a=[],u=0;d>u;u++){ c=e.getUint32(g+8*u,!i),l=e.getUint32(g+4+8*u,!i),a[u]=new Number(c/l),a[u].numerator=c,a[u].denominator=l; }return a;case 9:if(1==d){ return e.getInt32(t+8,!i); }for(a=[],u=0;d>u;u++){ a[u]=e.getInt32(g+4*u,!i); }return a;case 10:if(1==d){ return e.getInt32(g,!i)/e.getInt32(g+4,!i); }for(a=[],u=0;d>u;u++){ a[u]=e.getInt32(g+8*u,!i)/e.getInt32(g+4+8*u,!i); }return a}}function h(e,t,n){var r,i="";for(r=t;t+n>r;r++){ i+=String.fromCharCode(e.getUint8(r)); }return i}function g(e,t){if("Exif"!=h(e,t,4)){ return p&&console.log("Not valid EXIF data! "+h(e,t,4)),!1; }var n,r,i,o,a,s=t+6;if(18761==e.getUint16(s)){ n=!1; }else{if(19789!=e.getUint16(s)){ return p&&console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)"),!1; }n=!0;}if(42!=e.getUint16(s+2,!n)){ return p&&console.log("Not valid TIFF data! (no 0x002A)"),!1; }var u=e.getUint32(s+4,!n);if(8>u){ return p&&console.log("Not valid TIFF data! (First offset less than 8)",e.getUint32(s+4,!n)),!1; }if(r=f(e,s,s+u,v,n),r.ExifIFDPointer){o=f(e,s,s+r.ExifIFDPointer,w,n);for(i in o){switch(i){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":o[i]=b[i][o[i]];break;case"ExifVersion":case"FlashpixVersion":o[i]=String.fromCharCode(o[i][0],o[i][1],o[i][2],o[i][3]);break;case"ComponentsConfiguration":o[i]=b.Components[o[i][0]]+b.Components[o[i][1]]+b.Components[o[i][2]]+b.Components[o[i][3]];}r[i]=o[i];}}if(r.GPSInfoIFDPointer){a=f(e,s,s+r.GPSInfoIFDPointer,y,n);for(i in a){switch(i){case"GPSVersionID":a[i]=a[i][0]+"."+a[i][1]+"."+a[i][2]+"."+a[i][3];}r[i]=a[i];}}return r}var p=!1,m=function(e){return e instanceof m?e:this instanceof m?void(this.EXIFwrapped=e):new m(e)};"undefined"!=typeof e&&e.exports&&(t=e.exports=m),t.EXIF=m;var w=m.Tags={36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubsecTime",37521:"SubsecTimeOriginal",37522:"SubsecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"ISOSpeedRatings",34856:"OECF",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRation",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",40965:"InteroperabilityIFDPointer",42016:"ImageUniqueID"},v=m.TiffTags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright"},y=m.GPSTags={0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential"},b=m.StringValues={ExposureProgram:{0:"Not defined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Not defined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},Components:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"}},S={120:"caption",110:"credit",25:"keywords",55:"dateCreated",80:"byline",85:"bylineTitle",122:"captionWriter",105:"headline",116:"copyright",15:"category"};m.getData=function(e,t){return(e instanceof Image||e instanceof HTMLImageElement)&&!e.complete?!1:(n(e)?t&&t.call(e):s(e,t),!0)},m.getTag=function(e,t){return n(e)?e.exifdata[t]:void 0},m.getAllTags=function(e){if(!n(e)){ return{}; }var t,r=e.exifdata,i={};for(t in r){ r.hasOwnProperty(t)&&(i[t]=r[t]); }return i},m.pretty=function(e){if(!n(e)){ return""; }var t,r=e.exifdata,i="";for(t in r){ r.hasOwnProperty(t)&&(i+="object"==typeof r[t]?r[t]instanceof Number?t+" : "+r[t]+" ["+r[t].numerator+"/"+r[t].denominator+"]\r\n":t+" : ["+r[t].length+" values]\r\n":t+" : "+r[t]+"\r\n"); }return i},m.readFromBinaryFile=function(e){return u(e)},r=[],i=function(){return m}.apply(t,r),!(void 0!==i&&(e.exports=i));}).call(this);},function(e,t,n){var r,i;!function(){function n(e){var t=e.naturalWidth,n=e.naturalHeight;if(t*n>1048576){var r=document.createElement("canvas");r.width=r.height=1;var i=r.getContext("2d");return i.drawImage(e,-t+1,0),0===i.getImageData(0,0,1,1).data[3]}return!1}function o(e,t,n){var r=document.createElement("canvas");r.width=1,r.height=n;var i=r.getContext("2d");i.drawImage(e,0,0);for(var o=i.getImageData(0,0,1,n).data,a=0,s=n,u=n;u>a;){var c=o[4*(u-1)+3];0===c?s=u:a=u,u=s+a>>1;}var l=u/n;return 0===l?1:l}function a(e,t,n){var r=document.createElement("canvas");return s(e,r,t,n),r.toDataURL("image/jpeg",t.quality||.8)}function s(e,t,r,i){var a=e.naturalWidth,s=e.naturalHeight,c=r.width,l=r.height,f=t.getContext("2d");f.save(),u(t,f,c,l,r.orientation);var d=n(e);d&&(a/=2,s/=2);var h=1024,g=document.createElement("canvas");g.width=g.height=h;for(var p=g.getContext("2d"),m=i?o(e,a,s):1,w=Math.ceil(h*c/a),v=Math.ceil(h*l/s/m),y=0,b=0;s>y;){for(var S=0,I=0;a>S;){ p.clearRect(0,0,h,h),p.drawImage(e,-S,-y),f.drawImage(g,0,0,h,h,I,b,w,v),S+=h,I+=w; }y+=h,b+=v;}f.restore(),g=p=null;}function u(e,t,n,r,i){switch(i){case 5:case 6:case 7:case 8:e.width=r,e.height=n;break;default:e.width=n,e.height=r;}switch(i){case 2:t.translate(n,0),t.scale(-1,1);break;case 3:t.translate(n,r),t.rotate(Math.PI);break;case 4:t.translate(0,r),t.scale(1,-1);break;case 5:t.rotate(.5*Math.PI),t.scale(1,-1);break;case 6:t.rotate(.5*Math.PI),t.translate(0,-r);break;case 7:t.rotate(.5*Math.PI),t.translate(n,-r),t.scale(-1,1);break;case 8:t.rotate(-.5*Math.PI),t.translate(-n,0);}}function c(e){if(window.Blob&&e instanceof Blob){var t=new Image,n=window.URL&&window.URL.createObjectURL?window.URL:window.webkitURL&&window.webkitURL.createObjectURL?window.webkitURL:null;if(!n){ throw Error("No createObjectURL function found to create blob url"); }t.src=n.createObjectURL(e),this.blob=e,e=t;}if(!e.naturalWidth&&!e.naturalHeight){var r=this;e.onload=function(){var e=r.imageLoadListeners;if(e){r.imageLoadListeners=null;for(var t=0,n=e.length;n>t;t++){ e[t](); }}},this.imageLoadListeners=[];}this.srcImage=e;}c.prototype.render=function(e,t,n){if(this.imageLoadListeners){var r=this;return void this.imageLoadListeners.push(function(){r.render(e,t,n);})}t=t||{};var i=this.srcImage,o=i.src,u=o.length,c=i.naturalWidth,l=i.naturalHeight,f=t.width,d=t.height,h=t.maxWidth,g=t.maxHeight,p=this.blob&&"image/jpeg"===this.blob.type||0===o.indexOf("data:image/jpeg")||o.indexOf(".jpg")===u-4||o.indexOf(".jpeg")===u-5;f&&!d?d=l*f/c<<0:d&&!f?f=c*d/l<<0:(f=c,d=l),h&&f>h&&(f=h,d=l*f/c<<0),g&&d>g&&(d=g,f=c*d/l<<0);var m={width:f,height:d};for(var w in t){ m[w]=t[w]; }var v=e.tagName.toLowerCase();"img"===v?e.src=a(this.srcImage,m,p):"canvas"===v&&s(this.srcImage,e,m,p),"function"==typeof this.onrender&&this.onrender(e),n&&n();},r=[],i=function(){return c}.apply(t,r),!(void 0!==i&&(e.exports=i));}();},function(e,t){function n(e){function t(e){for(var t=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],n=0;64>n;n++){var r=F((t[n]*e+50)/100);1>r?r=1:r>255&&(r=255),D[N[n]]=r;}for(var i=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],o=0;64>o;o++){var a=F((i[o]*e+50)/100);1>a?a=1:a>255&&(a=255),x[N[o]]=a;}for(var s=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],u=0,c=0;8>c;c++){ for(var l=0;8>l;l++){ U[u]=1/(D[N[u]]*s[c]*s[l]*8),C[u]=1/(x[N[u]]*s[c]*s[l]*8),u++; } }}function n(e,t){for(var n=0,r=0,i=new Array,o=1;16>=o;o++){for(var a=1;a<=e[o];a++){ i[t[r]]=[],i[t[r]][0]=n,i[t[r]][1]=o,r++,n++; }n*=2;}return i}function r(){y=n(W,H),b=n(V,X),S=n(z,q),I=n(Q,Y);}function i(){for(var e=1,t=2,n=1;15>=n;n++){for(var r=e;t>r;r++){ A[32767+r]=n,T[32767+r]=[],T[32767+r][1]=n,T[32767+r][0]=r; }for(var i=-(t-1);-e>=i;i++){ A[32767+i]=n,T[32767+i]=[],T[32767+i][1]=n,T[32767+i][0]=t-1+i; }e<<=1,t<<=1;}}function o(){for(var e=0;256>e;e++){ k[e]=19595*e,k[e+256>>0]=38470*e,k[e+512>>0]=7471*e+32768,k[e+768>>0]=-11059*e,k[e+1024>>0]=-21709*e,k[e+1280>>0]=32768*e+8421375,k[e+1536>>0]=-27439*e,k[e+1792>>0]=-5329*e; }}function a(e){for(var t=e[0],n=e[1]-1;n>=0;){ t&1<O&&(255==G?(s(255),s(0)):s(G),O=7,G=0); }}function s(e){M.push(j[e]);}function u(e){s(e>>8&255),s(255&e);}function c(e,t){var n,r,i,o,a,s,u,c,l,f=0;var d=8,h=64;for(l=0;d>l;++l){n=e[f],r=e[f+1],i=e[f+2],o=e[f+3],a=e[f+4],s=e[f+5],u=e[f+6],c=e[f+7];var g=n+c,p=n-c,m=r+u,w=r-u,v=i+s,y=i-s,b=o+a,S=o-a,I=g+b,P=g-b,F=m+v,D=m-v;e[f]=I+F,e[f+4]=I-F;var x=.707106781*(D+P);e[f+2]=P+x,e[f+6]=P-x,I=S+y,F=y+w,D=w+p;var U=.382683433*(I-D),C=.5411961*I+U,T=1.306562965*D+U,A=.707106781*F,R=p+A,M=p-A;e[f+5]=M+C,e[f+3]=M-C,e[f+1]=R+T,e[f+7]=R-T,f+=8;}for(f=0,l=0;d>l;++l){n=e[f],r=e[f+8],i=e[f+16],o=e[f+24],a=e[f+32],s=e[f+40],u=e[f+48],c=e[f+56];var G=n+c,O=n-c,_=r+u,B=r-u,E=i+s,j=i-s,k=o+a,N=o-a,W=G+k,H=G-k,z=_+E,q=_-E;e[f]=W+z,e[f+32]=W-z;var V=.707106781*(q+H);e[f+16]=H+V,e[f+48]=H-V,W=N+j,z=j+B,q=B+O;var X=.382683433*(W-q),Q=.5411961*W+X,Y=1.306562965*q+X,K=.707106781*z,J=O+K,Z=O-K;e[f+40]=Z+Q,e[f+24]=Z-Q,e[f+8]=J+Y,e[f+56]=J-Y,f++;}var $;for(l=0;h>l;++l){ $=e[l]*t[l],L[l]=$>0?$+.5|0:$-.5|0; }return L}function l(){u(65504),u(16),s(74),s(70),s(73),s(70),s(0),s(1),s(1),s(0),u(1),u(1),s(0),s(0);}function f(e,t){u(65472),u(17),s(8),u(t),u(e),s(3),s(1),s(17),s(0),s(2),s(17),s(1),s(3),s(17),s(1);}function d(){u(65499),u(132),s(0);for(var e=0;64>e;e++){ s(D[e]); }s(1);for(var t=0;64>t;t++){ s(x[t]); }}function h(){u(65476),u(418),s(0);for(var e=0;16>e;e++){ s(W[e+1]); }for(var t=0;11>=t;t++){ s(H[t]); }s(16);for(var n=0;16>n;n++){ s(z[n+1]); }for(var r=0;161>=r;r++){ s(q[r]); }s(1);for(var i=0;16>i;i++){ s(V[i+1]); }for(var o=0;11>=o;o++){ s(X[o]); }s(17);for(var a=0;16>a;a++){ s(Q[a+1]); }for(var c=0;161>=c;c++){ s(Y[c]); }}function g(){u(65498),u(12),s(3),s(1),s(0),s(2),s(17),s(3),s(17),s(0),s(63),s(0);}function p(e,t,n,r,i){var o,s=i[0],u=i[240];var l=16,f=63,d=64;for(var h=c(e,t),g=0;d>g;++g){ R[N[g]]=h[g]; }var p=R[0]-n;n=R[0],0==p?a(r[0]):(o=32767+p,a(r[A[o]]),a(T[o]));for(var m=63;m>0&&0==R[m];m--){ }if(0==m){ return a(s),n; }for(var w,v=1;m>=v;){for(var y=v;0==R[v]&&m>=v;++v){ }var b=v-y;if(b>=l){w=b>>4;for(var S=1;w>=S;++S){ a(u); }b=15&b;}o=32767+R[v],a(i[(b<<4)+A[o]]),a(T[o]),v++;}return m!=f&&a(s),n}function m(){for(var e=String.fromCharCode,t=0;256>t;t++){ j[t]=e(t); }}function w(e){if(0>=e&&(e=1),e>100&&(e=100),P!=e){var n=0;n=50>e?Math.floor(5e3/e):Math.floor(200-2*e),t(n),P=e;}}function v(){var t=(new Date).getTime();e||(e=50),m(),r(),i(),o(),w(e);(new Date).getTime()-t;}var y,b,S,I,P,F=(Math.round,Math.floor),D=new Array(64),x=new Array(64),U=new Array(64),C=new Array(64),T=new Array(65535),A=new Array(65535),L=new Array(64),R=new Array(64),M=[],G=0,O=7,_=new Array(64),B=new Array(64),E=new Array(64),j=new Array(256),k=new Array(2048),N=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],W=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],H=[0,1,2,3,4,5,6,7,8,9,10,11],z=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],q=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],V=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],X=[0,1,2,3,4,5,6,7,8,9,10,11],Q=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],Y=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(e,t,n){var r=(new Date).getTime();t&&w(t),M=new Array,G=0,O=7,u(65496),l(),d(),f(e.width,e.height),h(),g();var i=0,o=0,s=0;G=0,O=7,this.encode.displayName="_encode_";for(var c,m,v,P,F,D,x,T,A,L=e.data,R=e.width,j=e.height,N=4*R,W=0;j>W;){for(c=0;N>c;){for(F=N*W+c,D=F,x=-1,T=0,A=0;64>A;A++){ T=A>>3,x=4*(7&A),D=F+T*N+x,W+T>=j&&(D-=N*(W+1+T-j)),c+x>=N&&(D-=c+x-N+4),m=L[D++],v=L[D++],P=L[D++],_[A]=(k[m]+k[v+256>>0]+k[P+512>>0]>>16)-128,B[A]=(k[m+768>>0]+k[v+1024>>0]+k[P+1280>>0]>>16)-128,E[A]=(k[m+1280>>0]+k[v+1536>>0]+k[P+1792>>0]>>16)-128; }i=p(_,U,i,y,S),o=p(B,C,o,b,I),s=p(E,C,s,b,I),c+=32;}W+=8;}if(O>=0){var H=[];H[1]=O+1,H[0]=(1<V;V++){ q[V]=M[V].charCodeAt(); }M=[];(new Date).getTime()-r;return q}var X="data:image/jpeg;base64,"+btoa(M.join(""));M=[];(new Date).getTime()-r;return X},v();}e.exports=n;},function(e,t,n){function r(e,t){var n=this;if(!e){ throw new Error("没有收到图片,可能的解决方案:https://github.com/think2011/localResizeIMG/issues/7"); }t=t||{},n.defaults={width:null,height:null,fieldName:"file",quality:.7},n.file=e;for(var r in t){ t.hasOwnProperty(r)&&(n.defaults[r]=t[r]); }return this.init()}function i(e){var t=null;return t=e?[].filter.call(document.scripts,function(t){return-1!==t.src.indexOf(e)})[0]:document.scripts[document.scripts.length-1],t?t.src.substr(0,t.src.lastIndexOf("/")):null}function o(e){var t;t=e.split(",")[0].indexOf("base64")>=0?atob(e.split(",")[1]):unescape(e.split(",")[1]);for(var n=e.split(",")[0].split(":")[1].split(";")[0],r=new Uint8Array(t.length),i=0;ie.file.size?(i=new FormData,t=e.file):(i=new s.FormData,t=o(r)),i.append(e.defaults.fieldName,t,e.fileName.replace(/\..+/g,".jpg")),n({formData:i,fileLen:+t.size,base64:r,base64Len:r.length,origin:e.file,file:t});for(var a in e){ e.hasOwnProperty(a)&&(e[a]=null); }URL.revokeObjectURL(e.blob);});},!r&&(i.crossOrigin="*"),i.src=c;})},r.prototype._getBase64=function(){var e=this,t=e.img,n=e.file,r=e.canvas;return new a(function(i){try{u.getData("object"==typeof n?n:t,function(){e.orientation=u.getTag(this,"Orientation"),e.resize=e._getResize(),e.ctx=r.getContext("2d"),r.width=e.resize.width,r.height=e.resize.height,e.ctx.fillStyle="#fff",e.ctx.fillRect(0,0,r.width,r.height),c.oldIOS?e._createBase64ForOldIOS().then(i):e._createBase64().then(i);});}catch(o){throw new Error(o)}})},r.prototype._createBase64ForOldIOS=function(){var e=this,t=e.img,r=e.canvas,i=e.defaults,o=e.orientation;return new a(function(e){!function(){var a=[n(6)];(function(n){var a=new n(t);"5678".indexOf(o)>-1?a.render(r,{width:r.height,height:r.width,orientation:o}):a.render(r,{width:r.width,height:r.height,orientation:o}),e(r.toDataURL("image/jpeg",i.quality));}).apply(null,a);}();})},r.prototype._createBase64=function(){var e=this,t=e.resize,r=e.img,i=e.canvas,o=e.ctx,s=e.defaults,u=e.orientation;switch(u){case 3:o.rotate(180*Math.PI/180),o.drawImage(r,-t.width,-t.height,t.width,t.height);break;case 6:o.rotate(90*Math.PI/180),o.drawImage(r,0,-t.width,t.height,t.width);break;case 8:o.rotate(270*Math.PI/180),o.drawImage(r,-t.height,0,t.height,t.width);break;case 2:o.translate(t.width,0),o.scale(-1,1),o.drawImage(r,0,0,t.width,t.height);break;case 4:o.translate(t.width,0),o.scale(-1,1),o.rotate(180*Math.PI/180),o.drawImage(r,-t.width,-t.height,t.width,t.height);break;case 5:o.translate(t.width,0),o.scale(-1,1),o.rotate(90*Math.PI/180),o.drawImage(r,0,-t.width,t.height,t.width);break;case 7:o.translate(t.width,0),o.scale(-1,1),o.rotate(270*Math.PI/180),o.drawImage(r,-t.height,0,t.height,t.width);break;default:o.drawImage(r,0,0,t.width,t.height);}return new a(function(e){c.oldAndroid||c.mQQBrowser||!navigator.userAgent?!function(){var t=[n(7)];(function(t){var n=new t,r=o.getImageData(0,0,i.width,i.height);e(n.encode(r,100*s.quality));}).apply(null,t);}():e(i.toDataURL("image/jpeg",s.quality));})},r.prototype._getResize=function(){var e=this,t=e.img,n=e.defaults,r=n.width,i=n.height,o=e.orientation,a={width:t.width,height:t.height};if("5678".indexOf(o)>-1&&(a.width=t.height,a.height=t.width),a.width=r/i?a.width>r&&(a.width=r,a.height=Math.ceil(r/s)):a.height>i&&(a.height=i,a.width=Math.ceil(i*s)):r?r=3264||a.height>=2448;){ a.width*=.8,a.height*=.8; }return a},window.lrz=function(e,t){return new r(e,t)},window.lrz.version="4.9.40",
307 | e.exports=window.lrz;}])});
308 | });
309 |
310 | var template$3 = " ";
311 |
312 | /**
313 | * Created by peak on 2017/2/10.
314 | */
315 | var dashboard$3 = {
316 | template: template$3,
317 | data: function data() {
318 | return {
319 | imageUrl: '',
320 | upload: {
321 | status: 'ready', // progress,success,error,abort
322 | errorMsg: null,
323 | progressComputable: false,
324 | complete: 0
325 | }
326 | }
327 | },
328 | methods: {
329 | reset: function reset(){
330 | this.upload.status = 'ready';
331 | },
332 | insertImageUrl: function insertImageUrl() {
333 | if (!this.imageUrl) {
334 | return
335 | }
336 | this.$parent.execCommand(Command.INSERT_IMAGE, this.imageUrl);
337 | this.imageUrl = null;
338 | },
339 | pick: function pick() {
340 | this.$refs.file.click();
341 | },
342 | setUploadError: function setUploadError(msg){
343 | this.upload.status = 'error';
344 | this.upload.errorMsg = msg;
345 | },
346 | process: function process() {
347 | var this$1 = this;
348 |
349 | var component = this;
350 | var config = this.$options.module.config;
351 | // compatibility with older format
352 | // {
353 | // server: null,
354 | // fieldName: 'image',
355 | // compress: true,
356 | // width: 1600,
357 | // height: 1600,
358 | // quality: 80
359 | // }
360 | // ----------- divider ----------------
361 | // {
362 | // upload: {
363 | // url: null,
364 | // headers: {},
365 | // params: {},
366 | // fieldName: {}
367 | // },
368 | // compress: {
369 | // width: 1600,
370 | // height: 1600,
371 | // quality: 80
372 | // },
373 | // }
374 |
375 | if (!config.upload && typeof config.server === 'string') {
376 | config.upload = {url: config.server};
377 | }
378 | if (config.upload && !config.upload.url) {
379 | config.upload = null;
380 | }
381 | if (config.upload && typeof config.fieldName === 'string') {
382 | config.upload.fieldName = config.fieldName;
383 | }
384 |
385 | if (typeof config.compress === 'boolean') {
386 | config.compress = {
387 | width: config.width,
388 | height: config.height,
389 | quality: config.quality
390 | };
391 | }
392 |
393 | var file = this.$refs.file.files[0];
394 | if (file.size > config.sizeLimit) {
395 | this.setUploadError(this.$parent.locale['exceed size limit']);
396 | return
397 | }
398 | this.$refs.file.value = null;
399 |
400 | if (config.compress) {
401 | config.compress.fieldName = config.upload && config.upload.fieldName
402 | ? config.upload.fieldName : 'image';
403 | lrz_all_bundle(file, config.compress).then(function (rst) {
404 | if (config.upload) {
405 | component.uploadToServer(rst.file);
406 | } else {
407 | component.insertBase64(rst.base64);
408 | }
409 | }).catch(function (err) {
410 | this$1.setUploadError(err.toString());
411 | });
412 | return
413 | }
414 | // 不需要压缩
415 | // base64
416 | if (!config.upload) {
417 | var reader = new FileReader();
418 | reader.onload = function (e) {
419 | component.insertBase64(e.target.result);
420 | };
421 | reader.readAsDataURL(file);
422 | return
423 | }
424 | // 上传服务器
425 | component.uploadToServer(file);
426 | },
427 | insertBase64: function insertBase64(data) {
428 | this.$parent.execCommand(Command.INSERT_IMAGE, data);
429 | },
430 | uploadToServer: function uploadToServer(file) {
431 | var this$1 = this;
432 |
433 | var config = this.$options.module.config;
434 |
435 | var formData = new FormData();
436 | formData.append(config.upload.fieldName || 'image', file);
437 |
438 | if (typeof config.upload.params === 'object') {
439 | Object.keys(config.upload.params).forEach(function (key) {
440 | var value = config.upload.params[key];
441 | if (Array.isArray(value)) {
442 | value.forEach(function (v) {
443 | formData.append(key, v);
444 | });
445 | } else {
446 | formData.append(key, value);
447 | }
448 | });
449 | }
450 |
451 | var xhr = new XMLHttpRequest();
452 |
453 | xhr.onprogress = function (e) {
454 | this$1.upload.status = 'progress';
455 | if (e.lengthComputable) {
456 | this$1.upload.progressComputable = true;
457 | var percentComplete = e.loaded / e.total;
458 | this$1.upload.complete = (percentComplete * 100).toFixed(2);
459 | } else {
460 | this$1.upload.progressComputable = false;
461 | }
462 | };
463 |
464 | xhr.onload = function () {
465 | if (xhr.status !== 200) {
466 | this$1.setUploadError(("request error,code " + (xhr.status)));
467 | return
468 | }
469 |
470 | try {
471 | var url = config.uploadHandler(xhr.responseText);
472 | if (url) {
473 | this$1.$parent.execCommand(Command.INSERT_IMAGE, url);
474 | }
475 | } catch (err) {
476 | this$1.setUploadError(err.toString());
477 | } finally {
478 | this$1.upload.status = 'ready';
479 | }
480 | };
481 |
482 | xhr.onerror = function () {
483 | // find network info in brower tools
484 | this$1.setUploadError('request error');
485 | };
486 |
487 | xhr.onabort = function () {
488 | this$1.upload.status = 'abort';
489 | };
490 |
491 | xhr.open('POST', config.upload.url);
492 | if (typeof config.upload.headers === 'object') {
493 | Object.keys(config.upload.headers).forEach(function (k) {
494 | xhr.setRequestHeader(k, config.upload.headers[k]);
495 | });
496 | }
497 | xhr.send(formData);
498 | }
499 | }
500 | };
501 |
502 | /**
503 | * insert image
504 | * Created by peak on 16/8/18.
505 | */
506 | var image = {
507 | name: 'image',
508 | icon: 'fa fa-file-image-o',
509 | i18n: 'image',
510 | config: {
511 | // server: null,
512 | // fieldName: 'image',
513 | // compress: true,
514 | // width: 1600,
515 | // height: 1600,
516 | // quality: 80,
517 | sizeLimit: 512 * 1024,// 512k
518 | // upload: {
519 | // url: null,
520 | // headers: {},
521 | // params: {},
522 | // fieldName: {}
523 | // },
524 | compress: {
525 | width: 1600,
526 | height: 1600,
527 | quality: 80
528 | },
529 | uploadHandler: function uploadHandler(responseText){
530 | var json = JSON.parse(responseText);
531 | return json.ok ? json.data : null
532 | }
533 | },
534 | dashboard: dashboard$3
535 | };
536 |
537 | var template$4 = " ";
538 |
539 | /**
540 | * Created by peak on 2017/2/10.
541 | */
542 | var dashboard$4 = {
543 | template: template$4,
544 | data: function data(){
545 | return {
546 | version: "1.1.0"
547 | }
548 | }
549 | };
550 |
551 | /**
552 | * editor info
553 | * Created by peak on 16/8/18.
554 | */
555 | var info = {
556 | name: 'info',
557 | icon: 'fa fa-info',
558 | i18n: 'info',
559 | // handler () {
560 | //
561 | // },
562 | // init (editor) {
563 | //
564 | // },
565 | // destroyed(editor){
566 | //
567 | // },
568 | dashboard: dashboard$4
569 | };
570 |
571 | var template$5 = "";
572 |
573 | var dashboard$5 = {
574 | template: template$5,
575 | data: function data(){
576 | return {url: null}
577 | },
578 | methods: {
579 | createLink: function createLink(){
580 | if (!this.url) {
581 | return
582 | }
583 | this.$parent.execCommand('createLink', this.url);
584 | this.url = null;
585 | }
586 | }
587 | };
588 |
589 | /**
590 | * create link
591 | * Created by peak on 16/8/18.
592 | */
593 | var link = {
594 | name: 'link',
595 | icon: 'fa fa-chain',
596 | i18n: 'link',
597 | dashboard: dashboard$5
598 | };
599 |
600 | var template$6 = " {{$parent.locale[\"ordered list\"]}} {{$parent.locale[\"unordered list\"]}}
";
601 |
602 | /**
603 | * Created by peak on 2017/2/10.
604 | */
605 | var dashboard$6 = {
606 | template: template$6
607 | };
608 |
609 | /**
610 | * list,ul,ol
611 | * Created by peak on 16/8/18.
612 | */
613 | var list = {
614 | name: 'list',
615 | icon: 'fa fa-list',
616 | i18n: 'list',
617 | dashboard: dashboard$6
618 | };
619 |
620 | var template$7 = "";
621 |
622 | /**
623 | * Created by peak on 2017/2/10.
624 | */
625 | var dashboard$7 = {
626 | template: template$7,
627 | data: function data(){
628 | return {
629 | rows: 2,
630 | cols: 2,
631 | hasHead: false,
632 | striped: false,
633 | hover: false
634 | }
635 | },
636 | methods: {
637 | insertTable: function insertTable(){
638 | if (this.rows < 2 || this.rows > 10) {
639 | return
640 | }
641 | if (this.cols < 2 || this.cols > 10) {
642 | return
643 | }
644 | var table = '';
645 | for (var i = 0; i < this.rows; i++) {
646 | table += '';
647 | for (var j = 0; j < this.cols; j++) {
648 | table += ' ';
649 | }
650 | table += ' ';
651 | }
652 | table += '
';
653 | this.$parent.execCommand('insertHTML', table);
654 | }
655 | }
656 | };
657 |
658 | /**
659 | * insert table
660 | * Created by peak on 16/8/18.
661 | */
662 | var table = {
663 | // can not named table
664 | // dashboard.html will add to editor as a child component and named as module name
665 | // Do not use built-in or reserved HTML elements as component id
666 | name: 'tabulation',
667 | icon: 'fa fa-table',
668 | i18n: 'table',
669 | dashboard: dashboard$7
670 | };
671 |
672 | var template$8 = " {{$parent.locale[\"bold\"]}} {{$parent.locale[\"italic\"]}} {{$parent.locale[\"underline\"]}} {{$parent.locale[\"strike through\"]}} {{$parent.locale[\"subscript\"]}} {{$parent.locale[\"superscript\"]}}
";
673 |
674 | var dashboard$8 = {
675 | template: template$8
676 | };
677 |
678 | /**
679 | * text,set the text bold or italic or underline or with strike through or subscript or superscript
680 | * Created by peak on 16/8/18.
681 | */
682 | var text = {
683 | name: 'text',
684 | icon: 'fa fa-pencil',
685 | i18n: 'text',
686 | dashboard: dashboard$8
687 | };
688 |
689 | /**
690 | * undo
691 | * Created by peak on 16/8/20.
692 | */
693 | var undo = {
694 | name: 'undo',
695 | icon: 'fa-undo fa',
696 | i18n: 'undo',
697 | handler: function handler(editor) {
698 | editor.execCommand('undo');
699 | }
700 | };
701 |
702 | /**
703 | * unlink
704 | * Created by peak on 16/8/18.
705 | */
706 | var unlink = {
707 | name: 'unlink',
708 | icon: 'fa fa-chain-broken',
709 | i18n: 'unlink',
710 | handler: function handler(editor) {
711 | editor.execCommand('unlink');
712 | }
713 | };
714 |
715 | /**
716 | * build-in moduls
717 | * Created by peak on 2016/11/1.
718 | */
719 | var buildInModules = [
720 | text,
721 | color,
722 | font,
723 | align,
724 | list,
725 | link,
726 | unlink,
727 | table,
728 | image,
729 | hr,
730 | eraser,
731 | undo,
732 | fullScreen$1,
733 | info
734 | ];
735 |
736 | /**
737 | * Created by peak on 2017/2/15.
738 | */
739 | /**
740 | * add every elements of extArr to sourceArr.
741 | * @param sourceArr
742 | * @param extArr
743 | */
744 | var mergeArray = function (sourceArr, extArr) {
745 | // note: Array.prototype.push.apply(arr1,arr2) is unreliable
746 | extArr.forEach(function (el) {
747 | sourceArr.push(el);
748 | });
749 | };
750 |
751 | /**
752 | * find all the descendant text nodes of a element
753 | * @param ancestor
754 | */
755 | var getDescendantTextNodes = function (ancestor) {
756 | if (ancestor.nodeType === Node.TEXT_NODE) {
757 | return [ancestor]
758 | }
759 | var textNodes = [];
760 | if (!ancestor.hasChildNodes()) {
761 | return textNodes
762 | }
763 | var childNodes = ancestor.childNodes;
764 | for (var i = 0; i < childNodes.length; i++) {
765 | var node = childNodes[i];
766 | if (node.nodeType === Node.TEXT_NODE) {
767 | textNodes.push(node);
768 | } else if (node.nodeType === Node.ELEMENT_NODE) {
769 | mergeArray(textNodes, getDescendantTextNodes(node));
770 | }
771 | }
772 | return textNodes
773 | };
774 | /**
775 | * find all the descendant text nodes of an ancestor element that before the specify end element,
776 | * the ancestor element must contains the end element.
777 | * @param ancestor
778 | * @param endEl
779 | */
780 | var getBeforeEndDescendantTextNodes = function (ancestor, endEl) {
781 | var textNodes = [];
782 | var endIndex = 0;
783 | for (var i = 0; i < ancestor.childNodes.length; i++) {
784 | if (ancestor.childNodes[i].contains(endEl)) {
785 | endIndex = i;
786 | break
787 | }
788 | }
789 |
790 | for (var i$1 = 0; i$1 <= endIndex; i$1++) {
791 | var node = ancestor.childNodes[i$1];
792 | if (node === endEl) {
793 | mergeArray(textNodes, getDescendantTextNodes(node));
794 | } else if (i$1 === endIndex) {
795 | if (node.nodeType === Node.TEXT_NODE) {
796 | textNodes.push(node);
797 | } else if (node.nodeType === Node.ELEMENT_NODE) {
798 | mergeArray(textNodes, getBeforeEndDescendantTextNodes(node, endEl));
799 | }
800 | } else if (node.nodeType === Node.TEXT_NODE) {
801 | textNodes.push(node);
802 | } else if (node.nodeType === Node.ELEMENT_NODE) {
803 | mergeArray(textNodes, getDescendantTextNodes(node));
804 | }
805 | }
806 | return textNodes
807 | };
808 | /**
809 | * find all the descendant text nodes of an ancestor element that after the specify start element,
810 | * the ancestor element must contains the start element.
811 | * @param ancestor
812 | * @param startEl
813 | */
814 | var getAfterStartDescendantTextNodes = function (ancestor, startEl) {
815 | var textNodes = [];
816 | var startIndex = 0;
817 | for (var i = 0; i < ancestor.childNodes.length; i++) {
818 | if (ancestor.childNodes[i].contains(startEl)) {
819 | startIndex = i;
820 | break
821 | }
822 | }
823 |
824 | for (var i$1 = startIndex; i$1 < ancestor.childNodes.length; i$1++) {
825 | var node = ancestor.childNodes[i$1];
826 | if (node === startEl) {
827 | mergeArray(textNodes, getDescendantTextNodes(node));
828 | } else if (i$1 === startIndex) {
829 | if (node.nodeType === Node.TEXT_NODE) {
830 | textNodes.push(node);
831 | } else if (node.nodeType === Node.ELEMENT_NODE) {
832 | mergeArray(textNodes,
833 | getAfterStartDescendantTextNodes(node, startEl));
834 | }
835 | } else if (node.nodeType === Node.TEXT_NODE) {
836 | textNodes.push(node);
837 | } else if (node.nodeType === Node.ELEMENT_NODE) {
838 | mergeArray(textNodes,
839 | getDescendantTextNodes(node));
840 | }
841 | }
842 | return textNodes
843 | };
844 |
845 |
846 | /**
847 | * get the closest parent block node of a text node.
848 | * @param node
849 | * @return {Node}
850 | */
851 | var getParentBlockNode = function (node) {
852 | var blockNodeNames = ['DIV', 'P', 'SECTION', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
853 | 'OL', 'UL', 'LI', 'TR', 'TD', 'TH', 'TBODY', 'THEAD', 'TABLE', 'ARTICLE', 'HEADER', 'FOOTER'];
854 | var container = node;
855 | while (container) {
856 | if (blockNodeNames.includes(container.nodeName)) {
857 | break
858 | }
859 | container = container.parentNode;
860 | }
861 | return container
862 | };
863 |
864 | var isInlineElement = function (node) {
865 | var inlineNodeNames = ['A', 'ABBR', 'ACRONYM', 'B', 'CITE', 'CODE', 'EM', 'I',
866 | 'FONT', 'IMG', 'S', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'U', 'SUB', 'SUP'];
867 | return inlineNodeNames.includes(node.nodeName)
868 | };
869 |
870 | // for IE 11
871 | if (!Text.prototype.contains) {
872 | Text.prototype.contains = function contains(otherNode) {
873 | return this === otherNode
874 | };
875 | }
876 |
877 |
878 | /**
879 | * Created by peak on 2017/2/14.
880 | */
881 | var RangeHandler = function RangeHandler(range) {
882 | if (!range || !(range instanceof Range)) {
883 | throw new TypeError('cant\'t resolve range')
884 | }
885 | this.range = range;
886 | };
887 |
888 |
889 | /**
890 | * find all the text nodes in range
891 | */
892 | RangeHandler.prototype.getAllTextNodesInRange = function getAllTextNodesInRange () {
893 | var startContainer = this.range.startContainer;
894 | var endContainer = this.range.endContainer;
895 | var rootEl = this.range.commonAncestorContainer;
896 | var textNodes = [];
897 |
898 | if (startContainer === endContainer) {
899 | if (startContainer.nodeType === Node.TEXT_NODE) {
900 | return [startContainer]
901 | }
902 | var childNodes = startContainer.childNodes;
903 | for (var i = this.range.startOffset; i < this.range.endOffset; i++) {
904 | mergeArray(textNodes, getDescendantTextNodes(childNodes[i]));
905 | }
906 | return textNodes
907 | }
908 |
909 | var startIndex = 0;
910 | var endIndex = 0;
911 | for (var i$1 = 0; i$1 < rootEl.childNodes.length; i$1++) {
912 | var node = rootEl.childNodes[i$1];
913 | if (node.contains(startContainer)) {
914 | startIndex = i$1;
915 | }
916 | if (node.contains(endContainer)) {
917 | endIndex = i$1;
918 | }
919 | }
920 |
921 | for (var i$2 = startIndex; i$2 <= endIndex; i$2++) {
922 | var node$1 = rootEl.childNodes[i$2];
923 | if (i$2 === startIndex) {
924 | if (node$1.nodeType === Node.TEXT_NODE) {
925 | textNodes.push(node$1);
926 | } else if (node$1.nodeType === Node.ELEMENT_NODE) {
927 | mergeArray(textNodes, getAfterStartDescendantTextNodes(node$1, startContainer));
928 | }
929 | } else if (i$2 === endIndex) {
930 | if (node$1.nodeType === Node.TEXT_NODE) {
931 | textNodes.push(node$1);
932 | } else if (node$1.nodeType === Node.ELEMENT_NODE) {
933 | mergeArray(textNodes, getBeforeEndDescendantTextNodes(node$1, endContainer));
934 | }
935 | } else if (node$1.nodeType === Node.TEXT_NODE) {
936 | textNodes.push(node$1);
937 | } else if (node$1.nodeType === Node.ELEMENT_NODE) {
938 | mergeArray(textNodes, getDescendantTextNodes(node$1));
939 | }
940 | }
941 | return textNodes
942 | };
943 |
944 | /**
945 | * execute edit command
946 | * @param {String} command
947 | * @param arg
948 | */
949 | RangeHandler.prototype.execCommand = function execCommand (command, arg) {
950 | var this$1 = this;
951 |
952 | switch (command) {
953 |
954 | case Command.JUSTIFY_LEFT: {
955 | document.execCommand(Command.JUSTIFY_LEFT, false, arg);
956 | break
957 | }
958 |
959 | case Command.JUSTIFY_RIGHT: {
960 | document.execCommand(Command.JUSTIFY_RIGHT, false, arg);
961 | break
962 | }
963 |
964 | case Command.JUSTIFY_CENTER: {
965 | document.execCommand(Command.JUSTIFY_CENTER, false, arg);
966 | break
967 | }
968 |
969 | case Command.FORE_COLOR: {
970 | document.execCommand(Command.FORE_COLOR, false, arg);
971 | break
972 | }
973 | case Command.BACK_COLOR: {
974 | document.execCommand(Command.BACK_COLOR, false, arg);
975 | break
976 | }
977 | case Command.REMOVE_FORMAT: {
978 | document.execCommand(Command.REMOVE_FORMAT, false, arg);
979 | break
980 | }
981 | case Command.FONT_NAME: {
982 | document.execCommand(Command.FONT_NAME, false, arg);
983 | break
984 | }
985 | case Command.FONT_SIZE: {
986 | // 重新实现,改为直接修改样式
987 | var textNodes = this.getAllTextNodesInRange();
988 | if (!textNodes.length) {
989 | break
990 | }
991 | if (textNodes.length === 1 && textNodes[0] === this.range.startContainer
992 | && textNodes[0] === this.range.endContainer) {
993 | var textNode = textNodes[0];
994 | if (this.range.startOffset === 0
995 | && this.range.endOffset === textNode.textContent.length) {
996 | if (textNode.parentNode.childNodes.length === 1
997 | && isInlineElement(textNode.parentNode)) {
998 | textNode.parentNode.style.fontSize = arg;
999 | break
1000 | }
1001 | var span = document.createElement('span');
1002 | span.style.fontSize = arg;
1003 | textNode.parentNode.insertBefore(span, textNode);
1004 | span.appendChild(textNode);
1005 | break
1006 | }
1007 | var span$1 = document.createElement('span');
1008 | span$1.innerText = textNode.textContent.substring(
1009 | this.range.startOffset, this.range.endOffset);
1010 | span$1.style.fontSize = arg;
1011 | var frontPart = document.createTextNode(
1012 | textNode.textContent.substring(0, this.range.startOffset));
1013 | textNode.parentNode.insertBefore(frontPart, textNode);
1014 | textNode.parentNode.insertBefore(span$1, textNode);
1015 | textNode.textContent = textNode.textContent.substring(this.range.endOffset);
1016 | this.range.setStart(span$1, 0);
1017 | this.range.setEnd(span$1, 1);
1018 | break
1019 | }
1020 |
1021 | textNodes.forEach(function (textNode) {
1022 | if (textNode === this$1.range.startContainer) {
1023 | if (this$1.range.startOffset === 0) {
1024 | if (textNode.parentNode.childNodes.length === 1
1025 | && isInlineElement(textNode.parentNode)) {
1026 | textNode.parentNode.style.fontSize = arg;
1027 | } else {
1028 | var span$1 = document.createElement('span');
1029 | span$1.style.fontSize = arg;
1030 | textNode.parentNode.insertBefore(span$1, textNode);
1031 | span$1.appendChild(textNode);
1032 | }
1033 | return
1034 | }
1035 | var span$2 = document.createElement('span');
1036 | textNode.textContent = textNode.textContent.substring(
1037 | 0, this$1.range.startOffset);
1038 | span$2.style.fontSize = arg;
1039 | textNode.parentNode.insertBefore(span$2, textNode);
1040 | this$1.range.setStart(textNode, 0);
1041 | return
1042 | }
1043 | if (textNode === this$1.range.endContainer) {
1044 | if (this$1.range.endOffset === textNode.textContent.length) {
1045 | if (textNode.parentNode.childNodes.length === 1
1046 | && isInlineElement(textNode.parentNode)) {
1047 | textNode.parentNode.style.fontSize = arg;
1048 | } else {
1049 | var span$3 = document.createElement('span');
1050 | span$3.style.fontSize = arg;
1051 | textNode.parentNode.insertBefore(span$3, textNode);
1052 | span$3.appendChild(textNode);
1053 | }
1054 | return
1055 | }
1056 | var span$4 = document.createElement('span');
1057 | textNode.textContent = textNode.textContent.substring(this$1.range.endOffset);
1058 | span$4.style.fontSize = arg;
1059 | textNode.parentNode.insertBefore(span$4, textNode);
1060 | span$4.appendChild(textNode);
1061 | this$1.range.setStart(textNode, textNode.textContent.length);
1062 | return
1063 | }
1064 | if (textNode.parentNode.childNodes.length === 1
1065 | && isInlineElement(textNode.parentNode)) {
1066 | textNode.parentNode.style.fontSize = arg;
1067 | return
1068 | }
1069 |
1070 | var span = document.createElement('span');
1071 | span.style.fontSize = arg;
1072 | textNode.parentNode.insertBefore(span, textNode);
1073 | span.appendChild(textNode);
1074 | });
1075 | break
1076 | }
1077 | case Command.FORMAT_BLOCK: {
1078 | if (document.execCommand(Command.FORMAT_BLOCK, false, arg)) {
1079 | break
1080 | }
1081 | // hack
1082 | var element = document.createElement(arg);
1083 | this.range.surroundContents(element);
1084 | break
1085 | }
1086 | case Command.LINE_HEIGHT: {
1087 | var textNodes$1 = this.getAllTextNodesInRange();
1088 | textNodes$1.forEach(function (textNode) {
1089 | var parentBlock = getParentBlockNode(textNode);
1090 | if (parentBlock) {
1091 | parentBlock.style.lineHeight = arg;
1092 | }
1093 | });
1094 | break
1095 | }
1096 | case Command.INSERT_HORIZONTAL_RULE: {
1097 | document.execCommand(Command.INSERT_HORIZONTAL_RULE, false);
1098 | break
1099 | }
1100 | case Command.INSERT_IMAGE: {
1101 | document.execCommand(Command.INSERT_IMAGE, false, arg);
1102 | break
1103 | }
1104 | case Command.CREATE_LINK: {
1105 | document.execCommand(Command.CREATE_LINK, false, arg);
1106 | break
1107 | }
1108 | case Command.INSERT_ORDERED_LIST: {
1109 | document.execCommand(Command.INSERT_ORDERED_LIST, false, arg);
1110 | break
1111 | }
1112 | case Command.INSERT_UNORDERED_LIST: {
1113 | document.execCommand(Command.INSERT_UNORDERED_LIST, false, arg);
1114 | break
1115 | }
1116 | case Command.INSERT_HTML: {
1117 | if (document.execCommand(Command.INSERT_HTML, false, arg)) {
1118 | break
1119 | }
1120 | // hack
1121 | var fragment = document.createDocumentFragment();
1122 | var div = document.createElement('div');
1123 | div.innerHTML = arg;
1124 | if (div.hasChildNodes()) {
1125 | for (var i = 0; i < div.childNodes.length; i++) {
1126 | fragment.appendChild(div.childNodes[i].cloneNode(true));
1127 | }
1128 | }
1129 | this.range.deleteContents();
1130 | this.range.insertNode(fragment);
1131 | break
1132 | }
1133 | case Command.BOLD: {
1134 | document.execCommand(Command.BOLD, false, arg);
1135 | break
1136 | }
1137 | case Command.ITALIC: {
1138 | document.execCommand(Command.ITALIC, false);
1139 | break
1140 | }
1141 | case Command.UNDERLINE: {
1142 | document.execCommand(Command.UNDERLINE, false);
1143 | break
1144 | }
1145 | case Command.STRIKE_THROUGH: {
1146 | document.execCommand(Command.STRIKE_THROUGH, false);
1147 | break
1148 | }
1149 | case Command.SUBSCRIPT: {
1150 | document.execCommand(Command.SUBSCRIPT, false);
1151 | break
1152 | }
1153 | case Command.SUPERSCRIPT: {
1154 | document.execCommand(Command.SUPERSCRIPT, false);
1155 | break
1156 | }
1157 | case Command.UNDO: {
1158 | document.execCommand(Command.UNDO, false);
1159 | break
1160 | }
1161 | case Command.UNLINK: {
1162 | document.execCommand(Command.UNLINK, false);
1163 | break
1164 | }
1165 | default: {
1166 | document.execCommand(command, false, arg);
1167 | break
1168 | }
1169 | }
1170 | };
1171 |
1172 | __$styleInject(".vue-html5-editor,.vue-html5-editor *{box-sizing:border-box}.vue-html5-editor{font-size:14px;line-height:1.5;background-color:#fff;color:#333;border:1px solid #ddd;text-align:left;border-radius:5px;overflow:hidden}.vue-html5-editor.full-screen{position:fixed!important;top:0!important;left:0!important;bottom:0!important;right:0!important;border-radius:0}.vue-html5-editor>.toolbar{position:relative;background-color:inherit}.vue-html5-editor>.toolbar>ul{list-style:none;padding:0;margin:0;border-bottom:1px solid #ddd}.vue-html5-editor>.toolbar>ul>li{display:inline-block;cursor:pointer;text-align:center;line-height:36px;padding:0 10px}.vue-html5-editor>.toolbar>ul>li .icon{height:16px;width:16px;display:inline-block;vertical-align:middle}.vue-html5-editor>.toolbar>.dashboard{background-color:inherit;border-bottom:1px solid #ddd;padding:10px;position:absolute;top:100%;left:0;right:0;overflow:auto}.vue-html5-editor>.toolbar>.dashboard input[type=text],.vue-html5-editor>.toolbar>.dashboard input[type=number],.vue-html5-editor>.toolbar>.dashboard select{padding:6px 12px;color:inherit;background-color:transparent;border:1px solid #ddd;border-radius:5px}.vue-html5-editor>.toolbar>.dashboard input[type=text]:hover,.vue-html5-editor>.toolbar>.dashboard input[type=number]:hover,.vue-html5-editor>.toolbar>.dashboard select:hover{border-color:#bebebe}.vue-html5-editor>.toolbar>.dashboard input[type=text][disabled],.vue-html5-editor>.toolbar>.dashboard input[type=text][readonly],.vue-html5-editor>.toolbar>.dashboard input[type=number][disabled],.vue-html5-editor>.toolbar>.dashboard input[type=number][readonly],.vue-html5-editor>.toolbar>.dashboard select[disabled],.vue-html5-editor>.toolbar>.dashboard select[readonly]{background-color:#eee;opacity:1}.vue-html5-editor>.toolbar>.dashboard input[type=text][disabled],.vue-html5-editor>.toolbar>.dashboard input[type=number][disabled],.vue-html5-editor>.toolbar>.dashboard select[disabled]{cursor:not-allowed}.vue-html5-editor>.toolbar>.dashboard button{color:inherit;background-color:inherit;padding:6px 12px;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid #ddd;border-radius:5px;margin-right:4px;margin-bottom:4px}.vue-html5-editor>.toolbar>.dashboard button:hover{border-color:#bebebe}.vue-html5-editor>.toolbar>.dashboard button[disabled]{cursor:not-allowed;opacity:.68}.vue-html5-editor>.toolbar>.dashboard button:last-child{margin-right:0}.vue-html5-editor>.toolbar>.dashboard label{font-weight:bolder}.vue-html5-editor>.content{overflow:auto;padding:10px}.vue-html5-editor>.content:focus{outline:0}",undefined);
1173 |
1174 | var template$9 = "";
1175 |
1176 | /**
1177 | * Created by peak on 2017/2/9.
1178 | */
1179 | var editor = {
1180 | template: template$9,
1181 | props: {
1182 | content: {
1183 | type: String,
1184 | required: true,
1185 | default: ''
1186 | },
1187 | height: {
1188 | type: Number,
1189 | default: 300,
1190 | validator: function validator(val){
1191 | return val >= 100
1192 | }
1193 | },
1194 | zIndex: {
1195 | type: Number,
1196 | default: 1000
1197 | },
1198 | autoHeight: {
1199 | type: Boolean,
1200 | default: true
1201 | },
1202 | showModuleName: {}
1203 | },
1204 | data: function data(){
1205 | return {
1206 | // defaultShowModuleName:false
1207 | // locale: {},
1208 | // modules:{},
1209 | fullScreen: false,
1210 | dashboard: null
1211 | }
1212 | },
1213 | watch: {
1214 | content: function content(val) {
1215 | var content = this.$refs.content.innerHTML;
1216 | if (val !== content) {
1217 | this.$refs.content.innerHTML = val;
1218 | }
1219 | },
1220 | fullScreen: function fullScreen(val){
1221 | var component = this;
1222 | if (val) {
1223 | component.parentEl = component.$el.parentNode;
1224 | component.nextEl = component.$el.nextSibling;
1225 | document.body.appendChild(component.$el);
1226 | return
1227 | }
1228 | if (component.nextEl) {
1229 | component.parentEl.insertBefore(component.$el, component.nextEl);
1230 | return
1231 | }
1232 | component.parentEl.appendChild(component.$el);
1233 | }
1234 | },
1235 | computed: {
1236 | contentStyle: function contentStyle(){
1237 | var style = {};
1238 | if (this.fullScreen) {
1239 | style.height = (window.innerHeight - this.$refs.toolbar.clientHeight - 1) + "px";
1240 | return style
1241 | }
1242 | if (!this.autoHeight) {
1243 | style.height = (this.height) + "px";
1244 | return style
1245 | }
1246 | style['min-height'] = (this.height) + "px";
1247 | return style
1248 | }
1249 | },
1250 | methods: {
1251 | toggleFullScreen: function toggleFullScreen(){
1252 | this.fullScreen = !this.fullScreen;
1253 | },
1254 | enableFullScreen: function enableFullScreen(){
1255 | this.fullScreen = true;
1256 | },
1257 | exitFullScreen: function exitFullScreen(){
1258 | this.fullScreen = false;
1259 | },
1260 | focus: function focus(){
1261 | this.$refs.content.focus();
1262 | },
1263 | toggleDashboard: function toggleDashboard(dashboard){
1264 | this.dashboard = this.dashboard === dashboard ? null : dashboard;
1265 | },
1266 | execCommand: function execCommand(command, arg){
1267 | this.restoreSelection();
1268 | if (this.range) {
1269 | new RangeHandler(this.range).execCommand(command, arg);
1270 | }
1271 | this.toggleDashboard();
1272 | this.$emit('change', this.$refs.content.innerHTML);
1273 | },
1274 | getCurrentRange: function getCurrentRange(){
1275 | return this.range
1276 | },
1277 | saveCurrentRange: function saveCurrentRange(){
1278 | var this$1 = this;
1279 |
1280 | var selection = window.getSelection ? window.getSelection() : document.getSelection();
1281 | if (!selection.rangeCount) {
1282 | return
1283 | }
1284 | var content = this.$refs.content;
1285 | for (var i = 0; i < selection.rangeCount; i++) {
1286 | var range = selection.getRangeAt(0);
1287 | var start = range.startContainer;
1288 | var end = range.endContainer;
1289 | // for IE11 : node.contains(textNode) always return false
1290 | start = start.nodeType === Node.TEXT_NODE ? start.parentNode : start;
1291 | end = end.nodeType === Node.TEXT_NODE ? end.parentNode : end;
1292 | if (content.contains(start) && content.contains(end)) {
1293 | this$1.range = range;
1294 | break
1295 | }
1296 | }
1297 | },
1298 | restoreSelection: function restoreSelection(){
1299 | var selection = window.getSelection ? window.getSelection() : document.getSelection();
1300 | selection.removeAllRanges();
1301 | if (this.range) {
1302 | selection.addRange(this.range);
1303 | } else {
1304 | var content = this.$refs.content;
1305 | var div = document.createElement('div');
1306 | var range = document.createRange();
1307 | content.appendChild(div);
1308 | range.setStart(div, 0);
1309 | range.setEnd(div, 0);
1310 | selection.addRange(range);
1311 | this.range = range;
1312 | }
1313 | },
1314 | activeModule: function activeModule(module){
1315 | if (typeof module.handler === 'function') {
1316 | module.handler(this);
1317 | return
1318 | }
1319 | if (module.hasDashboard) {
1320 | this.toggleDashboard(("dashboard-" + (module.name)));
1321 | }
1322 | }
1323 | },
1324 | created: function created(){
1325 | var this$1 = this;
1326 |
1327 | this.modules.forEach(function (module) {
1328 | if (typeof module.init === 'function') {
1329 | module.init(this$1);
1330 | }
1331 | });
1332 | },
1333 | mounted: function mounted(){
1334 | var this$1 = this;
1335 |
1336 | var content = this.$refs.content;
1337 | content.innerHTML = this.content;
1338 | content.addEventListener('mouseup', this.saveCurrentRange, false);
1339 | content.addEventListener('keyup', function () {
1340 | this$1.$emit('change', content.innerHTML);
1341 | this$1.saveCurrentRange();
1342 | }, false);
1343 | content.addEventListener('mouseout', function (e) {
1344 | if (e.target === content) {
1345 | this$1.saveCurrentRange();
1346 | }
1347 | }, false);
1348 | this.touchHandler = function (e) {
1349 | if (content.contains(e.target)) {
1350 | this$1.saveCurrentRange();
1351 | }
1352 | };
1353 |
1354 | window.addEventListener('touchend', this.touchHandler, false);
1355 | },
1356 | updated: function updated(){
1357 | // update dashboard style
1358 | if (this.$refs.dashboard){
1359 | this.$refs.dashboard.style.maxHeight = (this.$refs.content.clientHeight) + "px";
1360 | }
1361 | },
1362 | beforeDestroy: function beforeDestroy(){
1363 | var this$1 = this;
1364 |
1365 | window.removeEventListener('touchend', this.touchHandler);
1366 | this.modules.forEach(function (module) {
1367 | if (typeof module.destroyed === 'function') {
1368 | module.destroyed(this$1);
1369 | }
1370 | });
1371 | }
1372 | };
1373 |
1374 | var i18nZhCn = {
1375 | align: '对齐方式',
1376 | image: '图片',
1377 | list: '列表',
1378 | link: '链接',
1379 | unlink: '去除链接',
1380 | table: '表格',
1381 | font: '文字',
1382 | 'full screen': '全屏',
1383 | text: '排版',
1384 | eraser: '格式清除',
1385 | info: '关于',
1386 | color: '颜色',
1387 | 'please enter a url': '请输入地址',
1388 | 'create link': '创建链接',
1389 | bold: '加粗',
1390 | italic: '倾斜',
1391 | underline: '下划线',
1392 | 'strike through': '删除线',
1393 | subscript: '上标',
1394 | superscript: '下标',
1395 | heading: '标题',
1396 | 'font name': '字体',
1397 | 'font size': '文字大小',
1398 | 'left justify': '左对齐',
1399 | 'center justify': '居中',
1400 | 'right justify': '右对齐',
1401 | 'ordered list': '有序列表',
1402 | 'unordered list': '无序列表',
1403 | 'fore color': '前景色',
1404 | 'background color': '背景色',
1405 | 'row count': '行数',
1406 | 'column count': '列数',
1407 | save: '确定',
1408 | upload: '上传',
1409 | progress: '进度',
1410 | unknown: '未知',
1411 | 'please wait': '请稍等',
1412 | error: '错误',
1413 | abort: '中断',
1414 | reset: '重置',
1415 | hr: '分隔线',
1416 | undo: '撤消',
1417 | 'line height': '行高',
1418 | 'exceed size limit': '超出大小限制'
1419 | };
1420 |
1421 | var i18nEnUs = {
1422 | align: 'align',
1423 | image: 'image',
1424 | list: 'list',
1425 | link: 'link',
1426 | unlink: 'unlink',
1427 | table: 'table',
1428 | font: 'font',
1429 | 'full screen': 'full screen',
1430 | text: 'text',
1431 | eraser: 'remove format',
1432 | info: 'info',
1433 | color: 'color',
1434 | 'please enter a url': 'please enter a url',
1435 | 'create link': 'create link',
1436 | bold: 'bold',
1437 | italic: 'italic',
1438 | underline: 'underline',
1439 | 'strike through': 'strike through',
1440 | subscript: 'subscript',
1441 | superscript: 'superscript',
1442 | heading: 'heading',
1443 | 'font name': 'font name',
1444 | 'font size': 'font size',
1445 | 'left justify': 'left justify',
1446 | 'center justify': 'center justify',
1447 | 'right justify': 'right justify',
1448 | 'ordered list': 'ordered list',
1449 | 'unordered list': 'unordered list',
1450 | 'fore color': 'fore color',
1451 | 'background color': 'background color',
1452 | 'row count': 'row count',
1453 | 'column count': 'column count',
1454 | save: 'save',
1455 | upload: 'upload',
1456 | progress: 'progress',
1457 | unknown: 'unknown',
1458 | 'please wait': 'please wait',
1459 | error: 'error',
1460 | abort: 'abort',
1461 | reset: 'reset',
1462 | hr: 'horizontal rule',
1463 | undo: 'undo',
1464 | 'line height': 'line height',
1465 | 'exceed size limit': 'exceed size limit'
1466 | };
1467 |
1468 | /**
1469 | * Created by peak on 2017/2/24.
1470 | */
1471 | /**
1472 | * shadow clone
1473 | *
1474 | * @param source source object
1475 | * @param ext extended object
1476 | */
1477 | function mixin(source, ext) {
1478 | if ( source === void 0 ) source = {};
1479 | if ( ext === void 0 ) ext = {};
1480 |
1481 | Object.keys(ext).forEach(function (k) {
1482 | // for data function
1483 | if (k === 'data') {
1484 | var dataSrc = source[k];
1485 | var dataDesc = ext[k];
1486 | if (typeof dataDesc === 'function') {
1487 | if (typeof dataSrc !== 'function') {
1488 | source[k] = dataDesc;
1489 | } else {
1490 | source[k] = function () { return mixin(dataSrc(), dataDesc()); };
1491 | }
1492 | }
1493 | } else {
1494 | source[k] = ext[k];
1495 | }
1496 | });
1497 | return source
1498 | }
1499 |
1500 | polyfill();
1501 | /**
1502 | * Vue html5 Editor
1503 | * @param Vue {Vue}
1504 | * @param options {Object}
1505 | */
1506 | var VueHtml5Editor = function VueHtml5Editor(options) {
1507 | if ( options === void 0 ) options = {};
1508 |
1509 | var modules = [].concat( buildInModules );
1510 | var components = {};
1511 |
1512 | // extended modules
1513 | if (Array.isArray(options.modules)) {
1514 | options.modules.forEach(function (module) {
1515 | if (module.name) {
1516 | modules.push(module);
1517 | }
1518 | });
1519 | }
1520 | // hidden modules
1521 | if (Array.isArray(options.hiddenModules)) {
1522 | modules = (function () {
1523 | var arr = [];
1524 | modules.forEach(function (m) {
1525 | if (!options.hiddenModules.includes(m.name)) {
1526 | arr.push(m);
1527 | }
1528 | });
1529 | return arr
1530 | })();
1531 | }
1532 | // visible modules
1533 | if (Array.isArray(options.visibleModules)) {
1534 | modules = (function () {
1535 | var arr = [];
1536 | modules.forEach(function (module) {
1537 | if (options.visibleModules.includes(module.name)) {
1538 | arr.push(module);
1539 | }
1540 | });
1541 | return arr
1542 | })();
1543 | }
1544 |
1545 |
1546 | modules.forEach(function (module) {
1547 | // specify the config for each module in options by name
1548 | var config = options[module.name];
1549 | module.config = mixin(module.config, config);
1550 |
1551 | if (module.dashboard) {
1552 | // $options.module
1553 | module.dashboard.module = module;
1554 | components[("dashboard-" + (module.name))] = module.dashboard;
1555 | }
1556 | if (options.icons && options.icons[module.name]) {
1557 | module.icon = options.icons[module.name];
1558 | }
1559 |
1560 | module.hasDashboard = !!module.dashboard;
1561 | // prevent vue sync
1562 | module.dashboard = null;
1563 | });
1564 |
1565 | // i18n
1566 | var i18n = {'zh-cn': i18nZhCn, 'en-us': i18nEnUs};
1567 | var customI18n = options.i18n || {};
1568 | Object.keys(customI18n).forEach(function (key) {
1569 | i18n[key] = i18n[key] ? mixin(i18n[key], customI18n[key]) : customI18n[key];
1570 | });
1571 | var language = options.language || 'en-us';
1572 | var locale = i18n[language];
1573 |
1574 | // showModuleName
1575 | var defaultShowModuleName = !!options.showModuleName;
1576 |
1577 | // ######################################
1578 | var compo = mixin(editor, {
1579 | data: function data() {
1580 | return {modules: modules, locale: locale, defaultShowModuleName: defaultShowModuleName}
1581 | },
1582 | components: components
1583 | });
1584 | mixin(this, compo);
1585 | };
1586 |
1587 | /**
1588 | * global install
1589 | *
1590 | * @param Vue
1591 | * @param options
1592 | */
1593 | VueHtml5Editor.install = function install (Vue, options) {
1594 | if ( options === void 0 ) options = {};
1595 |
1596 | Vue.component(options.name || 'vue-html5-editor', new VueHtml5Editor(options));
1597 | };
1598 |
1599 | return VueHtml5Editor;
1600 |
1601 | })));
1602 |
--------------------------------------------------------------------------------
/example/basic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 | focus
22 | full screen
23 | toggle module name
24 | change content
25 | content length : {{content.length}}
26 |
27 |
28 |
30 |
31 |
32 |
70 |
71 |
--------------------------------------------------------------------------------
/example/custom-color.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
10 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
42 |
43 |
--------------------------------------------------------------------------------
/example/custom-icon.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
79 |
80 |
81 |
82 |
83 |
84 |
112 |
113 |
--------------------------------------------------------------------------------
/example/custom-icons/align.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/align.png
--------------------------------------------------------------------------------
/example/custom-icons/color.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/color.gif
--------------------------------------------------------------------------------
/example/custom-icons/eraser.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/eraser.gif
--------------------------------------------------------------------------------
/example/custom-icons/font.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/font.png
--------------------------------------------------------------------------------
/example/custom-icons/full-screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/full-screen.gif
--------------------------------------------------------------------------------
/example/custom-icons/hr.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/hr.gif
--------------------------------------------------------------------------------
/example/custom-icons/image.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/image.gif
--------------------------------------------------------------------------------
/example/custom-icons/info.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/info.png
--------------------------------------------------------------------------------
/example/custom-icons/link.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/link.gif
--------------------------------------------------------------------------------
/example/custom-icons/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/list.png
--------------------------------------------------------------------------------
/example/custom-icons/table.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/table.gif
--------------------------------------------------------------------------------
/example/custom-icons/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/text.png
--------------------------------------------------------------------------------
/example/custom-icons/undo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/undo.gif
--------------------------------------------------------------------------------
/example/custom-icons/unlink.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeakTai/vue-html5-editor/dc130c9f627f8467a36d3a64662b73f1fd1f9d0a/example/custom-icons/unlink.gif
--------------------------------------------------------------------------------
/example/custom-modules.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
50 |
51 |
--------------------------------------------------------------------------------
/example/extended-module.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
30 |
115 |
116 |
--------------------------------------------------------------------------------
/example/i18n.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | vue html5 editor demo
6 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
80 |
81 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-html5-editor",
3 | "version": "1.1.0",
4 | "description": "A WYSIWYG text editor base on html5 and vue",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/PeakTai/vue-html5-editor"
8 | },
9 | "main": "dist/vue-html5-editor.js",
10 | "scripts": {
11 | "build": "npm run lint && rollup -c rollup.config.js",
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "lint": "eslint --fix -f codeframe src/*"
14 | },
15 | "keywords": [
16 | "vue",
17 | "WYSIWYG",
18 | "richtext",
19 | "html5",
20 | "editor"
21 | ],
22 | "author": "peak",
23 | "license": "Apache-2.0",
24 | "devDependencies": {
25 | "eslint": "^3.9.1",
26 | "eslint-config-airbnb": "^12.0.0",
27 | "eslint-friendly-formatter": "^2.0.6",
28 | "eslint-loader": "^1.6.0",
29 | "eslint-plugin-import": "^1.16.0",
30 | "eslint-plugin-jsx-a11y": "^2.2.3",
31 | "eslint-plugin-react": "^6.9.0",
32 | "lrz": "^4.9.40",
33 | "postcss-clean": "^1.0.2",
34 | "postcss-cssnext": "^2.9.0",
35 | "postcss-import": "^9.1.0",
36 | "rollup": "^0.37.0",
37 | "rollup-plugin-babel": "^2.7.1",
38 | "rollup-plugin-buble": "^0.15.0",
39 | "rollup-plugin-commonjs": "^6.0.1",
40 | "rollup-plugin-css-only": "^0.2.0",
41 | "rollup-plugin-flow-no-whitespace": "^1.0.0",
42 | "rollup-plugin-html": "^0.2.1",
43 | "rollup-plugin-license": "^0.3.0",
44 | "rollup-plugin-node-resolve": "^2.0.0",
45 | "rollup-plugin-postcss": "^0.2.0",
46 | "rollup-plugin-progress": "^0.2.1",
47 | "rollup-plugin-replace": "^1.1.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import progress from 'rollup-plugin-progress'
2 | import postcss from 'rollup-plugin-postcss'
3 | import cssnext from 'postcss-cssnext'
4 | import buble from 'rollup-plugin-buble'
5 | import html from 'rollup-plugin-html'
6 | import autoprefixer from 'autoprefixer'
7 | import clean from 'postcss-clean'
8 | import atImport from 'postcss-import'
9 | import nodeResolve from 'rollup-plugin-node-resolve'
10 | import replace from 'rollup-plugin-replace'
11 | import commonJs from 'rollup-plugin-commonjs'
12 | import license from 'rollup-plugin-license'
13 | var pkg = require('./package.json')
14 |
15 | /**
16 | * Created by peak on 2017/1/6.
17 | */
18 | export default {
19 | entry: 'src/index.js',
20 | dest: 'dist/vue-html5-editor.js',
21 | format: 'umd',
22 | moduleName: "VueHtml5Editor",
23 | plugins: [
24 | license({
25 | banner: `Vue-html5-editor ${pkg.version}\n${pkg.repository.url}\nbuild at ${new Date()}`
26 | }),
27 | progress({
28 | clearLine: false
29 | }),
30 | replace({
31 | VERSION: JSON.stringify(pkg.version)
32 | }),
33 | postcss({
34 | plugins: [
35 | atImport(),
36 | cssnext({
37 | warnForDuplicates: false
38 | }),
39 | autoprefixer(),
40 | clean()
41 | ],
42 | extensions: ['.css', '.pcss']
43 | }),
44 | html({
45 | include: '**/*.html',
46 | htmlMinifierOptions: {
47 | collapseWhitespace: true,
48 | collapseBooleanAttributes: true,
49 | conservativeCollapse: true
50 | }
51 | }),
52 | commonJs({
53 | include: 'node_modules/lrz/**'
54 | }),
55 | nodeResolve({jsnext: true}),
56 | buble({
57 | include: '**/*.js'
58 | })
59 | ]
60 | }
--------------------------------------------------------------------------------
/src/editor.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/editor.js:
--------------------------------------------------------------------------------
1 | import RangeHandler from './range/handler'
2 | import './style.css'
3 | import template from './editor.html'
4 | /**
5 | * Created by peak on 2017/2/9.
6 | */
7 | export default {
8 | template,
9 | props: {
10 | content: {
11 | type: String,
12 | required: true,
13 | default: ''
14 | },
15 | height: {
16 | type: Number,
17 | default: 300,
18 | validator(val){
19 | return val >= 100
20 | }
21 | },
22 | zIndex: {
23 | type: Number,
24 | default: 1000
25 | },
26 | autoHeight: {
27 | type: Boolean,
28 | default: true
29 | },
30 | showModuleName: {}
31 | },
32 | data(){
33 | return {
34 | // defaultShowModuleName:false
35 | // locale: {},
36 | // modules:{},
37 | fullScreen: false,
38 | dashboard: null
39 | }
40 | },
41 | watch: {
42 | content(val) {
43 | const content = this.$refs.content.innerHTML
44 | if (val !== content) {
45 | this.$refs.content.innerHTML = val
46 | }
47 | this.$emit('update:content', val)
48 | },
49 | fullScreen(val){
50 | const component = this
51 | if (val) {
52 | component.parentEl = component.$el.parentNode
53 | component.nextEl = component.$el.nextSibling
54 | document.body.appendChild(component.$el)
55 | return
56 | }
57 | if (component.nextEl) {
58 | component.parentEl.insertBefore(component.$el, component.nextEl)
59 | return
60 | }
61 | component.parentEl.appendChild(component.$el)
62 | }
63 | },
64 | computed: {
65 | contentStyle(){
66 | const style = {}
67 | if (this.fullScreen) {
68 | style.height = `${window.innerHeight - this.$refs.toolbar.clientHeight - 1}px`
69 | return style
70 | }
71 | if (!this.autoHeight) {
72 | style.height = `${this.height}px`
73 | return style
74 | }
75 | style['min-height'] = `${this.height}px`
76 | return style
77 | }
78 | },
79 | methods: {
80 | toggleFullScreen(){
81 | this.fullScreen = !this.fullScreen
82 | },
83 | enableFullScreen(){
84 | this.fullScreen = true
85 | },
86 | exitFullScreen(){
87 | this.fullScreen = false
88 | },
89 | focus(){
90 | this.$refs.content.focus()
91 | },
92 | toggleDashboard(dashboard){
93 | this.dashboard = this.dashboard === dashboard ? null : dashboard
94 | },
95 | execCommand(command, arg){
96 | this.restoreSelection()
97 | if (this.range) {
98 | new RangeHandler(this.range).execCommand(command, arg)
99 | }
100 | this.toggleDashboard()
101 | this.$emit('change', this.$refs.content.innerHTML)
102 | },
103 | getCurrentRange(){
104 | return this.range
105 | },
106 | saveCurrentRange(){
107 | const selection = window.getSelection ? window.getSelection() : document.getSelection()
108 | if (!selection.rangeCount) {
109 | return
110 | }
111 | const content = this.$refs.content
112 | for (let i = 0; i < selection.rangeCount; i++) {
113 | const range = selection.getRangeAt(0)
114 | let start = range.startContainer
115 | let end = range.endContainer
116 | // for IE11 : node.contains(textNode) always return false
117 | start = start.nodeType === Node.TEXT_NODE ? start.parentNode : start
118 | end = end.nodeType === Node.TEXT_NODE ? end.parentNode : end
119 | if (content.contains(start) && content.contains(end)) {
120 | this.range = range
121 | break
122 | }
123 | }
124 | },
125 | restoreSelection(){
126 | const selection = window.getSelection ? window.getSelection() : document.getSelection()
127 | selection.removeAllRanges()
128 | if (this.range) {
129 | selection.addRange(this.range)
130 | } else {
131 | const content = this.$refs.content
132 | const div = document.createElement('div')
133 | const range = document.createRange()
134 | content.appendChild(div)
135 | range.setStart(div, 0)
136 | range.setEnd(div, 0)
137 | selection.addRange(range)
138 | this.range = range
139 | }
140 | },
141 | activeModule(module){
142 | if (typeof module.handler === 'function') {
143 | module.handler(this)
144 | return
145 | }
146 | if (module.hasDashboard) {
147 | this.toggleDashboard(`dashboard-${module.name}`)
148 | }
149 | }
150 | },
151 | created(){
152 | this.modules.forEach((module) => {
153 | if (typeof module.init === 'function') {
154 | module.init(this)
155 | }
156 | })
157 | },
158 | mounted(){
159 | const content = this.$refs.content
160 | content.innerHTML = this.content
161 | content.addEventListener('mouseup', this.saveCurrentRange, false)
162 | content.addEventListener('keyup', () => {
163 | this.$emit('change', content.innerHTML)
164 | this.saveCurrentRange()
165 | }, false)
166 | content.addEventListener('mouseout', (e) => {
167 | if (e.target === content) {
168 | this.saveCurrentRange()
169 | }
170 | }, false)
171 | this.touchHandler = (e) => {
172 | if (content.contains(e.target)) {
173 | this.saveCurrentRange()
174 | }
175 | }
176 |
177 | window.addEventListener('touchend', this.touchHandler, false)
178 | },
179 | updated(){
180 | // update dashboard style
181 | if (this.$refs.dashboard){
182 | this.$refs.dashboard.style.maxHeight = `${this.$refs.content.clientHeight}px`
183 | }
184 | },
185 | beforeDestroy(){
186 | window.removeEventListener('touchend', this.touchHandler)
187 | this.modules.forEach((module) => {
188 | if (typeof module.destroyed === 'function') {
189 | module.destroyed(this)
190 | }
191 | })
192 | }
193 | }
--------------------------------------------------------------------------------
/src/i18n/en-us.js:
--------------------------------------------------------------------------------
1 | export default {
2 | align: 'align',
3 | image: 'image',
4 | list: 'list',
5 | link: 'link',
6 | unlink: 'unlink',
7 | table: 'table',
8 | font: 'font',
9 | 'full screen': 'full screen',
10 | text: 'text',
11 | eraser: 'remove format',
12 | info: 'info',
13 | color: 'color',
14 | 'please enter a url': 'please enter a url',
15 | 'create link': 'create link',
16 | bold: 'bold',
17 | italic: 'italic',
18 | underline: 'underline',
19 | 'strike through': 'strike through',
20 | subscript: 'subscript',
21 | superscript: 'superscript',
22 | heading: 'heading',
23 | 'font name': 'font name',
24 | 'font size': 'font size',
25 | 'left justify': 'left justify',
26 | 'center justify': 'center justify',
27 | 'right justify': 'right justify',
28 | 'ordered list': 'ordered list',
29 | 'unordered list': 'unordered list',
30 | 'fore color': 'fore color',
31 | 'background color': 'background color',
32 | 'row count': 'row count',
33 | 'column count': 'column count',
34 | save: 'save',
35 | upload: 'upload',
36 | progress: 'progress',
37 | unknown: 'unknown',
38 | 'please wait': 'please wait',
39 | error: 'error',
40 | abort: 'abort',
41 | reset: 'reset',
42 | hr: 'horizontal rule',
43 | undo: 'undo',
44 | 'line height': 'line height',
45 | 'exceed size limit': 'exceed size limit'
46 | }
--------------------------------------------------------------------------------
/src/i18n/zh-cn.js:
--------------------------------------------------------------------------------
1 | export default {
2 | align: '对齐方式',
3 | image: '图片',
4 | list: '列表',
5 | link: '链接',
6 | unlink: '去除链接',
7 | table: '表格',
8 | font: '文字',
9 | 'full screen': '全屏',
10 | text: '排版',
11 | eraser: '格式清除',
12 | info: '关于',
13 | color: '颜色',
14 | 'please enter a url': '请输入地址',
15 | 'create link': '创建链接',
16 | bold: '加粗',
17 | italic: '倾斜',
18 | underline: '下划线',
19 | 'strike through': '删除线',
20 | subscript: '上标',
21 | superscript: '下标',
22 | heading: '标题',
23 | 'font name': '字体',
24 | 'font size': '文字大小',
25 | 'left justify': '左对齐',
26 | 'center justify': '居中',
27 | 'right justify': '右对齐',
28 | 'ordered list': '有序列表',
29 | 'unordered list': '无序列表',
30 | 'fore color': '前景色',
31 | 'background color': '背景色',
32 | 'row count': '行数',
33 | 'column count': '列数',
34 | save: '确定',
35 | upload: '上传',
36 | progress: '进度',
37 | unknown: '未知',
38 | 'please wait': '请稍等',
39 | error: '错误',
40 | abort: '中断',
41 | reset: '重置',
42 | hr: '分隔线',
43 | undo: '撤消',
44 | 'line height': '行高',
45 | 'exceed size limit': '超出大小限制'
46 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import polyfill from './polyfill-ie'
2 | import buildInModules from './modules/index'
3 | import editor from './editor'
4 | import i18nZhCn from './i18n/zh-cn'
5 | import i18nEnUs from './i18n/en-us'
6 | import mixin from './util/mixin'
7 |
8 | polyfill()
9 | /**
10 | * Vue html5 Editor
11 | * @param Vue {Vue}
12 | * @param options {Object}
13 | */
14 | class VueHtml5Editor {
15 |
16 | /**
17 | * build an editor component
18 | */
19 | constructor(options = {}) {
20 | let modules = [...buildInModules]
21 | const components = {}
22 |
23 | // extended modules
24 | if (Array.isArray(options.modules)) {
25 | options.modules.forEach((module) => {
26 | if (module.name) {
27 | modules.push(module)
28 | }
29 | })
30 | }
31 | // hidden modules
32 | if (Array.isArray(options.hiddenModules)) {
33 | modules = (() => {
34 | const arr = []
35 | modules.forEach((m) => {
36 | if (!options.hiddenModules.includes(m.name)) {
37 | arr.push(m)
38 | }
39 | })
40 | return arr
41 | })()
42 | }
43 | // visible modules
44 | if (Array.isArray(options.visibleModules)) {
45 | modules = (() => {
46 | const arr = []
47 | modules.forEach((module) => {
48 | if (options.visibleModules.includes(module.name)) {
49 | arr.push(module)
50 | }
51 | })
52 | return arr
53 | })()
54 | }
55 |
56 |
57 | modules.forEach((module) => {
58 | // specify the config for each module in options by name
59 | const config = options[module.name]
60 | module.config = mixin(module.config, config)
61 |
62 | if (module.dashboard) {
63 | // $options.module
64 | module.dashboard.module = module
65 | components[`dashboard-${module.name}`] = module.dashboard
66 | }
67 | if (options.icons && options.icons[module.name]) {
68 | module.icon = options.icons[module.name]
69 | }
70 |
71 | module.hasDashboard = !!module.dashboard
72 | // prevent vue sync
73 | module.dashboard = null
74 | })
75 |
76 | // i18n
77 | const i18n = {'zh-cn': i18nZhCn, 'en-us': i18nEnUs}
78 | const customI18n = options.i18n || {}
79 | Object.keys(customI18n).forEach((key) => {
80 | i18n[key] = i18n[key] ? mixin(i18n[key], customI18n[key]) : customI18n[key]
81 | })
82 | const language = options.language || 'en-us'
83 | const locale = i18n[language]
84 |
85 | // showModuleName
86 | const defaultShowModuleName = !!options.showModuleName
87 |
88 | // ######################################
89 | const compo = mixin(editor, {
90 | data() {
91 | return {modules, locale, defaultShowModuleName}
92 | },
93 | components
94 | })
95 | mixin(this, compo)
96 | }
97 |
98 | /**
99 | * global install
100 | *
101 | * @param Vue
102 | * @param options
103 | */
104 | static install(Vue, options = {}) {
105 | Vue.component(options.name || 'vue-html5-editor', new VueHtml5Editor(options))
106 | }
107 | }
108 |
109 | export default VueHtml5Editor
--------------------------------------------------------------------------------
/src/modules/align/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{$parent.locale["left justify"]}}
4 |
5 |
6 | {{$parent.locale["center justify"]}}
7 |
8 |
9 | {{$parent.locale["right justify"]}}
10 |
11 |
--------------------------------------------------------------------------------
/src/modules/align/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 | /**
3 | * Created by peak on 2017/2/10.
4 | */
5 | export default {
6 | template
7 | }
--------------------------------------------------------------------------------
/src/modules/align/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * text align
3 | * Created by peak on 16/8/18.
4 | */
5 | import dashboard from './dashboard'
6 |
7 | export default {
8 | name: 'align',
9 | icon: 'fa fa-align-center',
10 | i18n: 'align',
11 | dashboard
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/modules/color/dashboard.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/modules/color/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 | import './style.css'
3 | /**
4 | * Created by peak on 2017/2/10.
5 | */
6 | export default {
7 | template,
8 | data(){
9 | return {
10 | // foreColor,backColor
11 | command: 'foreColor',
12 | colors: [
13 | '#000000', '#000033', '#000066', '#000099', '#003300', '#003333', '#003366',
14 | '#003399', '#006600', '#006633', '#009900', '#330000', '#330033', '#330066',
15 | '#333300', '#333366', '#660000', '#660033', '#663300', '#666600', '#666633',
16 | '#666666', '#666699', '#990000', '#990033', '#9900CC', '#996600', '#FFCC00',
17 | '#FFCCCC', '#FFCC99', '#FFFF00', '#FF9900', '#CCFFCC', '#CCFFFF', '#CCFF99'
18 | ]
19 | }
20 | },
21 | methods: {
22 | changeColor(color){
23 | this.$parent.execCommand(this.command, color)
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/modules/color/index.js:
--------------------------------------------------------------------------------
1 | import dashboard from './dashboard'
2 | /**
3 | * fore color and back color
4 | * Created by peak on 16/8/18.
5 | */
6 | export default {
7 | name: 'color',
8 | icon: 'fa fa-paint-brush',
9 | i18n: 'color',
10 | dashboard
11 | }
12 |
--------------------------------------------------------------------------------
/src/modules/color/style.css:
--------------------------------------------------------------------------------
1 | .vue-html5-editor .color-card {
2 | margin: 2px;
3 | width: 30px;
4 | height: 30px;
5 | float: left;
6 | cursor: pointer;
7 | }
--------------------------------------------------------------------------------
/src/modules/eraser/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * remove format of selection
3 | * Created by peak on 16/8/18.
4 | */
5 | export default {
6 | name: 'eraser',
7 | icon: 'fa fa-eraser',
8 | i18n: 'eraser',
9 | handler(editor) {
10 | editor.execCommand('removeFormat')
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/font/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{$parent.locale["heading"]}}:
4 | H{{h}}
5 |
6 |
7 |
8 | {{$parent.locale["font name"]}}:
9 |
10 | {{name}}
11 |
12 |
13 |
14 | {{$parent.locale["font size"]}}:
15 |
16 | {{size}}
17 |
18 |
19 |
20 | {{$parent.locale["line height"]}}:
21 |
22 |
23 | {{lh}}
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/modules/font/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 | import Command from '../../range/command'
3 | /**
4 | * Created by peak on 2017/2/10.
5 | */
6 | export default {
7 | template,
8 | data(){
9 | return {
10 | nameList: [
11 | 'Microsoft YaHei',
12 | 'Helvetica Neue',
13 | 'Helvetica',
14 | 'Arial',
15 | 'sans-serif',
16 | 'Verdana',
17 | 'Georgia',
18 | 'Times New Roman',
19 | 'Trebuchet MS',
20 | 'Microsoft JhengHei',
21 | 'Courier New',
22 | 'Impact',
23 | 'Comic Sans MS',
24 | 'Consolas'
25 | ],
26 | lineHeightList: [
27 | '1.0', '1.2', '1.5', '1.8', '2.0', '2.5', '3.0'
28 | ],
29 | fontSizeList: [
30 | '12px', '14px', '16px', '18px', '20px', '22px', '24px'
31 | ]
32 | }
33 | },
34 | methods: {
35 | setFontName(name){
36 | this.$parent.execCommand('fontName', name)
37 | },
38 | setFontSize(size){
39 | this.$parent.execCommand('fontSize', size)
40 | },
41 | setHeading(heading){
42 | this.$parent.execCommand('formatBlock', `h${heading}`)
43 | },
44 | setLineHeight(lh){
45 | this.$parent.execCommand(Command.LINE_HEIGHT, lh)
46 | }
47 | },
48 | created(){
49 | const config = this.$options.module.config
50 | // font name
51 | if (!config) {
52 | return
53 | }
54 | if (Array.isArray(config.fontNames)) {
55 | this.nameList = config.fontNames
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/modules/font/index.js:
--------------------------------------------------------------------------------
1 | import dashboard from './dashboard'
2 | /**
3 | * font name and font size
4 | * Created by peak on 16/8/18.
5 | */
6 | export default {
7 | name: 'font',
8 | icon: 'fa fa-font',
9 | i18n: 'font',
10 | dashboard
11 | }
--------------------------------------------------------------------------------
/src/modules/full-screen/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * toggle full screen mode
3 | * Created by peak on 16/8/18.
4 | */
5 | export default {
6 | name: 'full-screen',
7 | icon: 'fa fa-arrows-alt',
8 | i18n: 'full screen',
9 | handler(editor) {
10 | editor.toggleFullScreen()
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/hr/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * hr
3 | * Created by peak on 16/8/20.
4 | */
5 | export default {
6 | name: 'hr',
7 | icon: 'fa fa-minus',
8 | i18n: 'hr',
9 | handler(editor) {
10 | editor.execCommand('insertHorizontalRule')
11 | }
12 | // init (editor) {
13 | //
14 | // },
15 | // destroyed(editor){
16 | //
17 | // },
18 | }
19 |
--------------------------------------------------------------------------------
/src/modules/image/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{$parent.locale.save}}
7 |
8 |
10 | {{$parent.locale.upload}}
11 |
12 |
13 |
14 | {{$parent.locale.progress}}:{{upload.progressComputable ? $parent.locale.unknown : upload.complete}}
15 |
16 |
17 |
18 | {{$parent.locale["please wait"]}}...
19 |
20 |
21 |
22 | {{$parent.locale.error}}:{{upload.errorMsg}}
23 | {{$parent.locale.reset}}
24 |
25 |
26 |
27 | {{$parent.locale.upload}} {{$parent.locale.abort}},
28 | {{$parent.locale.reset}}
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/modules/image/dashboard.js:
--------------------------------------------------------------------------------
1 | import lrz from 'lrz'
2 | import template from './dashboard.html'
3 | import Command from '../../range/command'
4 |
5 | /**
6 | * Created by peak on 2017/2/10.
7 | */
8 | export default {
9 | template,
10 | data() {
11 | return {
12 | imageUrl: '',
13 | upload: {
14 | status: 'ready', // progress,success,error,abort
15 | errorMsg: null,
16 | progressComputable: false,
17 | complete: 0
18 | }
19 | }
20 | },
21 | methods: {
22 | reset(){
23 | this.upload.status = 'ready'
24 | },
25 | insertImageUrl() {
26 | if (!this.imageUrl) {
27 | return
28 | }
29 | this.$parent.execCommand(Command.INSERT_IMAGE, this.imageUrl)
30 | this.imageUrl = null
31 | },
32 | pick() {
33 | this.$refs.file.click()
34 | },
35 | setUploadError(msg){
36 | this.upload.status = 'error'
37 | this.upload.errorMsg = msg
38 | },
39 | process() {
40 | const component = this
41 | const config = this.$options.module.config
42 | // compatibility with older format
43 | // {
44 | // server: null,
45 | // fieldName: 'image',
46 | // compress: true,
47 | // width: 1600,
48 | // height: 1600,
49 | // quality: 80
50 | // }
51 | // ----------- divider ----------------
52 | // {
53 | // upload: {
54 | // url: null,
55 | // headers: {},
56 | // params: {},
57 | // fieldName: {}
58 | // },
59 | // compress: {
60 | // width: 1600,
61 | // height: 1600,
62 | // quality: 80
63 | // },
64 | // }
65 |
66 | if (!config.upload && typeof config.server === 'string') {
67 | config.upload = {url: config.server}
68 | }
69 | if (config.upload && !config.upload.url) {
70 | config.upload = null
71 | }
72 | if (config.upload && typeof config.fieldName === 'string') {
73 | config.upload.fieldName = config.fieldName
74 | }
75 |
76 | if (typeof config.compress === 'boolean') {
77 | config.compress = {
78 | width: config.width,
79 | height: config.height,
80 | quality: config.quality
81 | }
82 | }
83 |
84 | const file = this.$refs.file.files[0]
85 | if (file.size > config.sizeLimit) {
86 | this.setUploadError(this.$parent.locale['exceed size limit'])
87 | return
88 | }
89 | this.$refs.file.value = null
90 |
91 | if (config.compress) {
92 | config.compress.fieldName = config.upload && config.upload.fieldName
93 | ? config.upload.fieldName : 'image'
94 | lrz(file, config.compress).then((rst) => {
95 | if (config.upload) {
96 | component.uploadToServer(rst.file)
97 | } else {
98 | component.insertBase64(rst.base64)
99 | }
100 | }).catch((err) => {
101 | this.setUploadError(err.toString())
102 | })
103 | return
104 | }
105 | // 不需要压缩
106 | // base64
107 | if (!config.upload) {
108 | const reader = new FileReader()
109 | reader.onload = (e) => {
110 | component.insertBase64(e.target.result)
111 | }
112 | reader.readAsDataURL(file)
113 | return
114 | }
115 | // 上传服务器
116 | component.uploadToServer(file)
117 | },
118 | insertBase64(data) {
119 | this.$parent.execCommand(Command.INSERT_IMAGE, data)
120 | },
121 | uploadToServer(file) {
122 | const config = this.$options.module.config
123 |
124 | const formData = new FormData()
125 | formData.append(config.upload.fieldName || 'image', file)
126 |
127 | if (typeof config.upload.params === 'object') {
128 | Object.keys(config.upload.params).forEach((key) => {
129 | const value = config.upload.params[key]
130 | if (Array.isArray(value)) {
131 | value.forEach((v) => {
132 | formData.append(key, v)
133 | })
134 | } else {
135 | formData.append(key, value)
136 | }
137 | })
138 | }
139 |
140 | const xhr = new XMLHttpRequest()
141 |
142 | xhr.onprogress = (e) => {
143 | this.upload.status = 'progress'
144 | if (e.lengthComputable) {
145 | this.upload.progressComputable = true
146 | const percentComplete = e.loaded / e.total
147 | this.upload.complete = (percentComplete * 100).toFixed(2)
148 | } else {
149 | this.upload.progressComputable = false
150 | }
151 | }
152 |
153 | xhr.onload = () => {
154 | if (xhr.status >= 300) {
155 | this.setUploadError(`request error,code ${xhr.status}`)
156 | return
157 | }
158 |
159 | try {
160 | const url = config.uploadHandler(xhr.responseText)
161 | if (url) {
162 | this.$parent.execCommand(Command.INSERT_IMAGE, url)
163 | }
164 | } catch (err) {
165 | this.setUploadError(err.toString())
166 | } finally {
167 | this.upload.status = 'ready'
168 | }
169 | }
170 |
171 | xhr.onerror = () => {
172 | // find network info in brower tools
173 | this.setUploadError('request error')
174 | }
175 |
176 | xhr.onabort = () => {
177 | this.upload.status = 'abort'
178 | }
179 |
180 | xhr.open('POST', config.upload.url)
181 | if (typeof config.upload.headers === 'object') {
182 | Object.keys(config.upload.headers).forEach((k) => {
183 | xhr.setRequestHeader(k, config.upload.headers[k])
184 | })
185 | }
186 | xhr.send(formData)
187 | }
188 | }
189 | }
190 |
--------------------------------------------------------------------------------
/src/modules/image/index.js:
--------------------------------------------------------------------------------
1 | import dashboard from './dashboard'
2 |
3 | /**
4 | * insert image
5 | * Created by peak on 16/8/18.
6 | */
7 | export default {
8 | name: 'image',
9 | icon: 'fa fa-file-image-o',
10 | i18n: 'image',
11 | config: {
12 | // server: null,
13 | // fieldName: 'image',
14 | // compress: true,
15 | // width: 1600,
16 | // height: 1600,
17 | // quality: 80,
18 | sizeLimit: 512 * 1024,// 512k
19 | // upload: {
20 | // url: null,
21 | // headers: {},
22 | // params: {},
23 | // fieldName: {}
24 | // },
25 | compress: {
26 | width: 1600,
27 | height: 1600,
28 | quality: 80
29 | },
30 | uploadHandler(responseText){
31 | const json = JSON.parse(responseText)
32 | return json.ok ? json.data : null
33 | }
34 | },
35 | dashboard
36 | }
37 |
--------------------------------------------------------------------------------
/src/modules/index.js:
--------------------------------------------------------------------------------
1 | import align from './align/index'
2 | import color from './color/index'
3 | import eraser from './eraser/index'
4 | import font from './font/index'
5 | import fullScreen from './full-screen/index'
6 | import hr from './hr/index'
7 | import image from './image/index'
8 | import info from './info/index'
9 | import link from './link/index'
10 | import list from './list/index'
11 | import table from './table/index'
12 | import text from './text/index'
13 | import undo from './undo/index'
14 | import unlink from './unlink/index'
15 |
16 | /**
17 | * build-in moduls
18 | * Created by peak on 2016/11/1.
19 | */
20 | export default [
21 | text,
22 | color,
23 | font,
24 | align,
25 | list,
26 | link,
27 | unlink,
28 | table,
29 | image,
30 | hr,
31 | eraser,
32 | undo,
33 | fullScreen,
34 | info
35 | ]
36 |
--------------------------------------------------------------------------------
/src/modules/info/dashboard.html:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/modules/info/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 | /**
3 | * Created by peak on 2017/2/10.
4 | */
5 | export default {
6 | template,
7 | data(){
8 | return {
9 | version: VERSION
10 | }
11 | }
12 | }
--------------------------------------------------------------------------------
/src/modules/info/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * editor info
3 | * Created by peak on 16/8/18.
4 | */
5 | import dashboard from './dashboard'
6 |
7 | export default {
8 | name: 'info',
9 | icon: 'fa fa-info',
10 | i18n: 'info',
11 | // handler () {
12 | //
13 | // },
14 | // init (editor) {
15 | //
16 | // },
17 | // destroyed(editor){
18 | //
19 | // },
20 | dashboard
21 | }
22 |
--------------------------------------------------------------------------------
/src/modules/link/dashboard.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/modules/link/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 |
3 | export default {
4 | template,
5 | data(){
6 | return {url: null}
7 | },
8 | methods: {
9 | createLink(){
10 | if (!this.url) {
11 | return
12 | }
13 | this.$parent.execCommand('createLink', this.url)
14 | this.url = null
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/modules/link/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * create link
3 | * Created by peak on 16/8/18.
4 | */
5 | import dashboard from './dashboard'
6 |
7 | export default {
8 | name: 'link',
9 | icon: 'fa fa-chain',
10 | i18n: 'link',
11 | dashboard
12 | }
13 |
--------------------------------------------------------------------------------
/src/modules/list/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{$parent.locale["ordered list"]}}
4 |
5 |
6 | {{$parent.locale["unordered list"]}}
7 |
8 |
--------------------------------------------------------------------------------
/src/modules/list/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 | /**
3 | * Created by peak on 2017/2/10.
4 | */
5 | export default {
6 | template
7 | }
--------------------------------------------------------------------------------
/src/modules/list/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * list,ul,ol
3 | * Created by peak on 16/8/18.
4 | */
5 | import dashboard from './dashboard'
6 |
7 | export default {
8 | name: 'list',
9 | icon: 'fa fa-list',
10 | i18n: 'list',
11 | dashboard
12 | }
--------------------------------------------------------------------------------
/src/modules/table/dashboard.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/modules/table/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 |
3 | /**
4 | * Created by peak on 2017/2/10.
5 | */
6 | export default {
7 | template,
8 | data(){
9 | return {
10 | rows: 2,
11 | cols: 2,
12 | hasHead: false,
13 | striped: false,
14 | hover: false
15 | }
16 | },
17 | methods: {
18 | insertTable(){
19 | if (this.rows < 2 || this.rows > 10) {
20 | return
21 | }
22 | if (this.cols < 2 || this.cols > 10) {
23 | return
24 | }
25 | let table = ''
26 | for (let i = 0; i < this.rows; i++) {
27 | table += ''
28 | for (let j = 0; j < this.cols; j++) {
29 | table += ' '
30 | }
31 | table += ' '
32 | }
33 | table += '
'
34 | this.$parent.execCommand('insertHTML', table)
35 | }
36 | }
37 | }
--------------------------------------------------------------------------------
/src/modules/table/index.js:
--------------------------------------------------------------------------------
1 | import dashboard from './dashboard'
2 | /**
3 | * insert table
4 | * Created by peak on 16/8/18.
5 | */
6 | export default {
7 | // can not named table
8 | // dashboard.html will add to editor as a child component and named as module name
9 | // Do not use built-in or reserved HTML elements as component id
10 | name: 'tabulation',
11 | icon: 'fa fa-table',
12 | i18n: 'table',
13 | dashboard
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/src/modules/text/dashboard.html:
--------------------------------------------------------------------------------
1 |
2 | {{$parent.locale["bold"]}}
3 | {{$parent.locale["italic"]}}
4 | {{$parent.locale["underline"]}}
5 | {{$parent.locale["strike through"]}}
6 |
7 | {{$parent.locale["subscript"]}}
8 | {{$parent.locale["superscript"]}}
9 |
10 |
--------------------------------------------------------------------------------
/src/modules/text/dashboard.js:
--------------------------------------------------------------------------------
1 | import template from './dashboard.html'
2 |
3 | export default {
4 | template
5 | }
--------------------------------------------------------------------------------
/src/modules/text/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * text,set the text bold or italic or underline or with strike through or subscript or superscript
3 | * Created by peak on 16/8/18.
4 | */
5 | import dashboard from './dashboard'
6 |
7 | export default {
8 | name: 'text',
9 | icon: 'fa fa-pencil',
10 | i18n: 'text',
11 | dashboard
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/modules/undo/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * undo
3 | * Created by peak on 16/8/20.
4 | */
5 | export default {
6 | name: 'undo',
7 | icon: 'fa-undo fa',
8 | i18n: 'undo',
9 | handler(editor) {
10 | editor.execCommand('undo')
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/modules/unlink/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * unlink
3 | * Created by peak on 16/8/18.
4 | */
5 | export default {
6 | name: 'unlink',
7 | icon: 'fa fa-chain-broken',
8 | i18n: 'unlink',
9 | handler(editor) {
10 | editor.execCommand('unlink')
11 | }
12 | }
--------------------------------------------------------------------------------
/src/polyfill-ie.js:
--------------------------------------------------------------------------------
1 | export default () => {
2 | // https://tc39.github.io/ecma262/#sec-array.prototype.includes
3 | if (!Array.prototype.includes) {
4 | Object.defineProperty(Array.prototype, 'includes', {
5 | value(searchElement, fromIndex) {
6 | // 1. Let O be ? ToObject(this value).
7 | if (this == null) {
8 | throw new TypeError('"this" is null or not defined')
9 | }
10 |
11 | const o = Object(this)
12 |
13 | // 2. Let len be ? ToLength(? Get(O, "length")).
14 | const len = o.length >>> 0
15 |
16 | // 3. If len is 0, return false.
17 | if (len === 0) {
18 | return false
19 | }
20 |
21 | // 4. Let n be ? ToInteger(fromIndex).
22 | // (If fromIndex is undefined, this step produces the value 0.)
23 | const n = fromIndex | 0
24 |
25 | // 5. If n ≥ 0, then
26 | // a. Let k be n.
27 | // 6. Else n < 0,
28 | // a. Let k be len + n.
29 | // b. If k < 0, let k be 0.
30 | let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0)
31 |
32 | // 7. Repeat, while k < len
33 | while (k < len) {
34 | // a. Let elementK be the result of ? Get(O, ! ToString(k)).
35 | // b. If SameValueZero(searchElement, elementK) is true, return true.
36 | // c. Increase k by 1.
37 | // NOTE: === provides the correct "SameValueZero" comparison needed here.
38 | if (o[k] === searchElement) {
39 | return true
40 | }
41 | k++
42 | }
43 |
44 | // 8. Return false
45 | return false
46 | }
47 | })
48 | }
49 | // text.contains()
50 | if (!Text.prototype.contains) {
51 | Text.prototype.contains = function contains(node) {
52 | return this === node
53 | }
54 | }
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/src/range/README.md:
--------------------------------------------------------------------------------
1 | ### document.execCommand()指令测试结果
2 |
3 | command | IE11 | chrome | firefox
4 | ----: | :---: | :---: | :-----
5 | justifyLeft | Y | Y | Y
6 | justifyCenter | Y | Y | Y
7 | justifyRight | Y | Y | Y
8 | foreColor | Y | Y | Y
9 | backColor | Y | Y | Y
10 | removeFormat | Y | Y | Y
11 | fontName | Y | Y | Y
12 | fontSize | Y | Y | Y
13 | formatBlock | Y | N | N
14 | insertHorizontalRule | Y | Y | Y
15 | insertImage | Y | Y | Y
16 | createLink | Y | Y | Y
17 | insertOrderedList | Y | Y | Y
18 | insertUnorderedList | Y | Y | Y
19 | insertHTML | N | Y | Y
20 | bold | Y | Y | Y
21 | italic | Y | Y | Y
22 | underline | Y | Y | Y
23 | strikeThrough | Y | Y | Y
24 | subscript | Y | Y | Y
25 | superscript | Y | Y | Y
26 | undo | Y | Y | Y
27 | unlink | Y | Y | Y
28 |
29 |
30 |
--------------------------------------------------------------------------------
/src/range/command.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by peak on 2017/2/14.
3 | */
4 | export default {
5 | JUSTIFY_LEFT: 'justifyLeft',
6 | JUSTIFY_CENTER: 'justifyCenter',
7 | JUSTIFY_RIGHT: 'justifyRight',
8 | FORE_COLOR: 'foreColor',
9 | BACK_COLOR: 'backColor',
10 | REMOVE_FORMAT: 'removeFormat',
11 | FONT_NAME: 'fontName',
12 | FONT_SIZE: 'fontSize',
13 | FORMAT_BLOCK: 'formatBlock',
14 | LINE_HEIGHT: 'lineHeight',
15 | INSERT_HORIZONTAL_RULE: 'insertHorizontalRule',
16 | INSERT_IMAGE: 'insertImage',
17 | CREATE_LINK: 'createLink',
18 | INSERT_ORDERED_LIST: 'insertOrderedList',
19 | INSERT_UNORDERED_LIST: 'insertUnorderedList',
20 | INSERT_HTML: 'insertHTML',
21 | BOLD: 'bold',
22 | ITALIC: 'italic',
23 | UNDERLINE: 'underline',
24 | STRIKE_THROUGH: 'strikeThrough',
25 | SUBSCRIPT: 'subscript',
26 | SUPERSCRIPT: 'superscript',
27 | UNDO: 'undo',
28 | UNLINK: 'unlink'
29 | }
--------------------------------------------------------------------------------
/src/range/handler.js:
--------------------------------------------------------------------------------
1 | import Command from './command'
2 | import {
3 | mergeArray,
4 | getDescendantTextNodes,
5 | getAfterStartDescendantTextNodes,
6 | getBeforeEndDescendantTextNodes,
7 | getParentBlockNode,
8 | isInlineElement
9 | } from './util'
10 |
11 | // for IE 11
12 | if (!Text.prototype.contains) {
13 | Text.prototype.contains = function contains(otherNode) {
14 | return this === otherNode
15 | }
16 | }
17 |
18 |
19 | /**
20 | * Created by peak on 2017/2/14.
21 | */
22 | export default class RangeHandler {
23 | /**
24 | * build range handler
25 | * @param {Range} range
26 | */
27 | constructor(range) {
28 | if (!range || !(range instanceof Range)) {
29 | throw new TypeError('cant\'t resolve range')
30 | }
31 | this.range = range
32 | }
33 |
34 |
35 | /**
36 | * find all the text nodes in range
37 | */
38 | getAllTextNodesInRange() {
39 | const startContainer = this.range.startContainer
40 | const endContainer = this.range.endContainer
41 | const rootEl = this.range.commonAncestorContainer
42 | const textNodes = []
43 |
44 | if (startContainer === endContainer) {
45 | if (startContainer.nodeType === Node.TEXT_NODE) {
46 | return [startContainer]
47 | }
48 | const childNodes = startContainer.childNodes
49 | for (let i = this.range.startOffset; i < this.range.endOffset; i++) {
50 | mergeArray(textNodes, getDescendantTextNodes(childNodes[i]))
51 | }
52 | return textNodes
53 | }
54 |
55 | let startIndex = 0
56 | let endIndex = 0
57 | for (let i = 0; i < rootEl.childNodes.length; i++) {
58 | const node = rootEl.childNodes[i]
59 | if (node.contains(startContainer)) {
60 | startIndex = i
61 | }
62 | if (node.contains(endContainer)) {
63 | endIndex = i
64 | }
65 | }
66 |
67 | for (let i = startIndex; i <= endIndex; i++) {
68 | const node = rootEl.childNodes[i]
69 | if (i === startIndex) {
70 | if (node.nodeType === Node.TEXT_NODE) {
71 | textNodes.push(node)
72 | } else if (node.nodeType === Node.ELEMENT_NODE) {
73 | mergeArray(textNodes, getAfterStartDescendantTextNodes(node, startContainer))
74 | }
75 | } else if (i === endIndex) {
76 | if (node.nodeType === Node.TEXT_NODE) {
77 | textNodes.push(node)
78 | } else if (node.nodeType === Node.ELEMENT_NODE) {
79 | mergeArray(textNodes, getBeforeEndDescendantTextNodes(node, endContainer))
80 | }
81 | } else if (node.nodeType === Node.TEXT_NODE) {
82 | textNodes.push(node)
83 | } else if (node.nodeType === Node.ELEMENT_NODE) {
84 | mergeArray(textNodes, getDescendantTextNodes(node))
85 | }
86 | }
87 | return textNodes
88 | }
89 |
90 | /**
91 | * execute edit command
92 | * @param {String} command
93 | * @param arg
94 | */
95 | execCommand(command, arg) {
96 | switch (command) {
97 |
98 | case Command.JUSTIFY_LEFT: {
99 | document.execCommand(Command.JUSTIFY_LEFT, false, arg)
100 | break
101 | }
102 |
103 | case Command.JUSTIFY_RIGHT: {
104 | document.execCommand(Command.JUSTIFY_RIGHT, false, arg)
105 | break
106 | }
107 |
108 | case Command.JUSTIFY_CENTER: {
109 | document.execCommand(Command.JUSTIFY_CENTER, false, arg)
110 | break
111 | }
112 |
113 | case Command.FORE_COLOR: {
114 | document.execCommand(Command.FORE_COLOR, false, arg)
115 | break
116 | }
117 | case Command.BACK_COLOR: {
118 | document.execCommand(Command.BACK_COLOR, false, arg)
119 | break
120 | }
121 | case Command.REMOVE_FORMAT: {
122 | document.execCommand(Command.REMOVE_FORMAT, false, arg)
123 | break
124 | }
125 | case Command.FONT_NAME: {
126 | document.execCommand(Command.FONT_NAME, false, arg)
127 | break
128 | }
129 | case Command.FONT_SIZE: {
130 | // 重新实现,改为直接修改样式
131 | const textNodes = this.getAllTextNodesInRange()
132 | if (!textNodes.length) {
133 | break
134 | }
135 | if (textNodes.length === 1 && textNodes[0] === this.range.startContainer
136 | && textNodes[0] === this.range.endContainer) {
137 | const textNode = textNodes[0]
138 | if (this.range.startOffset === 0
139 | && this.range.endOffset === textNode.textContent.length) {
140 | if (textNode.parentNode.childNodes.length === 1
141 | && isInlineElement(textNode.parentNode)) {
142 | textNode.parentNode.style.fontSize = arg
143 | break
144 | }
145 | const span = document.createElement('span')
146 | span.style.fontSize = arg
147 | textNode.parentNode.insertBefore(span, textNode)
148 | span.appendChild(textNode)
149 | break
150 | }
151 | const span = document.createElement('span')
152 | span.innerText = textNode.textContent.substring(
153 | this.range.startOffset, this.range.endOffset)
154 | span.style.fontSize = arg
155 | const frontPart = document.createTextNode(
156 | textNode.textContent.substring(0, this.range.startOffset))
157 | textNode.parentNode.insertBefore(frontPart, textNode)
158 | textNode.parentNode.insertBefore(span, textNode)
159 | textNode.textContent = textNode.textContent.substring(this.range.endOffset)
160 | this.range.setStart(span, 0)
161 | this.range.setEnd(span, 1)
162 | break
163 | }
164 |
165 | textNodes.forEach((textNode) => {
166 | if (textNode === this.range.startContainer) {
167 | if (this.range.startOffset === 0) {
168 | if (textNode.parentNode.childNodes.length === 1
169 | && isInlineElement(textNode.parentNode)) {
170 | textNode.parentNode.style.fontSize = arg
171 | } else {
172 | const span = document.createElement('span')
173 | span.style.fontSize = arg
174 | textNode.parentNode.insertBefore(span, textNode)
175 | span.appendChild(textNode)
176 | }
177 | return
178 | }
179 | const span = document.createElement('span')
180 | textNode.textContent = textNode.textContent.substring(
181 | 0, this.range.startOffset)
182 | span.style.fontSize = arg
183 | textNode.parentNode.insertBefore(span, textNode)
184 | this.range.setStart(textNode, 0)
185 | return
186 | }
187 | if (textNode === this.range.endContainer) {
188 | if (this.range.endOffset === textNode.textContent.length) {
189 | if (textNode.parentNode.childNodes.length === 1
190 | && isInlineElement(textNode.parentNode)) {
191 | textNode.parentNode.style.fontSize = arg
192 | } else {
193 | const span = document.createElement('span')
194 | span.style.fontSize = arg
195 | textNode.parentNode.insertBefore(span, textNode)
196 | span.appendChild(textNode)
197 | }
198 | return
199 | }
200 | const span = document.createElement('span')
201 | textNode.textContent = textNode.textContent.substring(this.range.endOffset)
202 | span.style.fontSize = arg
203 | textNode.parentNode.insertBefore(span, textNode)
204 | span.appendChild(textNode)
205 | this.range.setStart(textNode, textNode.textContent.length)
206 | return
207 | }
208 | if (textNode.parentNode.childNodes.length === 1
209 | && isInlineElement(textNode.parentNode)) {
210 | textNode.parentNode.style.fontSize = arg
211 | return
212 | }
213 |
214 | const span = document.createElement('span')
215 | span.style.fontSize = arg
216 | textNode.parentNode.insertBefore(span, textNode)
217 | span.appendChild(textNode)
218 | })
219 | break
220 | }
221 | case Command.FORMAT_BLOCK: {
222 | if (document.execCommand(Command.FORMAT_BLOCK, false, arg)) {
223 | break
224 | }
225 | // hack
226 | const element = document.createElement(arg)
227 | this.range.surroundContents(element)
228 | break
229 | }
230 | case Command.LINE_HEIGHT: {
231 | const textNodes = this.getAllTextNodesInRange()
232 | textNodes.forEach((textNode) => {
233 | const parentBlock = getParentBlockNode(textNode)
234 | if (parentBlock) {
235 | parentBlock.style.lineHeight = arg
236 | }
237 | })
238 | break
239 | }
240 | case Command.INSERT_HORIZONTAL_RULE: {
241 | document.execCommand(Command.INSERT_HORIZONTAL_RULE, false)
242 | break
243 | }
244 | case Command.INSERT_IMAGE: {
245 | document.execCommand(Command.INSERT_IMAGE, false, arg)
246 | break
247 | }
248 | case Command.CREATE_LINK: {
249 | document.execCommand(Command.CREATE_LINK, false, arg)
250 | break
251 | }
252 | case Command.INSERT_ORDERED_LIST: {
253 | document.execCommand(Command.INSERT_ORDERED_LIST, false, arg)
254 | break
255 | }
256 | case Command.INSERT_UNORDERED_LIST: {
257 | document.execCommand(Command.INSERT_UNORDERED_LIST, false, arg)
258 | break
259 | }
260 | case Command.INSERT_HTML: {
261 | if (document.execCommand(Command.INSERT_HTML, false, arg)) {
262 | break
263 | }
264 | // hack
265 | const fragment = document.createDocumentFragment()
266 | const div = document.createElement('div')
267 | div.innerHTML = arg
268 | if (div.hasChildNodes()) {
269 | for (let i = 0; i < div.childNodes.length; i++) {
270 | fragment.appendChild(div.childNodes[i].cloneNode(true))
271 | }
272 | }
273 | this.range.deleteContents()
274 | this.range.insertNode(fragment)
275 | break
276 | }
277 | case Command.BOLD: {
278 | document.execCommand(Command.BOLD, false, arg)
279 | break
280 | }
281 | case Command.ITALIC: {
282 | document.execCommand(Command.ITALIC, false)
283 | break
284 | }
285 | case Command.UNDERLINE: {
286 | document.execCommand(Command.UNDERLINE, false)
287 | break
288 | }
289 | case Command.STRIKE_THROUGH: {
290 | document.execCommand(Command.STRIKE_THROUGH, false)
291 | break
292 | }
293 | case Command.SUBSCRIPT: {
294 | document.execCommand(Command.SUBSCRIPT, false)
295 | break
296 | }
297 | case Command.SUPERSCRIPT: {
298 | document.execCommand(Command.SUPERSCRIPT, false)
299 | break
300 | }
301 | case Command.UNDO: {
302 | document.execCommand(Command.UNDO, false)
303 | break
304 | }
305 | case Command.UNLINK: {
306 | document.execCommand(Command.UNLINK, false)
307 | break
308 | }
309 | default: {
310 | document.execCommand(command, false, arg)
311 | break
312 | }
313 | }
314 | }
315 | }
--------------------------------------------------------------------------------
/src/range/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by peak on 2017/2/15.
3 | */
4 | /**
5 | * add every elements of extArr to sourceArr.
6 | * @param sourceArr
7 | * @param extArr
8 | */
9 | export const mergeArray = (sourceArr, extArr) => {
10 | // note: Array.prototype.push.apply(arr1,arr2) is unreliable
11 | extArr.forEach((el) => {
12 | sourceArr.push(el)
13 | })
14 | }
15 |
16 | /**
17 | * find all the descendant text nodes of a element
18 | * @param ancestor
19 | */
20 | export const getDescendantTextNodes = (ancestor) => {
21 | if (ancestor.nodeType === Node.TEXT_NODE) {
22 | return [ancestor]
23 | }
24 | const textNodes = []
25 | if (!ancestor.hasChildNodes()) {
26 | return textNodes
27 | }
28 | const childNodes = ancestor.childNodes
29 | for (let i = 0; i < childNodes.length; i++) {
30 | const node = childNodes[i]
31 | if (node.nodeType === Node.TEXT_NODE) {
32 | textNodes.push(node)
33 | } else if (node.nodeType === Node.ELEMENT_NODE) {
34 | mergeArray(textNodes, getDescendantTextNodes(node))
35 | }
36 | }
37 | return textNodes
38 | }
39 | /**
40 | * find all the descendant text nodes of an ancestor element that before the specify end element,
41 | * the ancestor element must contains the end element.
42 | * @param ancestor
43 | * @param endEl
44 | */
45 | export const getBeforeEndDescendantTextNodes = (ancestor, endEl) => {
46 | const textNodes = []
47 | let endIndex = 0
48 | for (let i = 0; i < ancestor.childNodes.length; i++) {
49 | if (ancestor.childNodes[i].contains(endEl)) {
50 | endIndex = i
51 | break
52 | }
53 | }
54 |
55 | for (let i = 0; i <= endIndex; i++) {
56 | const node = ancestor.childNodes[i]
57 | if (node === endEl) {
58 | mergeArray(textNodes, getDescendantTextNodes(node))
59 | } else if (i === endIndex) {
60 | if (node.nodeType === Node.TEXT_NODE) {
61 | textNodes.push(node)
62 | } else if (node.nodeType === Node.ELEMENT_NODE) {
63 | mergeArray(textNodes, getBeforeEndDescendantTextNodes(node, endEl))
64 | }
65 | } else if (node.nodeType === Node.TEXT_NODE) {
66 | textNodes.push(node)
67 | } else if (node.nodeType === Node.ELEMENT_NODE) {
68 | mergeArray(textNodes, getDescendantTextNodes(node))
69 | }
70 | }
71 | return textNodes
72 | }
73 | /**
74 | * find all the descendant text nodes of an ancestor element that after the specify start element,
75 | * the ancestor element must contains the start element.
76 | * @param ancestor
77 | * @param startEl
78 | */
79 | export const getAfterStartDescendantTextNodes = (ancestor, startEl) => {
80 | const textNodes = []
81 | let startIndex = 0
82 | for (let i = 0; i < ancestor.childNodes.length; i++) {
83 | if (ancestor.childNodes[i].contains(startEl)) {
84 | startIndex = i
85 | break
86 | }
87 | }
88 |
89 | for (let i = startIndex; i < ancestor.childNodes.length; i++) {
90 | const node = ancestor.childNodes[i]
91 | if (node === startEl) {
92 | mergeArray(textNodes, getDescendantTextNodes(node))
93 | } else if (i === startIndex) {
94 | if (node.nodeType === Node.TEXT_NODE) {
95 | textNodes.push(node)
96 | } else if (node.nodeType === Node.ELEMENT_NODE) {
97 | mergeArray(textNodes,
98 | getAfterStartDescendantTextNodes(node, startEl))
99 | }
100 | } else if (node.nodeType === Node.TEXT_NODE) {
101 | textNodes.push(node)
102 | } else if (node.nodeType === Node.ELEMENT_NODE) {
103 | mergeArray(textNodes,
104 | getDescendantTextNodes(node))
105 | }
106 | }
107 | return textNodes
108 | }
109 |
110 |
111 | /**
112 | * get the closest parent block node of a text node.
113 | * @param node
114 | * @return {Node}
115 | */
116 | export const getParentBlockNode = (node) => {
117 | const blockNodeNames = ['DIV', 'P', 'SECTION', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
118 | 'OL', 'UL', 'LI', 'TR', 'TD', 'TH', 'TBODY', 'THEAD', 'TABLE', 'ARTICLE', 'HEADER', 'FOOTER']
119 | let container = node
120 | while (container) {
121 | if (blockNodeNames.includes(container.nodeName)) {
122 | break
123 | }
124 | container = container.parentNode
125 | }
126 | return container
127 | }
128 |
129 | export const isInlineElement = (node) => {
130 | const inlineNodeNames = ['A', 'ABBR', 'ACRONYM', 'B', 'CITE', 'CODE', 'EM', 'I',
131 | 'FONT', 'IMG', 'S', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'U', 'SUB', 'SUP']
132 | return inlineNodeNames.includes(node.nodeName)
133 | }
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --border-color: #ddd;
3 | --border-radius: 5px;
4 | --color: #333;
5 | }
6 |
7 | /**
8 | .vue-html5-editor
9 | ├──.toolbar
10 | | ├── ul (menu)
11 | | └── .dashboard.html
12 | └──.content
13 | */
14 |
15 | .vue-html5-editor {
16 | font-size: 14px;
17 | line-height: 1.5;
18 | background-color: white;
19 | color: var(--color);
20 | border: 1px solid var(--border-color);
21 | text-align: left;
22 | border-radius: var(--border-radius);
23 | overflow: hidden;
24 | box-sizing: border-box;
25 |
26 | & * {
27 | box-sizing: border-box;
28 | }
29 |
30 | &.full-screen {
31 | position: fixed !important;
32 | top: 0 !important;
33 | left: 0 !important;
34 | bottom: 0 !important;
35 | right: 0 !important;
36 | border-radius: 0;
37 | }
38 |
39 | & > .toolbar {
40 | position: relative;
41 | background-color: inherit;
42 |
43 | & > ul {
44 | list-style: none;
45 | padding: 0;
46 | margin: 0;
47 | border-bottom: 1px solid var(--border-color);
48 |
49 | & > li {
50 | display: inline-block;
51 | cursor: pointer;
52 | text-align: center;
53 | line-height: 36px;
54 | padding: 0 10px;
55 | & .icon {
56 | height: 16px;
57 | width: 16px;
58 | display: inline-block;
59 | vertical-align: middle;
60 | }
61 | }
62 | }
63 |
64 | & > .dashboard {
65 | background-color: inherit;
66 | border-bottom: 1px solid var(--border-color);
67 | padding: 10px;
68 | position: absolute;
69 | top: 100%;
70 | left: 0;
71 | right: 0;
72 | overflow: auto;
73 |
74 | & input[type='text'], & input[type='number'], & select {
75 | padding: 6px 12px;
76 | color: inherit;
77 | background-color: transparent;
78 | border: 1px solid var(--border-color);
79 | border-radius: var(--border-radius);
80 |
81 | &:hover {
82 | border-color: color(var(--border-color) blackness(30%));
83 | }
84 |
85 | &[disabled], &[readonly] {
86 | background-color: #eee;
87 | opacity: 1;
88 | }
89 |
90 | &[disabled] {
91 | cursor: not-allowed;
92 | }
93 | }
94 |
95 | & button {
96 | color: inherit;
97 | background-color: inherit;
98 | padding: 6px 12px;
99 | white-space: nowrap;
100 | vertical-align: middle;
101 | cursor: pointer;
102 | user-select: none;
103 | border: 1px solid var(--border-color);
104 | border-radius: var(--border-radius);
105 | margin-right: 4px;
106 | margin-bottom: 4px;
107 |
108 | &:hover {
109 | border-color: color(var(--border-color) blackness(30%));
110 | }
111 |
112 | &[disabled] {
113 | cursor: not-allowed;
114 | opacity: .68;
115 | }
116 |
117 | &:last-child {
118 | margin-right: 0;
119 | }
120 | }
121 |
122 | & input, button, select {
123 | line-height: normal;
124 | }
125 |
126 | & label {
127 | font-weight: bolder;
128 | }
129 |
130 | }
131 |
132 | }
133 | & > .content {
134 | overflow: auto;
135 | padding: 10px;
136 |
137 | &:focus {
138 | outline: 0;
139 | }
140 |
141 | }
142 |
143 | }
144 |
145 | @media (max-width: 767px) {
146 | .vue-html5-editor {
147 | .dashboard {
148 | label, input[type='text'], input[type='number'], button, select {
149 | display: block;
150 | margin-bottom: 5px;
151 | width: 100% !important;
152 | &:last-child {
153 | margin-bottom: 0;
154 | }
155 | }
156 | }
157 | }
158 | }
159 |
160 | @media (min-width: 768px) {
161 | .vue-html5-editor {
162 | .dashboard {
163 | label, input, button, select {
164 | display: inline-block;
165 | margin-right: 4px;
166 | max-width: 100%;
167 | &:last-child {
168 | margin-right: 0;
169 | }
170 | }
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/src/util/mixin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by peak on 2017/2/24.
3 | */
4 | /**
5 | * shadow clone
6 | *
7 | * @param source source object
8 | * @param ext extended object
9 | */
10 | export default function mixin(source = {}, ext = {}) {
11 | Object.keys(ext).forEach((k) => {
12 | // for data function
13 | if (k === 'data') {
14 | const dataSrc = source[k]
15 | const dataDesc = ext[k]
16 | if (typeof dataDesc === 'function') {
17 | if (typeof dataSrc !== 'function') {
18 | source[k] = dataDesc
19 | } else {
20 | source[k] = () => mixin(dataSrc(), dataDesc())
21 | }
22 | }
23 | } else {
24 | source[k] = ext[k]
25 | }
26 | })
27 | return source
28 | }
--------------------------------------------------------------------------------