├── .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 | ![screenshot](http://tai.coding.me/vue-html5-editor/editor.png?v=20160912) 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 = "
"; 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 = "
"; 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 = "
{{$parent.locale.progress}}:{{upload.progressComputable ? $parent.locale.unknown : upload.complete}}
{{$parent.locale[\"please wait\"]}}...
{{$parent.locale.error}}:{{upload.errorMsg}}
{{$parent.locale.upload}} {{$parent.locale.abort}},
"; 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 = "

Vue-html5-editor {{version}}

repository: https://github.com/PeakTai/vue-html5-editor

"; 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 = "
"; 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 = "
"; 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 | 22 | 23 | 24 | 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 |
2 |
3 |
    4 | 12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
21 |
22 |
-------------------------------------------------------------------------------- /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 | 5 | 8 | 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 |
2 |
3 | 7 | 11 |
12 |
13 |
15 |
16 |
17 |
18 |
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 | 4 | 5 |
6 |
7 | 10 | 11 |
12 |
13 | 16 | 17 |
18 |
19 | 22 | 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 | 7 | 8 | 10 | 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 | 24 |
25 | 26 |
27 | {{$parent.locale.upload}} {{$parent.locale.abort}}, 28 | 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 |
2 |

Vue-html5-editor {{version}}

3 |

4 | repository: 5 | 6 | https://github.com/PeakTai/vue-html5-editor 7 | 8 |

9 |
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 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /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 | 5 | 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 |
2 | 6 | 10 | 11 | 12 |
-------------------------------------------------------------------------------- /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 | 3 | 4 | 5 | 7 | 8 | 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 | } --------------------------------------------------------------------------------