From 9577a806d45801feef58c1f3a138c35e525a45f1 Mon Sep 17 00:00:00 2001 From: xiongxiaoyang <773861846@qq.com> Date: Tue, 19 Nov 2019 18:54:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=8F=E8=AF=B4=E5=8F=91=E5=B8=83=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E4=B8=AD=E3=80=82=E3=80=82=E3=80=82=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../books/controller/BookController.java | 82 +- .../java/com/java2nb/books/dao/BookDao.java | 4 + .../com/java2nb/books/dao/BookIndexDao.java | 9 + .../java2nb/books/service/BookService.java | 18 + .../books/service/impl/BookServiceImpl.java | 69 +- .../com/java2nb/books/util/StringUtil.java | 38 + .../com/java2nb/books/vo/BookIndexVO.java | 12 + .../src/main/resources/application.yml | 6 +- .../mybatis/books/BookIndexMapper.xml | 59 +- .../resources/mybatis/books/BookMapper.xml | 154 +- .../static/js/appjs/books/book/add.js | 116 +- .../static/js/appjs/books/book/book.js | 412 +- .../static/js/appjs/books/bookIndex/add.js | 50 +- .../js/appjs/books/bookIndex/bookIndex.js | 327 +- .../src/main/resources/static/js/dict-util.js | 52 + .../resources/static/wangEditor/.eslintignore | 2 + .../static/wangEditor/.eslintrc.json | 38 + .../static/wangEditor/.gitattributes | 22 + .../resources/static/wangEditor/.gitignore | 51 + .../resources/static/wangEditor/.npmignore | 5 + .../main/resources/static/wangEditor/ISSUE.md | 157 + .../main/resources/static/wangEditor/LICENSE | 22 + .../resources/static/wangEditor/README.md | 70 + .../resources/static/wangEditor/bower.json | 20 + .../static/wangEditor/docs/dev/README.md | 25 + .../docs/usage/01-getstart/01-demo.md | 41 + .../docs/usage/01-getstart/02-use-module.md | 49 + .../docs/usage/01-getstart/03-sperate.md | 48 + .../docs/usage/01-getstart/04-multi.md | 50 + .../docs/usage/02-content/01-set-content.md | 46 + .../docs/usage/02-content/02-get-content.md | 80 + .../docs/usage/02-content/03-use-textarea.md | 25 + .../docs/usage/02-content/04-get-json.md | 82 + .../docs/usage/03-config/01-menu.md | 52 + .../docs/usage/03-config/02-debug.md | 21 + .../docs/usage/03-config/03-onchange.md | 40 + .../docs/usage/03-config/04-z-index.md | 19 + .../docs/usage/03-config/05-lang.md | 30 + .../docs/usage/03-config/06-paste.md | 33 + .../usage/03-config/07-linkImgCallback.md | 12 + .../docs/usage/03-config/08-linkCheck.md | 16 + .../docs/usage/03-config/09-onfocus.md | 19 + .../docs/usage/03-config/10-onblur.md | 20 + .../docs/usage/03-config/11-linkImgCheck.md | 15 + .../docs/usage/03-config/12-colors.md | 29 + .../docs/usage/03-config/13-emot.md | 48 + .../docs/usage/04-uploadimg/01-show-tab.md | 52 + .../docs/usage/04-uploadimg/02-base64.md | 23 + .../usage/04-uploadimg/03-upload-config.md | 188 + .../docs/usage/04-uploadimg/04-qiniu.md | 115 + .../usage/05-other/01-全屏-预览-查看源码.md | 10 + .../docs/usage/05-other/02-上传附件.md | 24 + .../docs/usage/05-other/03-markdown.md | 12 + .../wangEditor/docs/usage/05-other/04-xss.md | 23 + .../docs/usage/05-other/05-react.md | 7 + .../wangEditor/docs/usage/05-other/06-vue.md | 7 + .../wangEditor/docs/usage/05-other/07-ng.md | 3 + .../wangEditor/docs/usage/05-other/08-api.md | 27 + .../static/wangEditor/docs/usage/README.md | 3 + .../static/wangEditor/example/README.md | 1 + .../example/demo/in-react/package.json | 19 + .../example/demo/in-react/public/favicon.ico | Bin 0 -> 24838 bytes .../example/demo/in-react/public/index.html | 40 + .../demo/in-react/public/manifest.json | 15 + .../example/demo/in-react/src/App.css | 24 + .../example/demo/in-react/src/App.js | 48 + .../example/demo/in-react/src/App.test.js | 8 + .../example/demo/in-react/src/index.css | 5 + .../example/demo/in-react/src/index.js | 8 + .../example/demo/in-react/src/logo.svg | 7 + .../in-react/src/registerServiceWorker.js | 51 + .../wangEditor/example/demo/in-vue/.babelrc | 14 + .../example/demo/in-vue/.editorconfig | 9 + .../example/demo/in-vue/.postcssrc.js | 8 + .../example/demo/in-vue/build/build.js | 35 + .../demo/in-vue/build/check-versions.js | 48 + .../example/demo/in-vue/build/dev-client.js | 9 + .../example/demo/in-vue/build/dev-server.js | 89 + .../example/demo/in-vue/build/utils.js | 71 + .../demo/in-vue/build/vue-loader.conf.js | 12 + .../demo/in-vue/build/webpack.base.conf.js | 58 + .../demo/in-vue/build/webpack.dev.conf.js | 35 + .../demo/in-vue/build/webpack.prod.conf.js | 120 + .../example/demo/in-vue/config/dev.env.js | 6 + .../example/demo/in-vue/config/index.js | 38 + .../example/demo/in-vue/config/prod.env.js | 3 + .../wangEditor/example/demo/in-vue/index.html | 11 + .../example/demo/in-vue/package.json | 60 + .../example/demo/in-vue/src/App.vue | 31 + .../example/demo/in-vue/src/assets/logo.png | Bin 0 -> 6849 bytes .../demo/in-vue/src/components/Editor.vue | 34 + .../demo/in-vue/src/components/Hello.vue | 53 + .../example/demo/in-vue/src/main.js | 13 + .../example/demo/in-vue/static/.gitkeep | 0 .../wangEditor/example/demo/test-amd-main.js | 4 + .../wangEditor/example/demo/test-amd.html | 15 + .../example/demo/test-css-reset.html | 66 + .../wangEditor/example/demo/test-emot.html | 84 + .../example/demo/test-fullscreen.html | 114 + .../example/demo/test-get-content.html | 34 + .../wangEditor/example/demo/test-getJSON.html | 30 + .../wangEditor/example/demo/test-lang.html | 31 + .../wangEditor/example/demo/test-menus.html | 26 + .../wangEditor/example/demo/test-mult.html | 44 + .../wangEditor/example/demo/test-onblur.html | 23 + .../example/demo/test-onchange.html | 24 + .../wangEditor/example/demo/test-onfocus.html | 22 + .../wangEditor/example/demo/test-paste.html | 25 + .../example/demo/test-set-content.html | 35 + .../wangEditor/example/demo/test-sperate.html | 35 + .../example/demo/test-textarea.html | 33 + .../example/demo/test-uploadimg.html | 58 + .../static/wangEditor/example/favicon.ico | Bin 0 -> 4286 bytes .../wangEditor/example/icomoon/Read Me.txt | 7 + .../example/icomoon/demo-files/demo.css | 155 + .../example/icomoon/demo-files/demo.js | 30 + .../wangEditor/example/icomoon/demo.html | 505 ++ .../example/icomoon/fonts/icomoon.eot | Bin 0 -> 5656 bytes .../example/icomoon/fonts/icomoon.svg | 37 + .../example/icomoon/fonts/icomoon.ttf | Bin 0 -> 5492 bytes .../example/icomoon/fonts/icomoon.woff | Bin 0 -> 5568 bytes .../wangEditor/example/icomoon/selection.json | 775 +++ .../wangEditor/example/icomoon/style.css | 113 + .../static/wangEditor/example/index.html | 62 + .../static/wangEditor/example/pay.png | Bin 0 -> 95517 bytes .../static/wangEditor/example/server/index.js | 88 + .../static/wangEditor/example/server/util.js | 14 + .../resources/static/wangEditor/gulpfile.js | 122 + .../resources/static/wangEditor/package.json | 60 + .../wangEditor/release/fonts/w-e-icon.woff | Bin 0 -> 5568 bytes .../static/wangEditor/release/wangEditor.css | 405 ++ .../static/wangEditor/release/wangEditor.js | 4679 +++++++++++++++++ .../wangEditor/release/wangEditor.min.css | 1 + .../wangEditor/release/wangEditor.min.js | 4 + .../wangEditor/release/wangEditor.min.js.map | 1 + .../static/wangEditor/src/fonts/w-e-icon.woff | Bin 0 -> 5568 bytes .../static/wangEditor/src/js/.babelrc | 10 + .../static/wangEditor/src/js/command/index.js | 106 + .../static/wangEditor/src/js/config.js | 387 ++ .../static/wangEditor/src/js/editor/index.js | 339 ++ .../src/js/editor/upload/progress.js | 65 + .../src/js/editor/upload/upload-img.js | 316 ++ .../static/wangEditor/src/js/index.js | 24 + .../src/js/menus/backColor/index.js | 46 + .../wangEditor/src/js/menus/bold/index.js | 60 + .../wangEditor/src/js/menus/code/index.js | 150 + .../wangEditor/src/js/menus/droplist.js | 130 + .../wangEditor/src/js/menus/emoticon/index.js | 115 + .../src/js/menus/foreColor/index.js | 46 + .../wangEditor/src/js/menus/head/index.js | 70 + .../wangEditor/src/js/menus/img/index.js | 252 + .../static/wangEditor/src/js/menus/index.js | 125 + .../wangEditor/src/js/menus/italic/index.js | 60 + .../wangEditor/src/js/menus/justify/index.js | 44 + .../wangEditor/src/js/menus/link/index.js | 168 + .../wangEditor/src/js/menus/list/index.js | 82 + .../wangEditor/src/js/menus/menu-list.js | 63 + .../static/wangEditor/src/js/menus/panel.js | 195 + .../wangEditor/src/js/menus/quote/index.js | 75 + .../wangEditor/src/js/menus/redo/index.js | 35 + .../src/js/menus/strikethrough/index.js | 60 + .../wangEditor/src/js/menus/table/index.js | 376 ++ .../src/js/menus/underline/index.js | 60 + .../wangEditor/src/js/menus/undo/index.js | 35 + .../wangEditor/src/js/menus/video/index.js | 86 + .../wangEditor/src/js/selection/index.js | 186 + .../static/wangEditor/src/js/text/index.js | 551 ++ .../static/wangEditor/src/js/util/dom-core.js | 488 ++ .../wangEditor/src/js/util/paste-handle.js | 86 + .../wangEditor/src/js/util/poly-fill.js | 48 + .../wangEditor/src/js/util/replace-lang.js | 21 + .../static/wangEditor/src/js/util/util.js | 71 + .../static/wangEditor/src/less/common.less | 19 + .../static/wangEditor/src/less/droplist.less | 48 + .../static/wangEditor/src/less/icon.less | 102 + .../static/wangEditor/src/less/menus.less | 33 + .../static/wangEditor/src/less/panel.less | 159 + .../static/wangEditor/src/less/text.less | 77 + .../resources/templates/books/book/add.html | 183 +- .../resources/templates/books/book/book.html | 21 +- .../resources/templates/books/book/edit.html | 184 +- .../templates/books/bookContent/add.html | 66 +- .../templates/books/bookIndex/add.html | 51 +- .../templates/books/bookIndex/bookIndex.html | 9 +- .../src/main/resources/templates/include.html | 2 + .../src/main/resources/templates/index.html | 10 + .../src/main/resources/application.yml | 2 +- 187 files changed, 17094 insertions(+), 736 deletions(-) create mode 100644 novel-admin/src/main/java/com/java2nb/books/util/StringUtil.java create mode 100644 novel-admin/src/main/java/com/java2nb/books/vo/BookIndexVO.java create mode 100644 novel-admin/src/main/resources/static/js/dict-util.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/.eslintignore create mode 100644 novel-admin/src/main/resources/static/wangEditor/.eslintrc.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/.gitattributes create mode 100644 novel-admin/src/main/resources/static/wangEditor/.gitignore create mode 100644 novel-admin/src/main/resources/static/wangEditor/.npmignore create mode 100644 novel-admin/src/main/resources/static/wangEditor/ISSUE.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/LICENSE create mode 100644 novel-admin/src/main/resources/static/wangEditor/README.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/bower.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/dev/README.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/01-demo.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/02-use-module.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/03-sperate.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/04-multi.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/01-set-content.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/02-get-content.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/03-use-textarea.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/04-get-json.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/01-menu.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/02-debug.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/03-onchange.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/04-z-index.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/05-lang.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/06-paste.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/07-linkImgCallback.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/08-linkCheck.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/09-onfocus.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/10-onblur.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/11-linkImgCheck.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/12-colors.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/13-emot.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/01-show-tab.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/02-base64.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/03-upload-config.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/04-qiniu.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/01-全屏-预览-查看源码.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/02-上传附件.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/03-markdown.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/04-xss.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/05-react.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/06-vue.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/07-ng.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/08-api.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/docs/usage/README.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/README.md create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/package.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/favicon.ico create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/index.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/manifest.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.test.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/logo.svg create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/registerServiceWorker.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.babelrc create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.editorconfig create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.postcssrc.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/build.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/check-versions.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-client.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-server.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/utils.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/vue-loader.conf.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.base.conf.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.dev.conf.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.prod.conf.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/dev.env.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/prod.env.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/index.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/package.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/App.vue create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/assets/logo.png create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/components/Editor.vue create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/components/Hello.vue create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/main.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/static/.gitkeep create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd-main.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-css-reset.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-emot.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-fullscreen.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-get-content.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-getJSON.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-lang.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-menus.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-mult.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-onblur.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-onchange.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-onfocus.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-paste.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-set-content.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-sperate.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-textarea.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/demo/test-uploadimg.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/favicon.ico create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/Read Me.txt create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/demo-files/demo.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/demo-files/demo.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/demo.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.eot create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.svg create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.ttf create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.woff create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/selection.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/icomoon/style.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/index.html create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/pay.png create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/server/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/example/server/util.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/gulpfile.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/package.json create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/fonts/w-e-icon.woff create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/wangEditor.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/wangEditor.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/wangEditor.min.css create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/wangEditor.min.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/release/wangEditor.min.js.map create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/fonts/w-e-icon.woff create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/.babelrc create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/command/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/config.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/editor/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/editor/upload/progress.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/editor/upload/upload-img.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/backColor/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/bold/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/code/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/droplist.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/emoticon/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/foreColor/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/head/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/img/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/italic/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/justify/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/link/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/list/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/menu-list.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/panel.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/quote/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/redo/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/strikethrough/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/table/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/underline/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/undo/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/menus/video/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/selection/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/text/index.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/util/dom-core.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/util/paste-handle.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/util/poly-fill.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/util/replace-lang.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/js/util/util.js create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/common.less create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/droplist.less create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/icon.less create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/menus.less create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/panel.less create mode 100644 novel-admin/src/main/resources/static/wangEditor/src/less/text.less diff --git a/novel-admin/src/main/java/com/java2nb/books/controller/BookController.java b/novel-admin/src/main/java/com/java2nb/books/controller/BookController.java index ef7e275..4b68fcf 100644 --- a/novel-admin/src/main/java/com/java2nb/books/controller/BookController.java +++ b/novel-admin/src/main/java/com/java2nb/books/controller/BookController.java @@ -3,6 +3,9 @@ package com.java2nb.books.controller; import java.util.List; import java.util.Map; +import com.java2nb.books.domain.BookContentDO; +import com.java2nb.books.domain.BookIndexDO; +import com.java2nb.books.vo.BookIndexVO; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; @@ -22,9 +25,9 @@ import com.java2nb.common.utils.PageBean; import com.java2nb.common.utils.Query; import com.java2nb.common.utils.R; +import javax.jws.WebParam; + /** - * - * * @author xiongxy * @email 1179705413@qq.com * @date 2019-11-13 09:27:04 @@ -64,7 +67,7 @@ public class BookController { @ApiOperation(value = "修改页面", notes = "修改页面") @GetMapping("/edit/{id}") String edit(@PathVariable("id") Long id, Model model) { - BookDO book = bookService.get(id); + BookDO book = bookService.get(id); model.addAttribute("book", book); return "books/book/edit"; } @@ -72,7 +75,7 @@ public class BookController { @ApiOperation(value = "查看页面", notes = "查看页面") @GetMapping("/detail/{id}") String detail(@PathVariable("id") Long id, Model model) { - BookDO book = bookService.get(id); + BookDO book = bookService.get(id); model.addAttribute("book", book); return "books/book/detail"; } @@ -83,7 +86,7 @@ public class BookController { @ApiOperation(value = "新增", notes = "新增") @ResponseBody @PostMapping("/save") - public R save( BookDO book) { + public R save(BookDO book) { if (bookService.save(book) > 0) { return R.ok(); } @@ -96,8 +99,8 @@ public class BookController { @ApiOperation(value = "修改", notes = "修改") @ResponseBody @RequestMapping("/update") - public R update( BookDO book) { - bookService.update(book); + public R update(BookDO book) { + bookService.update(book); return R.ok(); } @@ -107,7 +110,7 @@ public class BookController { @ApiOperation(value = "删除", notes = "删除") @PostMapping("/remove") @ResponseBody - public R remove( Long id) { + public R remove(Long id) { if (bookService.remove(id) > 0) { return R.ok(); } @@ -121,8 +124,69 @@ public class BookController { @PostMapping("/batchRemove") @ResponseBody public R remove(@RequestParam("ids[]") Long[] ids) { - bookService.batchRemove(ids); + bookService.batchRemove(ids); return R.ok(); } + + @ApiOperation(value = "新增章节页面", notes = "新增章节页面") + @GetMapping("/index/add") + String indexAdd(Long bookId, Model model) { + model.addAttribute("bookId",bookId); + return "books/bookIndex/add"; + } + + /** + * 保存章节 + */ + @ApiOperation(value = "新增章节", notes = "新增章节") + @ResponseBody + @PostMapping("/index/save") + public R indexSave(BookIndexDO bookIndex, BookContentDO bookContent) { + if (bookService.saveIndexAndContent(bookIndex,bookContent) > 0) { + return R.ok(); + } + return R.error(); + } + + @GetMapping("/index") + String BookIndex() { + return "books/bookIndex/bookIndex"; + } + + @ApiOperation(value = "获取章节列表", notes = "获取章节列表") + @ResponseBody + @GetMapping("/index/list") + public R indexList(@RequestParam Map params) { + //查询列表数据 + Query query = new Query(params); + List bookIndexList = bookService.indexVOList(query); + int total = bookService.indexVOCount(query); + PageBean pageBean = new PageBean(bookIndexList, total); + return R.ok().put("data", pageBean); + } + + /** + * 删除 + */ + @ApiOperation(value = "删除", notes = "删除") + @PostMapping("/index/remove") + @ResponseBody + public R indexRemove( Long id) { + if (bookService.indexRemove(id) > 0) { + return R.ok(); + } + return R.error(); + } + + /** + * 删除 + */ + @ApiOperation(value = "批量删除", notes = "批量删除") + @PostMapping("/index/batchRemove") + @ResponseBody + public R indexRemove(@RequestParam("ids[]") Long[] ids) { + bookService.batchIndexRemove(ids); + return R.ok(); + } } diff --git a/novel-admin/src/main/java/com/java2nb/books/dao/BookDao.java b/novel-admin/src/main/java/com/java2nb/books/dao/BookDao.java index 01327cf..2a1fca6 100644 --- a/novel-admin/src/main/java/com/java2nb/books/dao/BookDao.java +++ b/novel-admin/src/main/java/com/java2nb/books/dao/BookDao.java @@ -2,10 +2,12 @@ package com.java2nb.books.dao; import com.java2nb.books.domain.BookDO; +import java.util.Date; import java.util.List; import java.util.Map; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; /** * @@ -29,4 +31,6 @@ public interface BookDao { int remove(Long id); int batchRemove(Long[] ids); + + void uptUpdateTime( @Param("id") Long bookId, @Param("updateTime") Date date); } diff --git a/novel-admin/src/main/java/com/java2nb/books/dao/BookIndexDao.java b/novel-admin/src/main/java/com/java2nb/books/dao/BookIndexDao.java index 366e220..2f6d3a3 100644 --- a/novel-admin/src/main/java/com/java2nb/books/dao/BookIndexDao.java +++ b/novel-admin/src/main/java/com/java2nb/books/dao/BookIndexDao.java @@ -5,7 +5,10 @@ import com.java2nb.books.domain.BookIndexDO; import java.util.List; import java.util.Map; +import com.java2nb.books.vo.BookIndexVO; +import com.java2nb.common.utils.Query; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; /** * @@ -31,4 +34,10 @@ public interface BookIndexDao { int batchRemove(Long[] ids); void insertBatch(List newBookIndexList); + + Integer queryMaxIndexNum(@Param("bookId") Long bookId); + + List listVO(Query query); + + int countVO(Query query); } diff --git a/novel-admin/src/main/java/com/java2nb/books/service/BookService.java b/novel-admin/src/main/java/com/java2nb/books/service/BookService.java index 9cc347c..fd4b10c 100644 --- a/novel-admin/src/main/java/com/java2nb/books/service/BookService.java +++ b/novel-admin/src/main/java/com/java2nb/books/service/BookService.java @@ -1,6 +1,10 @@ package com.java2nb.books.service; +import com.java2nb.books.domain.BookContentDO; import com.java2nb.books.domain.BookDO; +import com.java2nb.books.domain.BookIndexDO; +import com.java2nb.books.vo.BookIndexVO; +import com.java2nb.common.utils.Query; import java.util.List; import java.util.Map; @@ -27,4 +31,18 @@ public interface BookService { int remove(Long id); int batchRemove(Long[] ids); + + /** + * 保存章节 + */ + int saveIndexAndContent(BookIndexDO bookIndex, BookContentDO bookContent); + + + List indexVOList(Query query); + + int indexVOCount(Query query); + + int indexRemove(Long id); + + int batchIndexRemove(Long[] ids); } diff --git a/novel-admin/src/main/java/com/java2nb/books/service/impl/BookServiceImpl.java b/novel-admin/src/main/java/com/java2nb/books/service/impl/BookServiceImpl.java index 9e555e4..a6500da 100644 --- a/novel-admin/src/main/java/com/java2nb/books/service/impl/BookServiceImpl.java +++ b/novel-admin/src/main/java/com/java2nb/books/service/impl/BookServiceImpl.java @@ -1,21 +1,38 @@ package com.java2nb.books.service.impl; +import com.java2nb.books.dao.BookContentDao; +import com.java2nb.books.dao.BookIndexDao; +import com.java2nb.books.domain.BookContentDO; +import com.java2nb.books.domain.BookIndexDO; +import com.java2nb.books.util.StringUtil; +import com.java2nb.books.vo.BookIndexVO; +import com.java2nb.common.utils.Query; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Date; +import java.util.HashMap; import java.util.List; import java.util.Map; import com.java2nb.books.dao.BookDao; import com.java2nb.books.domain.BookDO; import com.java2nb.books.service.BookService; - +import org.springframework.transaction.annotation.Transactional; @Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; + + @Autowired + private BookIndexDao bookIndexDao; + + @Autowired + private BookContentDao bookContentDao; + @Override public BookDO get(Long id){ @@ -24,6 +41,11 @@ public class BookServiceImpl implements BookService { @Override public List list(Map map){ + String sort = (String) map.get("sort"); + if(StringUtils.isNotBlank(sort)){ + map.put("sort",StringUtil.humpToLine(sort)); + + } return bookDao.list(map); } @@ -34,11 +56,16 @@ public class BookServiceImpl implements BookService { @Override public int save(BookDO book){ + book.setVisitCount(0l); + if(book.getUpdateTime() == null){ + book.setUpdateTime(new Date()); + } return bookDao.save(book); } @Override public int update(BookDO book){ + return bookDao.update(book); } @@ -51,5 +78,43 @@ public class BookServiceImpl implements BookService { public int batchRemove(Long[] ids){ return bookDao.batchRemove(ids); } - + + @Override + @Transactional + public int saveIndexAndContent(BookIndexDO bookIndex, BookContentDO bookContent) { + Integer maxBookNum = bookIndexDao.queryMaxIndexNum(bookIndex.getBookId()); + int nextIndexNum = 0; + if(maxBookNum != null){ + nextIndexNum = maxBookNum + 1; + } + bookIndex.setIndexNum(nextIndexNum); + bookContent.setBookId(bookIndex.getBookId()); + bookContent.setIndexNum(nextIndexNum); + bookDao.uptUpdateTime(bookIndex.getBookId(),new Date()); + + bookIndexDao.save(bookIndex); + bookContentDao.save(bookContent); + return 1; + } + + @Override + public List indexVOList(Query query) { + return bookIndexDao.listVO(query); + } + + @Override + public int indexVOCount(Query query) { + return bookIndexDao.countVO(query); + } + + @Override + public int indexRemove(Long id) { + return bookIndexDao.remove(id); + } + + @Override + public int batchIndexRemove(Long[] ids) { + return bookIndexDao.batchRemove(ids); + } + } diff --git a/novel-admin/src/main/java/com/java2nb/books/util/StringUtil.java b/novel-admin/src/main/java/com/java2nb/books/util/StringUtil.java new file mode 100644 index 0000000..2cb906e --- /dev/null +++ b/novel-admin/src/main/java/com/java2nb/books/util/StringUtil.java @@ -0,0 +1,38 @@ +package com.java2nb.books.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class StringUtil { + private static Pattern linePattern = Pattern.compile("_(\\w)"); + + /** + * 下划线转驼峰 + */ + public static String lineToHump(String str) { + str = str.toLowerCase(); + Matcher matcher = linePattern.matcher(str); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, matcher.group(1).toUpperCase()); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private static Pattern humpPattern = Pattern.compile("[A-Z]"); + + /** + * 驼峰转下划线 + */ + public static String humpToLine(String str) { + Matcher matcher = humpPattern.matcher(str); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase()); + } + matcher.appendTail(sb); + return sb.toString(); + } + +} diff --git a/novel-admin/src/main/java/com/java2nb/books/vo/BookIndexVO.java b/novel-admin/src/main/java/com/java2nb/books/vo/BookIndexVO.java new file mode 100644 index 0000000..6927028 --- /dev/null +++ b/novel-admin/src/main/java/com/java2nb/books/vo/BookIndexVO.java @@ -0,0 +1,12 @@ +package com.java2nb.books.vo; + +import com.java2nb.books.domain.BookIndexDO; +import lombok.Data; + +@Data +public class BookIndexVO extends BookIndexDO { + + private String bookName; + + +} diff --git a/novel-admin/src/main/resources/application.yml b/novel-admin/src/main/resources/application.yml index 284cabb..8f957be 100644 --- a/novel-admin/src/main/resources/application.yml +++ b/novel-admin/src/main/resources/application.yml @@ -35,9 +35,9 @@ spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driverClassName: com.mysql.jdbc.Driver - url: jdbc:mysql://127.0.0.1:3306/books?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai - username: root - password: test123456 + url: jdbc:mysql://47.106.243.172:3306/books_inner?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai + username: books_inner + password: books_inner!8888 #password: initialSize: 1 minIdle: 3 diff --git a/novel-admin/src/main/resources/mybatis/books/BookIndexMapper.xml b/novel-admin/src/main/resources/mybatis/books/BookIndexMapper.xml index 2943320..dc7f968 100644 --- a/novel-admin/src/main/resources/mybatis/books/BookIndexMapper.xml +++ b/novel-admin/src/main/resources/mybatis/books/BookIndexMapper.xml @@ -9,7 +9,7 @@ - + - + + + + + insert into book_index ( @@ -54,9 +91,9 @@ #{indexName} ) - + - update book_index + update book_index `book_id` = #{bookId}, `index_num` = #{indexNum}, @@ -64,13 +101,13 @@ where id = #{id} - + delete from book_index where id = #{value} - + - delete from book_index where id in + delete from book_index where id in #{id} @@ -89,4 +126,8 @@ + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/mybatis/books/BookMapper.xml b/novel-admin/src/main/resources/mybatis/books/BookMapper.xml index 9f3ab83..f0b2878 100644 --- a/novel-admin/src/main/resources/mybatis/books/BookMapper.xml +++ b/novel-admin/src/main/resources/mybatis/books/BookMapper.xml @@ -3,58 +3,68 @@ - select `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag` from book where id = #{value} - + select + `id`,`catId`,`pic_url`,`book_name`,`author`,`book_desc`,`score`,`book_status`,`visit_count`,`update_time`,`soft_cat`,`soft_tag` + from book + + and id = #{id} + + + and catId = #{catid} + + + and catId 8 + + + + and pic_url = #{picUrl} + and book_name = #{bookName} + and author = #{author} + and book_desc = #{bookDesc} + and score = #{score} + and book_status = #{bookStatus} + and visit_count = #{visitCount} + and update_time = #{updateTime} + and soft_cat = #{softCat} + and soft_tag = #{softTag} + order by ${sort} ${order} - + order by id desc - + - - limit #{offset}, #{limit} - - - - - - + + limit #{offset}, #{limit} + + + + + + insert into book ( `id`, @@ -86,34 +96,38 @@ #{softTag} ) - - - update book - - `catId` = #{catid}, - `pic_url` = #{picUrl}, - `book_name` = #{bookName}, - `author` = #{author}, - `book_desc` = #{bookDesc}, - `score` = #{score}, - `book_status` = #{bookStatus}, - `visit_count` = #{visitCount}, - `update_time` = #{updateTime}, - `soft_cat` = #{softCat}, - `soft_tag` = #{softTag} - - where id = #{id} - - - + + + update book + + `catId` = #{catid}, + `pic_url` = #{picUrl}, + `book_name` = #{bookName}, + `author` = #{author}, + `book_desc` = #{bookDesc}, + `score` = #{score}, + `book_status` = #{bookStatus}, + `visit_count` = #{visitCount}, + `update_time` = #{updateTime}, + `soft_cat` = #{softCat}, + `soft_tag` = #{softTag} + + where id = #{id} + + + delete from book where id = #{value} - - - delete from book where id in - - #{id} - - + + + delete from book where id in + + #{id} + + + + + update book set update_time = #{updateTime} where id = #{id} + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/js/appjs/books/book/add.js b/novel-admin/src/main/resources/static/js/appjs/books/book/add.js index e3ed59d..d2fe549 100644 --- a/novel-admin/src/main/resources/static/js/appjs/books/book/add.js +++ b/novel-admin/src/main/resources/static/js/appjs/books/book/add.js @@ -1,49 +1,89 @@ -$().ready(function() { - validateRule(); +$().ready(function () { + validateRule(); }); $.validator.setDefaults({ - submitHandler : function() { - save(); - } + submitHandler: function () { + save(); + } }); + function save() { - $.ajax({ - cache : true, - type : "POST", - url : "/books/book/save", - data : $('#signupForm').serialize(),// 你的formid - async : false, - error : function(request) { - parent.layer.alert("Connection error"); - }, - success : function(data) { - if (data.code == 0) { - parent.layer.msg("操作成功"); - parent.reLoad(); - var index = parent.layer.getFrameIndex(window.name); // 获取窗口索引 - parent.layer.close(index); + $.ajax({ + cache: true, + type: "POST", + url: "/books/book/save", + data: $('#signupForm').serialize(),// 你的formid + async: false, + error: function (request) { + parent.layer.alert("Connection error"); + }, + success: function (data) { + if (data.code == 0) { + parent.layer.msg("操作成功"); + parent.reLoad(); + var index = parent.layer.getFrameIndex(window.name); // 获取窗口索引 + parent.layer.close(index); - } else { - parent.layer.alert(data.msg) - } + } else { + parent.layer.alert(data.msg) + } - } - }); + } + }); } + function validateRule() { - var icon = " "; - $("#signupForm").validate({ - rules : { - name : { - required : true - } - }, - messages : { - name : { - required : icon + "请输入姓名" - } - } - }) + var icon = " "; + $("#signupForm").validate({ + ignore: "", + rules: { + catid: { + required: true + }, + picUrl: { + required: true + }, + bookName: { + required: true + }, + author: { + required: true + }, + bookDesc: { + required: true + }, + score: { + required: true + }, + bookStatus: { + required: true + } + + }, + messages: { + catid: { + required: icon + "请选择分类" + }, + picUrl: { + required: icon + "请选择封面" + }, + bookName: { + required: icon + "请填写书名" + }, + author: { + required: icon + "请填写作者" + }, + bookDesc: { + required: icon + "请填写简介" + }, + score: { + required: icon + "请填写评分" + }, + bookStatus: { + required: icon + "请填写更新状态" + } + } + }) } \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/js/appjs/books/book/book.js b/novel-admin/src/main/resources/static/js/appjs/books/book/book.js index c4fd97f..99411a1 100644 --- a/novel-admin/src/main/resources/static/js/appjs/books/book/book.js +++ b/novel-admin/src/main/resources/static/js/appjs/books/book/book.js @@ -1,214 +1,240 @@ - var prefix = "/books/book" -$(function() { - load(); +$(function () { + load(); }); function load() { - $('#exampleTable') - .bootstrapTable( - { - method : 'get', // 服务器数据的请求方式 get or post - url : prefix + "/list", // 服务器数据的加载地址 - // showRefresh : true, - // showToggle : true, - // showColumns : true, - iconSize : 'outline', - toolbar : '#exampleToolbar', - striped : true, // 设置为true会有隔行变色效果 - dataType : "json", // 服务器返回的数据类型 - pagination : true, // 设置为true会在底部显示分页条 - // queryParamsType : "limit", - // //设置为limit则会发送符合RESTFull格式的参数 - singleSelect : false, // 设置为true将禁止多选 - // contentType : "application/x-www-form-urlencoded", - // //发送到服务器的数据编码类型 - pageSize : 10, // 如果设置了分页,每页数据条数 - pageNumber : 1, // 如果设置了分布,首页页码 - //search : true, // 是否显示搜索框 - showColumns : false, // 是否显示内容下拉框(选择显示的列) - sidePagination : "server", // 设置在哪里进行分页,可选值为"client" 或者 "server" - queryParams : function(params) { - //说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对 - var queryParams = getFormJson("searchForm"); - queryParams.limit = params.limit; - queryParams.offset = params.offset; - return queryParams; - }, - // //请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果 - // queryParamsType = 'limit' ,返回参数必须包含 - // limit, offset, search, sort, order 否则, 需要包含: - // pageSize, pageNumber, searchText, sortName, - // sortOrder. - // 返回false将会终止请求 - responseHandler: function (rs) { + $('#exampleTable') + .bootstrapTable( + { + method: 'get', // 服务器数据的请求方式 get or post + url: prefix + "/list", // 服务器数据的加载地址 + // showRefresh : true, + // showToggle : true, + // showColumns : true, + iconSize: 'outline', + toolbar: '#exampleToolbar', + striped: true, // 设置为true会有隔行变色效果 + dataType: "json", // 服务器返回的数据类型 + pagination: true, // 设置为true会在底部显示分页条 + // queryParamsType : "limit", + // //设置为limit则会发送符合RESTFull格式的参数 + singleSelect: false, // 设置为true将禁止多选 + // contentType : "application/x-www-form-urlencoded", + // //发送到服务器的数据编码类型 + pageSize: 10, // 如果设置了分页,每页数据条数 + pageNumber: 1, // 如果设置了分布,首页页码 + //search : true, // 是否显示搜索框 + showColumns: false, // 是否显示内容下拉框(选择显示的列) + sidePagination: "server", // 设置在哪里进行分页,可选值为"client" 或者 "server" + queryParams: function (params) { + //说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对 + var queryParams = getFormJson("searchForm"); + queryParams.limit = params.limit; + queryParams.offset = params.offset; + queryParams.sort = params.sort; + queryParams.order = params.order; + return queryParams; + }, + // //请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果 + // queryParamsType = 'limit' ,返回参数必须包含 + // limit, offset, search, sort, order 否则, 需要包含: + // pageSize, pageNumber, searchText, sortName, + // sortOrder. + // 返回false将会终止请求 + responseHandler: function (rs) { - if (rs.code == 0) { - return rs.data; - } else { - parent.layer.alert(rs.msg) - return {total: 0, rows: []}; - } - }, - columns : [ - { - checkbox : true - }, - { - field : 'id', - title : '' - }, - { - field : 'catid', - title : '' - }, - { - field : 'picUrl', - title : '' - }, - { - field : 'bookName', - title : '' - }, - { - field : 'author', - title : '' - }, - { - field : 'bookDesc', - title : '' - }, - { - field : 'score', - title : '' - }, - { - field : 'bookStatus', - title : '' - }, - { - field : 'visitCount', - title : '' - }, - { - field : 'updateTime', - title : '' - }, - { - field : 'softCat', - title : '' - }, - { - field : 'softTag', - title : '' - }, - { - title : '操作', - field : 'id', - align : 'center', - formatter : function(value, row, index) { - var d = ' '; - var e = ' '; - var r = ' '; - return d + e + r ; - } - } ] - }); + if (rs.code == 0) { + return rs.data; + } else { + parent.layer.alert(rs.msg) + return {total: 0, rows: []}; + } + }, + columns: [ + { + checkbox: true + }, + { + title: '序号', + formatter: function () { + return arguments[2] + 1; + } + }, + { + field: 'catid', + title: '分类', + formatter: function (value, row, index) { + return formatDict("novel_category",value); + } + }, + { + field: 'picUrl', + title: '封面', + formatter: function (value, row, index) { + return ""; + } + }, + { + field: 'bookName', + title: '书名' + }, + { + field: 'author', + title: '作者' + }, + { + field: 'bookDesc', + width: '300px', + title: '简介' + }, + { + field: 'score', + title: '评分' + , sortable: true + }, + { + field: 'bookStatus', + title: '更新状态' + , sortable: true + }, + { + field: 'visitCount', + title: '访问次数' + , sortable: true + }, + { + field: 'updateTime', + title: '更新时间' + , sortable: true + }, + { + title: '操作', + field: 'id', + align: 'center', + formatter: function (value, row, index) { + var d = '详情
'; + /*var e = '编辑
';*/ + var r = '删除
'; + var p = '章节发布 '; + return d + r + p; + } + }] + }); } + function reLoad() { - $('#exampleTable').bootstrapTable('refresh'); + $('#exampleTable').bootstrapTable('refresh'); } + function add() { - layer.open({ - type : 2, - title : '增加', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/add' // iframe的url - }); + layer.open({ + type: 2, + title: '增加', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/add' // iframe的url + }); } + +function addIndex(bookId) { + layer.open({ + type: 2, + title: '发布章节', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/index/add?bookId='+bookId // iframe的url + }); +} + function detail(id) { - layer.open({ - type : 2, - title : '详情', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/detail/' + id // iframe的url - }); + layer.open({ + type: 2, + title: '详情', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/detail/' + id // iframe的url + }); } + function edit(id) { - layer.open({ - type : 2, - title : '编辑', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/edit/' + id // iframe的url - }); + layer.open({ + type: 2, + title: '编辑', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/edit/' + id // iframe的url + }); } + function remove(id) { - layer.confirm('确定要删除选中的记录?', { - btn : [ '确定', '取消' ] - }, function() { - $.ajax({ - url : prefix+"/remove", - type : "post", - data : { - 'id' : id - }, - success : function(r) { - if (r.code==0) { - layer.msg(r.msg); - reLoad(); - }else{ - layer.msg(r.msg); - } - } - }); - }) + layer.confirm('确定要删除选中的记录?', { + btn: ['确定', '取消'] + }, function () { + $.ajax({ + url: prefix + "/remove", + type: "post", + data: { + 'id': id + }, + success: function (r) { + if (r.code == 0) { + layer.msg(r.msg); + reLoad(); + } else { + layer.msg(r.msg); + } + } + }); + }) } function resetPwd(id) { } -function batchRemove() { - var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行,当没有选择的记录时,返回一个空数组 - if (rows.length == 0) { - layer.msg("请选择要删除的数据"); - return; - } - layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", { - btn : [ '确定', '取消' ] - // 按钮 - }, function() { - var ids = new Array(); - // 遍历所有选择的行数据,取每条数据对应的ID - $.each(rows, function(i, row) { - ids[i] = row['id']; - }); - $.ajax({ - type : 'POST', - data : { - "ids" : ids - }, - url : prefix + '/batchRemove', - success : function(r) { - if (r.code == 0) { - layer.msg(r.msg); - reLoad(); - } else { - layer.msg(r.msg); - } - } - }); - }, function() { - }); +function batchRemove() { + var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行,当没有选择的记录时,返回一个空数组 + if (rows.length == 0) { + layer.msg("请选择要删除的数据"); + return; + } + layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", { + btn: ['确定', '取消'] + // 按钮 + }, function () { + var ids = new Array(); + // 遍历所有选择的行数据,取每条数据对应的ID + $.each(rows, function (i, row) { + ids[i] = row['id']; + }); + $.ajax({ + type: 'POST', + data: { + "ids": ids + }, + url: prefix + '/batchRemove', + success: function (r) { + if (r.code == 0) { + layer.msg(r.msg); + reLoad(); + } else { + layer.msg(r.msg); + } + } + }); + }, function () { + + }); } \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/add.js b/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/add.js index aa72954..4b5ce72 100644 --- a/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/add.js +++ b/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/add.js @@ -1,3 +1,36 @@ +var E = window.wangEditor; +var editor2 = new E('#contentEditor'); +// 自定义菜单配置 +editor2.customConfig.menus = [ + 'head', // 标题 + 'bold', // 粗体 + 'fontSize', // 字号 + 'fontName', // 字体 + 'italic', // 斜体 + 'underline', // 下划线 + 'strikeThrough', // 删除线 + 'foreColor', // 文字颜色 + //'backColor', // 背景颜色 + //'link', // 插入链接 + 'list', // 列表 + 'justify', // 对齐方式 + 'quote', // 引用 + 'emoticon', // 表情 + 'image', // 插入图片 + //'table', // 表格 + //'video', // 插入视频 + //'code', // 插入代码 + 'undo', // 撤销 + 'redo' // 重复 +]; +editor2.customConfig.onchange = function (html) { + // html 即变化之后的内容 + $("#content").val(html); +} +editor2.create(); + + + $().ready(function() { validateRule(); }); @@ -11,7 +44,7 @@ function save() { $.ajax({ cache : true, type : "POST", - url : "/books/bookIndex/save", + url : "/books/book/index/save", data : $('#signupForm').serialize(),// 你的formid async : false, error : function(request) { @@ -33,16 +66,25 @@ function save() { } function validateRule() { + + var icon = " "; $("#signupForm").validate({ + ignore: "", rules : { - name : { + indexName : { + required : true + }, + content : { required : true } }, messages : { - name : { - required : icon + "请输入姓名" + indexName : { + required : icon + "请输入章节名" + }, + content : { + required : icon + "请输入章节内容" } } }) diff --git a/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/bookIndex.js b/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/bookIndex.js index 9b9eba2..6a9ed53 100644 --- a/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/bookIndex.js +++ b/novel-admin/src/main/resources/static/js/appjs/books/bookIndex/bookIndex.js @@ -1,182 +1,185 @@ - -var prefix = "/books/bookIndex" -$(function() { - load(); +var prefix = "/books/book/index" +$(function () { + load(); }); function load() { - $('#exampleTable') - .bootstrapTable( - { - method : 'get', // 服务器数据的请求方式 get or post - url : prefix + "/list", // 服务器数据的加载地址 - // showRefresh : true, - // showToggle : true, - // showColumns : true, - iconSize : 'outline', - toolbar : '#exampleToolbar', - striped : true, // 设置为true会有隔行变色效果 - dataType : "json", // 服务器返回的数据类型 - pagination : true, // 设置为true会在底部显示分页条 - // queryParamsType : "limit", - // //设置为limit则会发送符合RESTFull格式的参数 - singleSelect : false, // 设置为true将禁止多选 - // contentType : "application/x-www-form-urlencoded", - // //发送到服务器的数据编码类型 - pageSize : 10, // 如果设置了分页,每页数据条数 - pageNumber : 1, // 如果设置了分布,首页页码 - //search : true, // 是否显示搜索框 - showColumns : false, // 是否显示内容下拉框(选择显示的列) - sidePagination : "server", // 设置在哪里进行分页,可选值为"client" 或者 "server" - queryParams : function(params) { - //说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对 - var queryParams = getFormJson("searchForm"); - queryParams.limit = params.limit; - queryParams.offset = params.offset; - return queryParams; - }, - // //请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果 - // queryParamsType = 'limit' ,返回参数必须包含 - // limit, offset, search, sort, order 否则, 需要包含: - // pageSize, pageNumber, searchText, sortName, - // sortOrder. - // 返回false将会终止请求 - responseHandler: function (rs) { + $('#exampleTable') + .bootstrapTable( + { + method: 'get', // 服务器数据的请求方式 get or post + url: prefix + "/list", // 服务器数据的加载地址 + // showRefresh : true, + // showToggle : true, + // showColumns : true, + iconSize: 'outline', + toolbar: '#exampleToolbar', + striped: true, // 设置为true会有隔行变色效果 + dataType: "json", // 服务器返回的数据类型 + pagination: true, // 设置为true会在底部显示分页条 + // queryParamsType : "limit", + // //设置为limit则会发送符合RESTFull格式的参数 + singleSelect: false, // 设置为true将禁止多选 + // contentType : "application/x-www-form-urlencoded", + // //发送到服务器的数据编码类型 + pageSize: 10, // 如果设置了分页,每页数据条数 + pageNumber: 1, // 如果设置了分布,首页页码 + //search : true, // 是否显示搜索框 + showColumns: false, // 是否显示内容下拉框(选择显示的列) + sidePagination: "server", // 设置在哪里进行分页,可选值为"client" 或者 "server" + queryParams: function (params) { + //说明:传入后台的参数包括offset开始索引,limit步长,sort排序列,order:desc或者,以及所有列的键值对 + var queryParams = getFormJson("searchForm"); + queryParams.limit = params.limit; + queryParams.offset = params.offset; + return queryParams; + }, + // //请求服务器数据时,你可以通过重写参数的方式添加一些额外的参数,例如 toolbar 中的参数 如果 + // queryParamsType = 'limit' ,返回参数必须包含 + // limit, offset, search, sort, order 否则, 需要包含: + // pageSize, pageNumber, searchText, sortName, + // sortOrder. + // 返回false将会终止请求 + responseHandler: function (rs) { - if (rs.code == 0) { - return rs.data; - } else { - parent.layer.alert(rs.msg) - return {total: 0, rows: []}; - } - }, - columns : [ - { - checkbox : true - }, - { - field : 'id', - title : '' - }, - { - field : 'bookId', - title : '' - }, - { - field : 'indexNum', - title : '' - }, - { - field : 'indexName', - title : '' - }, - { - title : '操作', - field : 'id', - align : 'center', - formatter : function(value, row, index) { - var d = ' '; - var e = ' '; - var r = ' '; - return d + e + r ; - } - } ] - }); + if (rs.code == 0) { + return rs.data; + } else { + parent.layer.alert(rs.msg) + return {total: 0, rows: []}; + } + }, + columns: [ + { + checkbox: true + }, + { + title: '序号', + formatter: function () { + return arguments[2] + 1; + } + }, + { + field: 'bookName', + title: '书名' + }, + { + field: 'indexName', + title: '章节名' + }, + { + title: '操作', + field: 'id', + align: 'center', + formatter: function (value, row, index) { + /*var d = ' '; + var e = ' ';*/ + var r = ' '; + return r; + } + }] + }); } + function reLoad() { - $('#exampleTable').bootstrapTable('refresh'); + $('#exampleTable').bootstrapTable('refresh'); } + function add() { - layer.open({ - type : 2, - title : '增加', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/add' // iframe的url - }); + layer.open({ + type: 2, + title: '增加', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/add' // iframe的url + }); } + function detail(id) { - layer.open({ - type : 2, - title : '详情', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/detail/' + id // iframe的url - }); + layer.open({ + type: 2, + title: '详情', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/detail/' + id // iframe的url + }); } + function edit(id) { - layer.open({ - type : 2, - title : '编辑', - maxmin : true, - shadeClose : false, // 点击遮罩关闭层 - area : [ '800px', '520px' ], - content : prefix + '/edit/' + id // iframe的url - }); + layer.open({ + type: 2, + title: '编辑', + maxmin: true, + shadeClose: false, // 点击遮罩关闭层 + area: ['800px', '520px'], + content: prefix + '/edit/' + id // iframe的url + }); } + function remove(id) { - layer.confirm('确定要删除选中的记录?', { - btn : [ '确定', '取消' ] - }, function() { - $.ajax({ - url : prefix+"/remove", - type : "post", - data : { - 'id' : id - }, - success : function(r) { - if (r.code==0) { - layer.msg(r.msg); - reLoad(); - }else{ - layer.msg(r.msg); - } - } - }); - }) + layer.confirm('确定要删除选中的记录?', { + btn: ['确定', '取消'] + }, function () { + $.ajax({ + url: prefix + "/remove", + type: "post", + data: { + 'id': id + }, + success: function (r) { + if (r.code == 0) { + layer.msg(r.msg); + reLoad(); + } else { + layer.msg(r.msg); + } + } + }); + }) } function resetPwd(id) { } -function batchRemove() { - var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行,当没有选择的记录时,返回一个空数组 - if (rows.length == 0) { - layer.msg("请选择要删除的数据"); - return; - } - layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", { - btn : [ '确定', '取消' ] - // 按钮 - }, function() { - var ids = new Array(); - // 遍历所有选择的行数据,取每条数据对应的ID - $.each(rows, function(i, row) { - ids[i] = row['id']; - }); - $.ajax({ - type : 'POST', - data : { - "ids" : ids - }, - url : prefix + '/batchRemove', - success : function(r) { - if (r.code == 0) { - layer.msg(r.msg); - reLoad(); - } else { - layer.msg(r.msg); - } - } - }); - }, function() { - }); +function batchRemove() { + var rows = $('#exampleTable').bootstrapTable('getSelections'); // 返回所有选择的行,当没有选择的记录时,返回一个空数组 + if (rows.length == 0) { + layer.msg("请选择要删除的数据"); + return; + } + layer.confirm("确认要删除选中的'" + rows.length + "'条数据吗?", { + btn: ['确定', '取消'] + // 按钮 + }, function () { + var ids = new Array(); + // 遍历所有选择的行数据,取每条数据对应的ID + $.each(rows, function (i, row) { + ids[i] = row['id']; + }); + $.ajax({ + type: 'POST', + data: { + "ids": ids + }, + url: prefix + '/batchRemove', + success: function (r) { + if (r.code == 0) { + layer.msg(r.msg); + reLoad(); + } else { + layer.msg(r.msg); + } + } + }); + }, function () { + + }); } \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/js/dict-util.js b/novel-admin/src/main/resources/static/js/dict-util.js new file mode 100644 index 0000000..c7d7940 --- /dev/null +++ b/novel-admin/src/main/resources/static/js/dict-util.js @@ -0,0 +1,52 @@ +var dictList = parent.dictList; +$(function () { + + $(".chosen-select").each(function (index, domEle) { + + var dictType = $(domEle).attr("dict-type"); + var dictValue = $(domEle).attr("dict-value"); + var changeFunc = $(domEle).attr("dict-change-func"); + if (dictType) { + var html = ""; + // 加载数据 + for (var i = 0; i < dictList.length; i++) { + if (dictList[i].type == dictType) { + html += '' + + } + } + $(domEle).append(html); + $(domEle).chosen({ + maxHeight: 200 + }); + $(domEle).val(dictValue); + $(domEle).trigger("chosen:updated"); + // 点击事件 + $(domEle).on('change', function (e, params) { + eval(changeFunc+'()'); + }); + } + + + }); + + +}); + + +function formatDict(dictType, value) { + var name = ""; + // 加载数据 + for (var i = 0; i < dictList.length; i++) { + + if (dictList[i].type == dictType && dictList[i].value == value) { + name = dictList[i].name; + } + } + + return name; + + +} + + diff --git a/novel-admin/src/main/resources/static/wangEditor/.eslintignore b/novel-admin/src/main/resources/static/wangEditor/.eslintignore new file mode 100644 index 0000000..c02d109 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/.eslintignore @@ -0,0 +1,2 @@ +src/js/util/ierange.js +src/js/util/poly-fill.js \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/.eslintrc.json b/novel-admin/src/main/resources/static/wangEditor/.eslintrc.json new file mode 100644 index 0000000..0148b38 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/.eslintrc.json @@ -0,0 +1,38 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "globals": { + "ENV": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "no-console":0, + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single", + { + "allowTemplateLiterals": true + } + ], + "semi": [ + "error", + "never" + ], + "no-unused-vars": 0, + "no-debugger": 0 + } +} \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/.gitattributes b/novel-admin/src/main/resources/static/wangEditor/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/novel-admin/src/main/resources/static/wangEditor/.gitignore b/novel-admin/src/main/resources/static/wangEditor/.gitignore new file mode 100644 index 0000000..481571c --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/.gitignore @@ -0,0 +1,51 @@ + +#忽略 +**/node_modules/* +node_modules/* +npm-debug.log +example/upload-files + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk diff --git a/novel-admin/src/main/resources/static/wangEditor/.npmignore b/novel-admin/src/main/resources/static/wangEditor/.npmignore new file mode 100644 index 0000000..333dd25 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/.npmignore @@ -0,0 +1,5 @@ +node_modules/* +npm-debug.log +docs/* +src/* +example/* \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/ISSUE.md b/novel-admin/src/main/resources/static/wangEditor/ISSUE.md new file mode 100644 index 0000000..796cbee --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/ISSUE.md @@ -0,0 +1,157 @@ +# 问题记录 + +## 版本修复 + +### v3.0.1 + +- [done] 如何设置自动增加高度(补充文档) +- [done] src/js/editor/Bar 改为 Progress,仅供上传图片使用 +- [done] Panel 在右上角增加一个“关闭”按钮 +- [done] 显示页面 table、quote、code 等样式,说明一下 +- [done] 增加自定义上传回调函数,用以自定义返回图片的格式 +- [done] 上传附带的参数,也加入到 form-data 中一份 +- [done] 编辑器默认情况下,菜单栏不能点击,必须focus了编辑器求之后才能点击 +- [done] 点击菜单弹出panel之后,再点击编辑器区域其他地方,panel不消失 +- [done] 自定义filename,v2版本就有 +- [done] ff 中的 bug +- [done] ff 中粘贴图片和文字出现问题 https://github.com/wangfupeng1988/wangEditor/issues/609 +- [done] 火狐浏览器下,创建表格,编辑表格内容时,会出现两个控制点(有人提供了解决方案) +- [done] 配置最多上传的文件个数 +- [done] 连续给两段内容 添加有/无序列表时,样式会出问题,且其他内容找不到了,并且编辑器不处于编辑状态。 +- [done] onchange +- [done] IE11下面一直报错。并且表格无法正常使用 + +### v3.0.2 + +- [done] 用 onchange 完善 vue react 的 demo +- [done] 插入图片之后,光标移动到图片的前面,然后回车,图片消失,并且不能撤销 +- [done] 修复上传图片 customInsert 无效的bug +- [done] 编辑区域 z-index 可配置 +- [done] 上传图片不应该把状态码限制在 200,而是 2xx +- [done] editor.txt.html() 之后,没有定位光标位置 + +### v3.0.3 + +- [done] 粘贴图片在低版本的谷歌浏览器中无法使用,提示验证图片未通过,undefined不是图片。 +- [done] 动态赋值内容,会自动换行,因为给自动加了`


` +- [done] 不选中任何内容,点击“加粗”报错:Failed to execute 'setEnd' on 'Range' +- [done] toolbar 小图标的 z-index 可配置 + +### v3.0.4 + +- [done] 允许使用者通过`replace`实现多语言 +- [done] `_alert()`,可自定义配置提示框 +- [done] 支持用户自定义上传图片的事件,如用户要上传到七牛云、阿里云 + +### v3.0.5 + +- [done] 图片上传中,insertLinkImg 方法中,去掉 img.onload 之后再插入的逻辑吧,这样会打乱多个图片的顺序 +- [done] `` 标签重叠问题,两行文字都是`h2`,然后将第一行选中设置为`h1`,结果是 `

测试1

测试2` +- [done] 补充 ng 集成的示例 https://github.com/wangfupeng1988/wangEditor/issues/859 +- [done] 菜单不能折叠的说明,加入到文档中 +- [done] 上传图片 before 函数中,增加一个判断,可以让用户终止图片的上传 + +### v3.0.6 + +- [done] src/fonts 中的字体文件名改一下,用 icomoon 容易发生冲突 +- [done] 将禁用编辑器的操作完善到文档中 https://www.kancloud.cn/wangfupeng/wangeditor3/368562 +- [done] 开放表格中的粘贴功能(之前因不明问题而封闭) +- [done] 代码块中,光标定位到最后位置时,连续两次回车要跳出代码块 + +### v3.0.7 + +- [done] 紧急修复上一个版本导致的菜单图标不显示的 bug + +### v3.0.8 + +- [done] 修复 backColor 和 foreColor 代码文件名混淆的问题 +- [done] 修改 IE 中 “引用” 的功能 +- [done] 增加粘贴过滤样式的可配置 +- [done] 修复 IE 粘贴文字的问题 + +### v3.0.9 + +- [done] config 中,上传图片的 token 注视掉 +- [done] 将一些常见 API 开放,写到文档中 https://www.kancloud.cn/wangfupeng/wangeditor3/404586 +- [done] IE 火狐 插入多行代码有问题 +- [done] 粘贴时,在`

`中,不能只粘贴纯文本,还得要图片 +- [done] 粘贴内容中,过滤掉``注释 +- [done] **支持上传七牛云存储** + +### v3.0.10 + +- [done] 支持插入网络图片的回调函数 +- [done] 插入链接时候的格式校验 +- [done] 支持拖拽上传 + +### v3.0.11 + +- [done] 如何用 textarea 创建编辑器,完善到文档中,许多人提问 +- [done] 修复`editor.customConfig.customUploadImg`不触发的 bug +- [done] 修复有序列表和无序列表切换时 onchange 不触发的 bug + +### v3.0.12 + +- [done] 增加 onfocus 和 onblur (感谢 [hold-baby](https://github.com/hold-baby) 提交的 [PR](https://github.com/wangfupeng1988/wangEditor/pull/1076)) +- [done] 上传的自定义参数`editor.customConfig.uploadImgParams`是否拼接到 url 中,支持可配置 +- [done] onchange 触发的延迟时间,支持可配置 + +### v3.0.13 + +- [done] 修复图片 选中/取消选中 时,触发 onchange 的问题 +- [done] 修复只通过 length 判断 onchange 是否触发的问题 +- [done] 增加插入网络图片的校验函数 +- [done] 增加自定义处理粘贴文本的事件 +- [done] 修复选中一个图片时点击删除键会误删除其他内容的 bug +- [done] 修复 window chrome 中“复制图片”然后粘贴图片,会粘贴为两张的 bug +- [done] 修复无法撤销“引用”的问题 + +### v3.0.14 + +- [done] 可以配置前景色、背景色 +- [done] 回车时无法从`

....

`中跳出 +- [done] 增加获取 JSON 格式内容的 API + +### v3.0.15 + +- [done] 表情兼容图片和 emoji ,都可自定义配置 + +### v3.0.16 + +- [done] 修复粘贴图片的 bug +- [done] 修复`pasteTextHandle`执行两次的问题 +- [done] 修复插入链接时,文字和链接为空时,`linkCheck`不执行的 bug +- [done] 粘贴 html 时,过滤掉其中的`data-xxx`属性 +- [done] 修复中文输入法输入过程中出发 onchange 的问题,感谢 [github.com/CongAn](https://github.com/CongAn) PR +- [done] `editor.txt.html`和`editor.txt.text`中,替换`​`字符为空字符串 +- [done] 精确图片大小计算,将`maxSize / 1000 / 1000`改为`maxSize / 1024 / 1024` +- [done] 修复 droplist 类型菜单(颜色、背景色等)点击不准确的问题 + +### v3.0.17 + +- [done] 合并 pr [菜单和编辑区域分离 onfocus onblur 失效bug](https://github.com/wangfupeng1988/wangEditor/pull/1174) ,感谢 [hold-baby](https://github.com/hold-baby) 提供 pr +- [done] 使用`document.execCommand("styleWithCSS", null, true)`,这样设置字体颜色就会用 css 而不是用`` + + +### 近期计划解决 + +- 撤销的兼容性问题(会误伤其他编辑器或者 input textarea 等),考虑用 onchange 记录 undo 和 redo 的内容(但是得考虑直接修改 dom 的情况,如 quote code img list table 菜单) + - 列表撤销会删除一行?https://github.com/wangfupeng1988/wangEditor/issues/1131 + - 页面中有 input 等输入标签时,undo redo 会误伤 https://github.com/wangfupeng1988/wangEditor/issues/1024 + - 两个编辑器 undo 的问题 https://github.com/wangfupeng1988/wangEditor/issues/1010 + - list undo redo 有问题。选中几行,先设置有序列表,再设置无序列表,然后撤销,就能复现问题 +- 粘贴文字的样式问题(可暂时配置 `pasteTextHandle` 自行处理) + - 先输入文字,再粘贴 excel 表格,样式丢失 https://github.com/wangfupeng1988/wangEditor/issues/1000 + - IE 11 直接输入文字会空一行在第二行出现内容 https://github.com/wangfupeng1988/wangEditor/issues/919 + - windows 下 word excel 的粘贴,存在垃圾数据 + +## 待排期 + +- 调研 safari、IE 和ff中粘贴图片 https://github.com/wangfupeng1988/wangEditor/issues/831 +- 图片调整大小,表格调整的方式,是否用 toolbar 的方式? +- 删除掉`./release`之后,执行`npm run release`会报错,原因是`fonts`文件没拷贝全,就要去替换`css`中的字体文件为`base64`格式,导致找不到文件。 +- 先点击'B'再输入内容这种形式,前期先支持 webkit 和 IE,火狐的支持后面再加上 +- 图片压缩 canvas https://github.com/think2011/localResizeIMG +- github 徽章 https://github.com/EyreFree/GitHubBadgeIntroduction +- 将代码在进行拆分,做到“每个程序只做一件事”,不要出现过长的代码文件。例如 `src/js/command/index.js` 和 `src/js/selection/index.js` + diff --git a/novel-admin/src/main/resources/static/wangEditor/LICENSE b/novel-admin/src/main/resources/static/wangEditor/LICENSE new file mode 100644 index 0000000..5239660 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 王福朋 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/novel-admin/src/main/resources/static/wangEditor/README.md b/novel-admin/src/main/resources/static/wangEditor/README.md new file mode 100644 index 0000000..0ab9445 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/README.md @@ -0,0 +1,70 @@ + +# wangEditor + +## 介绍 + +**wangEditor** —— 轻量级 web 富文本编辑器,配置方便,使用简单。支持 IE10+ 浏览器。 + +- 官网:[www.wangEditor.com](http://www.wangeditor.com/) +- 文档:[www.kancloud.cn/wangfupeng/wangeditor3/332599](http://www.kancloud.cn/wangfupeng/wangeditor3/332599) +- 源码:[github.com/wangfupeng1988/wangEditor](https://github.com/wangfupeng1988/wangEditor) (欢迎 star) + +![图片](http://images2015.cnblogs.com/blog/138012/201705/138012-20170530202905633-1840158981.png) + +*查看 v2 版本的代码和文档点击[这里](https://github.com/wangfupeng1988/wangEditor/tree/v2)* + + +## 下载 + +- 直接下载:[https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases) +- 使用`npm`下载:`npm install wangeditor` (注意 `wangeditor` 全部是**小写字母**) +- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`) +- 使用CDN:[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js) + + +## 使用 + +```javascript +var E = window.wangEditor +var editor = new E('#div1') +editor.create() +``` + + +## 运行 demo + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 安装或者升级最新版本 node(最低`v6.x.x`) +- 进入目录,安装依赖包 `cd wangEditor && npm i` +- 安装包完成之后,windows 用户运行`npm run win-example`,Mac 用户运行`npm run example` +- 打开浏览器访问[localhost:3000/index.html](http://localhost:3000/index.html) +- 用于 React、vue 或者 angular 可查阅[文档](http://www.kancloud.cn/wangfupeng/wangeditor3/332599)中[其他](https://www.kancloud.cn/wangfupeng/wangeditor3/335783)章节中的相关介绍 + +## 交流 + +### QQ 群 + +以下 QQ 群欢迎加入交流问题(可能有些群已经满员) + +- 164999061 +- 281268320 + +### 提问 + +注意,作者只受理以下几种提问方式,其他方式直接忽略 + +- 直接在 [github issues](https://github.com/wangfupeng1988/wangEditor/issues) 提交问题 +- 去[知乎](https://www.zhihu.com/)提问,并邀请[作者](https://www.zhihu.com/people/wang-fu-peng-54/activities)来回答 +- 去[segmentfault](https://segmentfault.com)提问,并邀请[作者](https://segmentfault.com/u/wangfupeng1988)来回答 + +每次升级版本修复的问题记录在[这里](./ISSUE.md) + +## 关于作者 + +- 关注作者的博客 - 《[深入理解javascript原型和闭包系列](http://www.cnblogs.com/wangfupeng1988/p/4001284.html)》《[深入理解javascript异步系列](https://github.com/wangfupeng1988/js-async-tutorial)》《[CSS知多少](http://www.cnblogs.com/wangfupeng1988/p/4325007.html)》 +- 学习作者的教程 - 《[前端JS基础面试题](http://coding.imooc.com/class/115.html)》《[React.js模拟大众点评webapp](http://coding.imooc.com/class/99.html)》《[zepto设计与源码分析](http://www.imooc.com/learn/745)》《[用grunt搭建自动化的web前端开发环境](http://study.163.com/course/courseMain.htm?courseId=1103003)》《[json2.js源码解读](http://study.163.com/course/courseMain.htm?courseId=691008)》 + +如果你感觉有收获,欢迎给我打赏 ———— 以激励我更多输出优质开源内容 + +![图片](https://camo.githubusercontent.com/e1558b631931e0a1606c769a61f48770cc0ccb56/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730322f3133383031322d32303137303232383131323233373739382d313530373139363634332e706e67) + diff --git a/novel-admin/src/main/resources/static/wangEditor/bower.json b/novel-admin/src/main/resources/static/wangEditor/bower.json new file mode 100644 index 0000000..0c4ed0d --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/bower.json @@ -0,0 +1,20 @@ +{ + "name": "wangEditor", + "description": "wangEditor - 基于javascript和css开发的 web 富文本编辑器, 轻量、简洁、易用、开源免费", + "main": "release/wangEditor.js", + "authors": [ + "wangfupeng " + ], + "license": "MIT", + "keywords": [ + "wangEditor", + "web 富文本编辑器" + ], + "homepage": "https://github.com/wangfupeng1988/wangEditor", + "moduleType": [ + "amd", + "cmd", + "node" + ], + "private": true +} diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/dev/README.md b/novel-admin/src/main/resources/static/wangEditor/docs/dev/README.md new file mode 100644 index 0000000..473dbcb --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/dev/README.md @@ -0,0 +1,25 @@ +面向开发者的文档 + + +框架介绍 + +- 下载和运行 +- 目录结构介绍 +- `example`目录 +- `src`目录(`js`目录,`less`目录) +- `package.json` +- `gulpfile.js` + +如何提交 PR + + + +上线 + +- 修改`package.json`版本 +- 提交到github,并创建tag +- 提交到 npm +- 更新 .md 文档 +- 文档同步到 kancloud +- …… + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/01-demo.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/01-demo.md new file mode 100644 index 0000000..b71612f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/01-demo.md @@ -0,0 +1,41 @@ +# 简单的 demo + +## 下载 + +- 点击 [https://github.com/wangfupeng1988/wangEditor/releases](https://github.com/wangfupeng1988/wangEditor/releases) 下载最新版。进入`release`文件夹下找到`wangEditor.js`或者`wangEditor.min.js`即可 +- 使用CDN:[//unpkg.com/wangeditor/release/wangEditor.min.js](https://unpkg.com/wangeditor/release/wangEditor.min.js) +- 使用`bower`下载:`bower install wangEditor` (前提保证电脑已安装了`bower`) + +*PS:支持`npm`安装,请参见后面的章节* + +## 制作 demo + +编辑器效果如下。 + +![图片](https://camo.githubusercontent.com/f3d072718d8fcbbacf8cc80465a34cceffcf5b4a/687474703a2f2f696d61676573323031352e636e626c6f67732e636f6d2f626c6f672f3133383031322f3230313730352f3133383031322d32303137303533303230323930353633332d313834303135383938312e706e67) + +代码示例如下。**注意,以下代码中无需引用任何 CSS 文件!!!** + +```html + + + + + wangEditor demo + + +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + + +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/02-use-module.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/02-use-module.md new file mode 100644 index 0000000..0356a68 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/02-use-module.md @@ -0,0 +1,49 @@ +# 使用模块定义 + +wangEditor 除了直接使用` + + +``` + +## CommonJS + +可以使用`npm install wangeditor`安装(注意,这里`wangeditor`全是**小写字母**) + +```javascript +// 引用 +var E = require('wangeditor') // 使用 npm 安装 +var E = require('/wangEditor.min.js') // 使用下载的源码 + +// 创建编辑器 +var editor = new E('#editor') +editor.create() +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/03-sperate.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/03-sperate.md new file mode 100644 index 0000000..0fcd276 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/03-sperate.md @@ -0,0 +1,48 @@ +# 菜单和编辑区域分离 + +如果你想要像 知乎专栏、简书、石墨、网易云笔记 这些编辑页面一样,将编辑区域和菜单分离,也可以实现。 + +这样,菜单和编辑器区域就是使用者可自己控制的元素,可自定义样式。例如:将菜单`fixed`、编辑器区域高度自动增加等 + +## 代码示例 + +```html + + + + + wangEditor 菜单和编辑器区域分离 + + + +
+
+
中间隔离带
+
+

请输入内容

+
+ + + + + +``` + +## 显示效果 + +从上面代码可以看出,菜单和编辑区域其实就是两个单独的`
`,位置、尺寸都可以随便定义。 + +![](http://images2015.cnblogs.com/blog/138012/201705/138012-20170531224756289-7442240.png) + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/04-multi.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/04-multi.md new file mode 100644 index 0000000..aee3540 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/01-getstart/04-multi.md @@ -0,0 +1,50 @@ +# 同一个页面创建多个编辑器 + +wangEditor 支持一个页面创建多个编辑器 + +## 代码示例 + +```html + + + + + wangEditor 一个页面多个编辑器 + + + +
+
+
中间隔离带
+
+

第一个 demo(菜单和编辑器区域分开)

+
+ +
+

第二个 demo(常规)

+
+ + + + + + +``` + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/01-set-content.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/01-set-content.md new file mode 100644 index 0000000..7631f6f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/01-set-content.md @@ -0,0 +1,46 @@ +# 设置内容 + +以下方式中,如果条件允许,尽量使用第一种方式,效率最高。 + +## html 初始化内容 + +直接将内容写到要创建编辑器的`
`标签中 + +```html +
+

初始化的内容

+

初始化的内容

+
+ + + +``` + +## js 设置内容 + +创建编辑器之后,使用`editor.txt.html(...)`设置编辑器内容 + +```html +
+
+ + + +``` + +## 追加内容 + +创建编辑器之后,可使用`editor.txt.append('

追加的内容

')`继续追加内容。 + +## 清空内容 + +可使用`editor.txt.clear()`清空编辑器内容 diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/02-get-content.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/02-get-content.md new file mode 100644 index 0000000..e21c277 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/02-get-content.md @@ -0,0 +1,80 @@ +# 读取内容 + +可以`html`和`text`的方式读取编辑器的内容。 + +```html +
+

欢迎使用 wangEditor 编辑器

+
+ + + + + +``` + +需要注意的是:**从编辑器中获取的 html 代码是不包含任何样式的纯 html**,如果显示的时候需要对其中的`` `` `
`等标签进行自定义样式(这样既可实现多皮肤功能),下面提供了编辑器中使用的样式供参考 + +```css +/* table 样式 */ +table { + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; +} +table td, +table th { + border-bottom: 1px solid #ccc; + border-right: 1px solid #ccc; + padding: 3px 5px; +} +table th { + border-bottom: 2px solid #ccc; + text-align: center; +} + +/* blockquote 样式 */ +blockquote { + display: block; + border-left: 8px solid #d0e5f2; + padding: 5px 10px; + margin: 10px 0; + line-height: 1.4; + font-size: 100%; + background-color: #f1f1f1; +} + +/* code 样式 */ +code { + display: inline-block; + *display: inline; + *zoom: 1; + background-color: #f1f1f1; + border-radius: 3px; + padding: 3px 5px; + margin: 0 3px; +} +pre code { + display: block; +} + +/* ul ol 样式 */ +ul, ol { + margin: 10px 0 10px 20px; +} +``` + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/03-use-textarea.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/03-use-textarea.md new file mode 100644 index 0000000..1707e13 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/03-use-textarea.md @@ -0,0 +1,25 @@ +# 使用 textarea + +wangEditor 从`v3`版本开始不支持 textarea ,但是可以通过`onchange`来实现 textarea 中提交富文本内容。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/04-get-json.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/04-get-json.md new file mode 100644 index 0000000..d623ac4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/02-content/04-get-json.md @@ -0,0 +1,82 @@ +# 获取 JSON 格式的内容 + +可以通过`editor.txt.getJSON`获取 JSON 格式的编辑器的内容,`v3.0.14`开始支持,示例如下 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+ +
+ + + + +``` + + +----- + +如果编辑器区域的 html 内容是如下: + +```html +

欢迎使用 wangEditor 富文本编辑器

+ +``` + +那么获取的 JSON 格式就如下: + +```json +[ + { + "tag": "p", + "attrs": [], + "children": [ + "欢迎使用 ", + { + "tag": "b", + "attrs": [], + "children": [ + "wangEditor" + ] + }, + " 富文本编辑器" + ] + }, + { + "tag": "img", + "attrs": [ + { + "name": "src", + "value": "https://ss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/img/logo_top_ca79a146.png" + }, + { + "name": "style", + "value": "max-width:100%;" + } + ], + "children": [] + }, + { + "tag": "p", + "attrs": [], + "children": [ + { + "tag": "br", + "attrs": [], + "children": [] + } + ] + } +] +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/01-menu.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/01-menu.md new file mode 100644 index 0000000..bce6ba7 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/01-menu.md @@ -0,0 +1,52 @@ +# 自定义菜单 + +编辑器创建之前,可使用`editor.customConfig.menus`定义显示哪些菜单和菜单的顺序。**注意:v3 版本的菜单不支持换行折叠了(因为换行之后菜单栏是在太难看),如果菜单栏宽度不够,建议精简菜单项。** + +## 代码示例 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +## 默认菜单 + +编辑默认的菜单配置如下 + +```javascript +[ + 'head', // 标题 + 'bold', // 粗体 + 'italic', // 斜体 + 'underline', // 下划线 + 'strikeThrough', // 删除线 + 'foreColor', // 文字颜色 + 'backColor', // 背景颜色 + 'link', // 插入链接 + 'list', // 列表 + 'justify', // 对齐方式 + 'quote', // 引用 + 'emoticon', // 表情 + 'image', // 插入图片 + 'table', // 表格 + 'video', // 插入视频 + 'code', // 插入代码 + 'undo', // 撤销 + 'redo' // 重复 +] +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/02-debug.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/02-debug.md new file mode 100644 index 0000000..e94d7a4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/02-debug.md @@ -0,0 +1,21 @@ +# 定义 debug 模式 + +可通过`editor.customConfig.debug = true`配置`debug`模式,`debug`模式下,有 JS 错误会以`throw Error`方式提示出来。默认值为`false`,即不会抛出异常。 + +但是,在实际开发中不建议直接定义为`true`或者`false`,可通过 url 参数进行干预,示例如下: + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/03-onchange.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/03-onchange.md new file mode 100644 index 0000000..296091c --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/03-onchange.md @@ -0,0 +1,40 @@ +# 配置 onchange 函数 + +配置`onchange`函数之后,用户操作(鼠标点击、键盘打字等)导致的内容变化之后,会自动触发`onchange`函数执行。 + +但是,**用户自己使用 JS 修改了`div1`的`innerHTML`,不会自动触发`onchange`函数**,此时你可以通过执行`editor.change()`来手动触发`onchange`函数的执行。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

手动触发 onchange 函数执行

+ + + + +``` + +----- + +另外,如果需要修改 onchange 触发的延迟时间(onchange 会在用户无任何操作的 xxx 毫秒之后被触发),可通过如下配置 + +```js +// 自定义 onchange 触发的延迟时间,默认为 200 ms +editor.customConfig.onchangeTimeout = 1000 // 单位 ms +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/04-z-index.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/04-z-index.md new file mode 100644 index 0000000..129bf1c --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/04-z-index.md @@ -0,0 +1,19 @@ +# 配置编辑区域的 z-index + +编辑区域的`z-index`默认为`10000`,可自定义修改,代码配置如下。需改之后,编辑区域和菜单的`z-index`会同时生效。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/05-lang.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/05-lang.md new file mode 100644 index 0000000..01900fe --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/05-lang.md @@ -0,0 +1,30 @@ +# 多语言 + +可以通过`lang`配置项配置多语言,其实就是通过该配置项中的配置,将编辑器显示的文字,替换成你需要的文字。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +**注意,以上代码中的`链接文字`要写在`链接`前面,`上传图片`要写在`上传`前面,因为前者包含后者。如果不这样做,可能会出现替换不全的问题,切记切记!** diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/06-paste.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/06-paste.md new file mode 100644 index 0000000..a7126c8 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/06-paste.md @@ -0,0 +1,33 @@ +# 粘贴文本 + +**注意,以下配置暂时对 IE 无效。IE 暂时使用系统自带的粘贴功能,没有样式过滤!** + +## 关闭粘贴样式的过滤 + +当从其他网页复制文本内容粘贴到编辑器中,编辑器会默认过滤掉复制文本中自带的样式,目的是让粘贴后的文本变得更加简洁和轻量。用户可通过`editor.customConfig.pasteFilterStyle = false`手动关闭掉粘贴样式的过滤。 + +## 自定义处理粘贴的文本内容 + +使用者可通过`editor.customConfig.pasteTextHandle`对粘贴的文本内容进行自定义的过滤、处理等操作,然后返回处理之后的文本内容。编辑器最终会粘贴用户处理之后并且返回的的内容。 + +## 示例代码 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/07-linkImgCallback.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/07-linkImgCallback.md new file mode 100644 index 0000000..52169e8 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/07-linkImgCallback.md @@ -0,0 +1,12 @@ +# 插入网络图片的回调 + +插入网络图片时,可通过如下配置获取到图片的信息。`v3.0.10`开始支持。 + +```js +var E = window.wangEditor +var editor = new E('#div1') +editor.customConfig.linkImgCallback = function (url) { + console.log(url) // url 即插入图片的地址 +} +editor.create() +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/08-linkCheck.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/08-linkCheck.md new file mode 100644 index 0000000..b581438 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/08-linkCheck.md @@ -0,0 +1,16 @@ +# 插入链接的校验 + +插入链接时,可通过如下配置对文字和链接进行校验。`v3.0.10`开始支持。 + +```js +var E = window.wangEditor +var editor = new E('#div1') +editor.customConfig.linkCheck = function (text, link) { + console.log(text) // 插入的文字 + console.log(link) // 插入的链接 + + return true // 返回 true 表示校验成功 + // return '验证失败' // 返回字符串,即校验失败的提示信息 +} +editor.create() +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/09-onfocus.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/09-onfocus.md new file mode 100644 index 0000000..7caba6b --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/09-onfocus.md @@ -0,0 +1,19 @@ +# 配置 onfocus 函数 + +配置`onfocus`函数之后,用户点击富文本区域会触发`onfocus`函数执行。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/10-onblur.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/10-onblur.md new file mode 100644 index 0000000..f7544bc --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/10-onblur.md @@ -0,0 +1,20 @@ +# 配置 onblur 函数 + +配置`onblur`函数之后,如果当前有手动获取焦点的富文本并且鼠标点击富文本以外的区域,则会触发`onblur`函数执行。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/11-linkImgCheck.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/11-linkImgCheck.md new file mode 100644 index 0000000..efb3320 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/11-linkImgCheck.md @@ -0,0 +1,15 @@ +# 插入网络图片的校验 + +插入网络图片时,可对图片地址做自定义校验。`v3.0.13`开始支持。 + +```js +var E = window.wangEditor +var editor = new E('#div1') +editor.customConfig.linkImgCheck = function (src) { + console.log(src) // 图片的链接 + + return true // 返回 true 表示校验成功 + // return '验证失败' // 返回字符串,即校验失败的提示信息 +} +editor.create() +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/12-colors.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/12-colors.md new file mode 100644 index 0000000..e86e57d --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/12-colors.md @@ -0,0 +1,29 @@ +# 配置字体颜色、背景色 + +编辑器的字体颜色和背景色,可以通过`editor.customConfig.colors`自定义配置 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/13-emot.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/13-emot.md new file mode 100644 index 0000000..5363834 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/03-config/13-emot.md @@ -0,0 +1,48 @@ +# 配置表情 + +`v3.0.15`开始支持配置表情,支持图片格式和 emoji ,可通过`editor.customConfig.emotions`配置。**注意看代码示例中的注释:** + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +温馨提示:需要表情图片可以去 https://api.weibo.com/2/emotions.json?source=1362404091 和 http://yuncode.net/code/c_524ba520e58ce30 逛一逛,或者自己搜索。 diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/01-show-tab.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/01-show-tab.md new file mode 100644 index 0000000..8261950 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/01-show-tab.md @@ -0,0 +1,52 @@ +# 隐藏/显示 tab + +## 显示“上传图片”tab + +默认情况下,编辑器不会显示“上传图片”的tab,因为你还没有配置上传图片的信息。 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204308039-691571074.png) + +参考一下示例显示“上传图片”tab + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +显示效果 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204504524-830243744.png) + +## 隐藏“网络图片”tab + +默认情况下,“网络图片”tab是一直存在的。如果不需要,可以参考一下示例来隐藏它。 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/02-base64.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/02-base64.md new file mode 100644 index 0000000..3a2d71a --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/02-base64.md @@ -0,0 +1,23 @@ +# 使用 base64 保存图片 + +如果需要使用 base64 编码直接将图片插入到内容中,可参考一下示例配置 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +示例效果如下 + +![](http://images2015.cnblogs.com/blog/138012/201706/138012-20170601204759258-1412289899.png) + + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/03-upload-config.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/03-upload-config.md new file mode 100644 index 0000000..6720ce6 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/03-upload-config.md @@ -0,0 +1,188 @@ +# 上传图片 & 配置 + +将图片上传到服务器上的配置方式 + +## 上传图片 + +参考如下代码 + +```html +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + +``` + +其中`/upload`是上传图片的服务器端接口,接口返回的**数据格式**如下(**实际返回数据时,不要加任何注释!!!**) + +```json +{ + // errno 即错误代码,0 表示没有错误。 + // 如果有错误,errno != 0,可通过下文中的监听函数 fail 拿到该错误码进行自定义处理 + "errno": 0, + + // data 是一个数组,返回若干图片的线上地址 + "data": [ + "图片1地址", + "图片2地址", + "……" + ] +} +``` + +## 限制图片大小 + +默认限制图片大小是 5M + +```javascript +// 将图片大小限制为 3M +editor.customConfig.uploadImgMaxSize = 3 * 1024 * 1024 +``` + +## 限制一次最多能传几张图片 + +默认为 10000 张(即不限制),需要限制可自己配置 + +```javascript +// 限制一次最多上传 5 张图片 +editor.customConfig.uploadImgMaxLength = 5 +``` + +## 自定义上传参数 + +上传图片时可自定义传递一些参数,例如传递验证的`token`等。参数会被添加到`formdata`中。 + +```javascript +editor.customConfig.uploadImgParams = { + token: 'abcdef12345' // 属性值会自动进行 encode ,此处无需 encode +} +``` + +如果**还需要**将参数拼接到 url 中,可再加上如下配置 + +``` +editor.customConfig.uploadImgParamsWithUrl = true +``` + +## 自定义 fileName + +上传图片时,可自定义`filename`,即在使用`formdata.append(name, file)`添加图片文件时,自定义第一个参数。 + +```javascript +editor.customConfig.uploadFileName = 'yourFileName' +``` + +## 自定义 header + +上传图片时刻自定义设置 header + +```javascript +editor.customConfig.uploadImgHeaders = { + 'Accept': 'text/x-json' +} +``` + +## withCredentials(跨域传递 cookie) + +跨域上传中如果需要传递 cookie 需设置 withCredentials + +```javascript +editor.customConfig.withCredentials = true +``` + +## 自定义 timeout 时间 + +默认的 timeout 时间是 10 秒钟 + +```javascript +// 将 timeout 时间改为 3s +editor.customConfig.uploadImgTimeout = 3000 +``` + +## 监听函数 + +可使用监听函数在上传图片的不同阶段做相应处理 + +```javascript +editor.customConfig.uploadImgHooks = { + before: function (xhr, editor, files) { + // 图片上传之前触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,files 是选择的图片文件 + + // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 + // return { + // prevent: true, + // msg: '放弃上传' + // } + }, + success: function (xhr, editor, result) { + // 图片上传并返回结果,图片插入成功之后触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 + }, + fail: function (xhr, editor, result) { + // 图片上传并返回结果,但图片插入错误时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象,result 是服务器端返回的结果 + }, + error: function (xhr, editor) { + // 图片上传出错时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 + }, + timeout: function (xhr, editor) { + // 图片上传超时时触发 + // xhr 是 XMLHttpRequst 对象,editor 是编辑器对象 + }, + + // 如果服务器端返回的不是 {errno:0, data: [...]} 这种格式,可使用该配置 + // (但是,服务器端返回的必须是一个 JSON 格式字符串!!!否则会报错) + customInsert: function (insertImg, result, editor) { + // 图片上传并返回结果,自定义插入图片的事件(而不是编辑器自动插入图片!!!) + // insertImg 是插入图片的函数,editor 是编辑器对象,result 是服务器端返回的结果 + + // 举例:假如上传图片成功后,服务器端返回的是 {url:'....'} 这种格式,即可这样插入图片: + var url = result.url + insertImg(url) + + // result 必须是一个 JSON 格式字符串!!!否则报错 + } +} +``` + +## 自定义提示方法 + +上传图片的错误提示默认使用`alert`弹出,你也可以自定义用户体验更好的提示方式 + +```javascript +editor.customConfig.customAlert = function (info) { + // info 是需要提示的内容 + alert('自定义提示:' + info) +} +``` + +## 自定义上传图片事件 + +如果想完全自己控制图片上传的过程,可以使用如下代码 + +```javascript +editor.customConfig.customUploadImg = function (files, insert) { + // files 是 input 中选中的文件列表 + // insert 是获取图片 url 后,插入到编辑器的方法 + + // 上传代码返回结果之后,将图片插入到编辑器中 + insert(imgUrl) +} +``` diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/04-qiniu.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/04-qiniu.md new file mode 100644 index 0000000..e5c2ca4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/04-uploadimg/04-qiniu.md @@ -0,0 +1,115 @@ +# 上传到七牛云存储 + +完整的 demo 请参见 https://github.com/wangfupeng1988/js-sdk ,可下载下来本地运行 demo + +> 注意:配置了上传七牛云存储之后,**无法再使用插入网络图片** + +核心代码如下: + +```js +var E = window.wangEditor +var editor = new E('#div1') +// 允许上传到七牛云存储 +editor.customConfig.qiniu = true +editor.create() + +// 初始化七牛上传 +uploadInit() + +// 初始化七牛上传的方法 +function uploadInit() { + // 获取相关 DOM 节点的 ID + var btnId = editor.imgMenuId; + var containerId = editor.toolbarElemId; + var textElemId = editor.textElemId; + + // 创建上传对象 + var uploader = Qiniu.uploader({ + runtimes: 'html5,flash,html4', //上传模式,依次退化 + browse_button: btnId, //上传选择的点选按钮,**必需** + uptoken_url: '/uptoken', + //Ajax请求upToken的Url,**强烈建议设置**(服务端提供) + // uptoken : '', + //若未指定uptoken_url,则必须指定 uptoken ,uptoken由其他程序生成 + // unique_names: true, + // 默认 false,key为文件名。若开启该选项,SDK会为每个文件自动生成key(文件名) + // save_key: true, + // 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`,则开启,SDK在前端将不对key进行任何处理 + domain: 'http://7xrjl5.com1.z0.glb.clouddn.com/', + //bucket 域名,下载资源时用到,**必需** + container: containerId, //上传区域DOM ID,默认是browser_button的父元素, + max_file_size: '100mb', //最大文件体积限制 + flash_swf_url: '../js/plupload/Moxie.swf', //引入flash,相对路径 + filters: { + mime_types: [ + //只允许上传图片文件 (注意,extensions中,逗号后面不要加空格) + { title: "图片文件", extensions: "jpg,gif,png,bmp" } + ] + }, + max_retries: 3, //上传失败最大重试次数 + dragdrop: true, //开启可拖曳上传 + drop_element: textElemId, //拖曳上传区域元素的ID,拖曳文件或文件夹后可触发上传 + chunk_size: '4mb', //分块上传时,每片的体积 + auto_start: true, //选择文件后自动上传,若关闭需要自己绑定事件触发上传 + init: { + 'FilesAdded': function(up, files) { + plupload.each(files, function(file) { + // 文件添加进队列后,处理相关的事情 + printLog('on FilesAdded'); + }); + }, + 'BeforeUpload': function(up, file) { + // 每个文件上传前,处理相关的事情 + printLog('on BeforeUpload'); + }, + 'UploadProgress': function(up, file) { + // 显示进度 + printLog('进度 ' + file.percent) + }, + 'FileUploaded': function(up, file, info) { + // 每个文件上传成功后,处理相关的事情 + // 其中 info 是文件上传成功后,服务端返回的json,形式如 + // { + // "hash": "Fh8xVqod2MQ1mocfI4S4KpRL6D98", + // "key": "gogopher.jpg" + // } + printLog(info); + // 参考http://developer.qiniu.com/docs/v6/api/overview/up/response/simple-response.html + + var domain = up.getOption('domain'); + var res = $.parseJSON(info); + var sourceLink = domain + res.key; //获取上传成功后的文件的Url + + printLog(sourceLink); + + // 插入图片到editor + editor.cmd.do('insertHtml', '') + }, + 'Error': function(up, err, errTip) { + //上传出错时,处理相关的事情 + printLog('on Error'); + }, + 'UploadComplete': function() { + //队列文件处理完毕后,处理相关的事情 + printLog('on UploadComplete'); + } + // Key 函数如果有需要自行配置,无特殊需要请注释 + //, + // 'Key': function(up, file) { + // // 若想在前端对每个文件的key进行个性化处理,可以配置该函数 + // // 该配置必须要在 unique_names: false , save_key: false 时才生效 + // var key = ""; + // // do something with key here + // return key + // } + } + // domain 为七牛空间(bucket)对应的域名,选择某个空间后,可通过"空间设置->基本设置->域名设置"查看获取 + // uploader 为一个plupload对象,继承了所有plupload的方法,参考http://plupload.com/docs + }); +} + +// 封装 console.log 函数 +function printLog(title, info) { + window.console && console.log(title, info); +} +``` \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/01-全屏-预览-查看源码.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/01-全屏-预览-查看源码.md new file mode 100644 index 0000000..27588c8 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/01-全屏-预览-查看源码.md @@ -0,0 +1,10 @@ +# 全屏 & 预览 & 查看源码 + +## 全屏 + +虽然 wangEditor 没有内置全屏功能,但是你可以通过简单的代码来搞定,作者已经做了一个demo来示范。通过运行 demo(文档一开始就介绍了)即可看到该示例页面,直接查看页面源代码即可。 + +## 预览 & 查看源码 + +如果需要预览和查看源码的功能,也需要跟全屏功能一样,自己定义按钮。点击按钮时通过`editor.txt.html()`获取编辑器内容,然后自定义实现预览和查看源码功能。 + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/02-上传附件.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/02-上传附件.md new file mode 100644 index 0000000..1f3cc88 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/02-上传附件.md @@ -0,0 +1,24 @@ +# 关于上传附件 + +**有用户问到编辑器能否有上传附件的功能?我的建议是不要把附件做到内容中。** + +原因很简单,如果将附件上传之后再插入到富文本内容中,其实就是一个链接的形式。如下图: + +![](http://box.kancloud.cn/2016-02-19_56c718ec6f9bf.png) + +而用户在用编辑器编辑文本时,操作是非常随意多样的,他把这个链接删了,你服务器要想实时删除上传的附件文件,是难监控到的。 + +还有,用户如果要上传很多个附件,也是很难管理的,还是因为富文本的内容变化多样,用户可以随便在什么地方插入附件,而且形式和链接一样。 + +------- + +反过来,我们想一下平时用附件和编辑器最多的产品是什么——是邮箱。邮箱如何处理附件的,大家应该很清楚。它把文本内容和附件分开,这样附件就可以很轻松、明了的进行管理,绝对不会和编辑内容的链接产生混淆。 + +![](http://box.kancloud.cn/2016-02-19_56c718ec83f7e.png) + +你能看到的所有的邮箱产品,几乎都是这样设计的。 + +------- + +因此,在你提问编辑器能否上传附件这个问题的时候,可以想一下能否参照邮箱的实现来设计? + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/03-markdown.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/03-markdown.md new file mode 100644 index 0000000..c723347 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/03-markdown.md @@ -0,0 +1,12 @@ +# 关于 markdown + +**好多使用者问到,wangEditor编辑器能否集成markdown?——答案是:富文本编辑器无法和markdown集成到一起。** + +----- + + +你可以参考 [简书](http://www.jianshu.com/) 的实现方式,简书中编辑器也无法实现富文本和`markdown`的自由切换。要么使用富文本编写文章,要么使用`markdown`编写文章,不能公用。 + +本质上,富文本编辑器和`markdown`编辑器是两回事儿。 + + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/04-xss.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/04-xss.md new file mode 100644 index 0000000..286337f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/04-xss.md @@ -0,0 +1,23 @@ +# 预防 XSS 攻击 + +> 术业有专攻 + +要想在前端预防 xss 攻击,还得依赖于其他工具,例如[xss.js](http://jsxss.com/zh/index.html)(如果打不开页面,就从百度搜一下) + +代码示例如下 + +```html + + + +``` + diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/05-react.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/05-react.md new file mode 100644 index 0000000..8dcc2d4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/05-react.md @@ -0,0 +1,7 @@ +# 用于 React + +如果需要将 wangEditor 用于 React 中,可参见如下示例 + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 进入 React 示例目录 `cd wangEditor/example/demo/in-react/`,查看`src/App.js`即可 +- 也可以运行`npm install && npm start`查看在 React 中的效果(`http://localhost:3000/`) diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/06-vue.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/06-vue.md new file mode 100644 index 0000000..47e167a --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/06-vue.md @@ -0,0 +1,7 @@ +# 用于 Vue + +如果需要将 wangEditor 用于 Vue 中,可参见如下示例 + +- 下载源码 `git clone git@github.com:wangfupeng1988/wangEditor.git` +- 进入 vue 示例目录 `cd wangEditor/example/demo/in-vue/`,查看`src/components/Editor.vue`即可 +- 也可以运行`npm install && npm run dev`查看在 vue 中的效果(`http://localhost:8080/`) diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/07-ng.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/07-ng.md new file mode 100644 index 0000000..1d59afc --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/07-ng.md @@ -0,0 +1,3 @@ +# 用于 Angular + +感谢 [@fengnovo](https://github.com/fengnovo) 提供了一个 angular2 的兼容示例,可供参考 https://github.com/fengnovo/wangEditor/tree/master/example/demo/in-ng2 diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/08-api.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/08-api.md new file mode 100644 index 0000000..e8b4f6d --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/05-other/08-api.md @@ -0,0 +1,27 @@ +# 常用 API + +## 属性 + +- 获取编辑器的唯一标识 `editor.id` +- 获取编辑区域 DOM 节点 `editor.$textElem[0]` +- 获取菜单栏 DOM 节点 `editor.$toolbarElem[0]` +- 获取编辑器配置信息 `editor.config` +- 获取编辑区域 DOM 节点 ID `editor.textElemId` +- 获取菜单栏 DOM 节点 ID `editor.toolbarElemId` +- 获取菜单栏中“图片”菜单的 DOM 节点 ID `editor.imgMenuId` + +## 方法 + +### 选取操作 + +- 获取选中的文字 `editor.selection.getSelectionText()` +- 获取选取所在的 DOM 节点 `editor.selection.getSelectionContainerElem()[0]` + - 开始节点 `editor.selection.getSelectionStartElem()[0]` + - 结束节点 `editor.selection.getSelectionEndElem()[0]` +- 折叠选取 `editor.selection.collapseRange()` +- 更多可参见[源码中](https://github.com/wangfupeng1988/wangEditor/blob/master/src/js/selection/index.js)定义的方法 + +### 编辑内容操作 + +- 插入 HTML `editor.cmd.do('insertHTML', '

...

')` +- 可通过`editor.cmd.do(name, value)`来执行`document.execCommand(name, false, value)`的操作 \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/docs/usage/README.md b/novel-admin/src/main/resources/static/wangEditor/docs/usage/README.md new file mode 100644 index 0000000..4c801e6 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/docs/usage/README.md @@ -0,0 +1,3 @@ +同步[../../README.md](../../README.md)的内容 + +将所有文档跟新到 www.kancloud.cn/wangfupeng/wangeditor3/332599 中 diff --git a/novel-admin/src/main/resources/static/wangEditor/example/README.md b/novel-admin/src/main/resources/static/wangEditor/example/README.md new file mode 100644 index 0000000..6e17ca0 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/README.md @@ -0,0 +1 @@ +wangEditor demo diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/package.json b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/package.json new file mode 100644 index 0000000..054d5cd --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/package.json @@ -0,0 +1,19 @@ +{ + "name": "wangeditor-in-react", + "version": "0.1.0", + "private": true, + "dependencies": { + "react": "^15.5.4", + "react-dom": "^15.5.4", + "wangeditor": ">=3.0.0" + }, + "devDependencies": { + "react-scripts": "1.0.7" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/favicon.ico b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c125de5d897c1ff5692a656485b3216123dcd89 GIT binary patch literal 24838 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 + + + + + + + + + + React App + + + +
+ + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/manifest.json b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/manifest.json new file mode 100644 index 0000000..be607e4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "192x192", + "type": "image/png" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.css b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.css new file mode 100644 index 0000000..15adfdc --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.css @@ -0,0 +1,24 @@ +.App { + text-align: center; +} + +.App-logo { + animation: App-logo-spin infinite 20s linear; + height: 80px; +} + +.App-header { + background-color: #222; + height: 150px; + padding: 20px; + color: white; +} + +.App-intro { + font-size: large; +} + +@keyframes App-logo-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.js new file mode 100644 index 0000000..95b21fb --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react'; +import logo from './logo.svg'; +import './App.css'; +import E from 'wangeditor' + +class App extends Component { + constructor(props, context) { + super(props, context); + this.state = { + editorContent: '' + } + } + render() { + return ( +
+
+ logo +

Welcome to React

+
+

+ To get started, edit src/App.js and save to reload. +

+ + {/* 将生成编辑器 */} +
+
+ + +
+ ); + } + componentDidMount() { + const elem = this.refs.editorElem + const editor = new E(elem) + // 使用 onchange 函数监听内容的变化,并实时更新到 state 中 + editor.customConfig.onchange = html => { + this.setState({ + editorContent: html + }) + } + editor.create() + } + clickHandle() { + alert(this.state.editorContent) + } +} + +export default App; diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.test.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.test.js new file mode 100644 index 0000000..b84af98 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/App.test.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(, div); +}); diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.css b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.css new file mode 100644 index 0000000..b4cc725 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.css @@ -0,0 +1,5 @@ +body { + margin: 0; + padding: 0; + font-family: sans-serif; +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.js new file mode 100644 index 0000000..53c7688 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/index.js @@ -0,0 +1,8 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; +import registerServiceWorker from './registerServiceWorker'; +import './index.css'; + +ReactDOM.render(, document.getElementById('root')); +registerServiceWorker(); diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/logo.svg b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/logo.svg new file mode 100644 index 0000000..6b60c10 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/logo.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/registerServiceWorker.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/registerServiceWorker.js new file mode 100644 index 0000000..9966897 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-react/src/registerServiceWorker.js @@ -0,0 +1,51 @@ +// In production, we register a service worker to serve assets from local cache. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on the "N+1" visit to a page, since previously +// cached resources are updated in the background. + +// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. +// This link also includes instructions on opting out of this behavior. + +export default function register() { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the old content will have been purged and + // the fresh content will have been added to the cache. + // It's the perfect time to display a "New content is + // available; please refresh." message in your web app. + console.log('New content is available; please refresh.'); + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); + }); + } +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.babelrc b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.babelrc new file mode 100644 index 0000000..13f0e47 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + ["env", { "modules": false }], + "stage-2" + ], + "plugins": ["transform-runtime"], + "comments": false, + "env": { + "test": { + "presets": ["env", "stage-2"], + "plugins": [ "istanbul" ] + } + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.editorconfig b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.editorconfig new file mode 100644 index 0000000..9d08a1a --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.postcssrc.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.postcssrc.js new file mode 100644 index 0000000..ea9a5ab --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/.postcssrc.js @@ -0,0 +1,8 @@ +// https://github.com/michael-ciniawsky/postcss-load-config + +module.exports = { + "plugins": { + // to edit target browsers: use "browserlist" field in package.json + "autoprefixer": {} + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/build.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/build.js new file mode 100644 index 0000000..6b8add1 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/build.js @@ -0,0 +1,35 @@ +require('./check-versions')() + +process.env.NODE_ENV = 'production' + +var ora = require('ora') +var rm = require('rimraf') +var path = require('path') +var chalk = require('chalk') +var webpack = require('webpack') +var config = require('../config') +var webpackConfig = require('./webpack.prod.conf') + +var spinner = ora('building for production...') +spinner.start() + +rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { + if (err) throw err + webpack(webpackConfig, function (err, stats) { + spinner.stop() + if (err) throw err + process.stdout.write(stats.toString({ + colors: true, + modules: false, + children: false, + chunks: false, + chunkModules: false + }) + '\n\n') + + console.log(chalk.cyan(' Build complete.\n')) + console.log(chalk.yellow( + ' Tip: built files are meant to be served over an HTTP server.\n' + + ' Opening index.html over file:// won\'t work.\n' + )) + }) +}) diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/check-versions.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/check-versions.js new file mode 100644 index 0000000..100f3a0 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/check-versions.js @@ -0,0 +1,48 @@ +var chalk = require('chalk') +var semver = require('semver') +var packageConfig = require('../package.json') +var shell = require('shelljs') +function exec (cmd) { + return require('child_process').execSync(cmd).toString().trim() +} + +var versionRequirements = [ + { + name: 'node', + currentVersion: semver.clean(process.version), + versionRequirement: packageConfig.engines.node + }, +] + +if (shell.which('npm')) { + versionRequirements.push({ + name: 'npm', + currentVersion: exec('npm --version'), + versionRequirement: packageConfig.engines.npm + }) +} + +module.exports = function () { + var warnings = [] + for (var i = 0; i < versionRequirements.length; i++) { + var mod = versionRequirements[i] + if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { + warnings.push(mod.name + ': ' + + chalk.red(mod.currentVersion) + ' should be ' + + chalk.green(mod.versionRequirement) + ) + } + } + + if (warnings.length) { + console.log('') + console.log(chalk.yellow('To use this template, you must update following to modules:')) + console.log() + for (var i = 0; i < warnings.length; i++) { + var warning = warnings[i] + console.log(' ' + warning) + } + console.log() + process.exit(1) + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-client.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-client.js new file mode 100644 index 0000000..18aa1e2 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-client.js @@ -0,0 +1,9 @@ +/* eslint-disable */ +require('eventsource-polyfill') +var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') + +hotClient.subscribe(function (event) { + if (event.action === 'reload') { + window.location.reload() + } +}) diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-server.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-server.js new file mode 100644 index 0000000..782dc6f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/dev-server.js @@ -0,0 +1,89 @@ +require('./check-versions')() + +var config = require('../config') +if (!process.env.NODE_ENV) { + process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) +} + +var opn = require('opn') +var path = require('path') +var express = require('express') +var webpack = require('webpack') +var proxyMiddleware = require('http-proxy-middleware') +var webpackConfig = require('./webpack.dev.conf') + +// default port where dev server listens for incoming traffic +var port = process.env.PORT || config.dev.port +// automatically open browser, if not set will be false +var autoOpenBrowser = !!config.dev.autoOpenBrowser +// Define HTTP proxies to your custom API backend +// https://github.com/chimurai/http-proxy-middleware +var proxyTable = config.dev.proxyTable + +var app = express() +var compiler = webpack(webpackConfig) + +var devMiddleware = require('webpack-dev-middleware')(compiler, { + publicPath: webpackConfig.output.publicPath, + quiet: true +}) + +var hotMiddleware = require('webpack-hot-middleware')(compiler, { + log: () => {} +}) +// force page reload when html-webpack-plugin template changes +compiler.plugin('compilation', function (compilation) { + compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { + hotMiddleware.publish({ action: 'reload' }) + cb() + }) +}) + +// proxy api requests +Object.keys(proxyTable).forEach(function (context) { + var options = proxyTable[context] + if (typeof options === 'string') { + options = { target: options } + } + app.use(proxyMiddleware(options.filter || context, options)) +}) + +// handle fallback for HTML5 history API +app.use(require('connect-history-api-fallback')()) + +// serve webpack bundle output +app.use(devMiddleware) + +// enable hot-reload and state-preserving +// compilation error display +app.use(hotMiddleware) + +// serve pure static assets +var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) +app.use(staticPath, express.static('./static')) + +var uri = 'http://localhost:' + port + +var _resolve +var readyPromise = new Promise(resolve => { + _resolve = resolve +}) + +console.log('> Starting dev server...') +devMiddleware.waitUntilValid(() => { + console.log('> Listening at ' + uri + '\n') + // when env is testing, don't need open it + if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { + opn(uri) + } + _resolve() +}) + +var server = app.listen(port) + +module.exports = { + ready: readyPromise, + close: () => { + server.close() + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/utils.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/utils.js new file mode 100644 index 0000000..b1d54b4 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/utils.js @@ -0,0 +1,71 @@ +var path = require('path') +var config = require('../config') +var ExtractTextPlugin = require('extract-text-webpack-plugin') + +exports.assetsPath = function (_path) { + var assetsSubDirectory = process.env.NODE_ENV === 'production' + ? config.build.assetsSubDirectory + : config.dev.assetsSubDirectory + return path.posix.join(assetsSubDirectory, _path) +} + +exports.cssLoaders = function (options) { + options = options || {} + + var cssLoader = { + loader: 'css-loader', + options: { + minimize: process.env.NODE_ENV === 'production', + sourceMap: options.sourceMap + } + } + + // generate loader string to be used with extract text plugin + function generateLoaders (loader, loaderOptions) { + var loaders = [cssLoader] + if (loader) { + loaders.push({ + loader: loader + '-loader', + options: Object.assign({}, loaderOptions, { + sourceMap: options.sourceMap + }) + }) + } + + // Extract CSS when that option is specified + // (which is the case during production build) + if (options.extract) { + return ExtractTextPlugin.extract({ + use: loaders, + fallback: 'vue-style-loader' + }) + } else { + return ['vue-style-loader'].concat(loaders) + } + } + + // https://vue-loader.vuejs.org/en/configurations/extract-css.html + return { + css: generateLoaders(), + postcss: generateLoaders(), + less: generateLoaders('less'), + sass: generateLoaders('sass', { indentedSyntax: true }), + scss: generateLoaders('sass'), + stylus: generateLoaders('stylus'), + styl: generateLoaders('stylus') + } +} + +// Generate loaders for standalone style files (outside of .vue) +exports.styleLoaders = function (options) { + var output = [] + var loaders = exports.cssLoaders(options) + for (var extension in loaders) { + var loader = loaders[extension] + output.push({ + test: new RegExp('\\.' + extension + '$'), + use: loader + }) + } + return output +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/vue-loader.conf.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/vue-loader.conf.js new file mode 100644 index 0000000..7aee79b --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/vue-loader.conf.js @@ -0,0 +1,12 @@ +var utils = require('./utils') +var config = require('../config') +var isProduction = process.env.NODE_ENV === 'production' + +module.exports = { + loaders: utils.cssLoaders({ + sourceMap: isProduction + ? config.build.productionSourceMap + : config.dev.cssSourceMap, + extract: isProduction + }) +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.base.conf.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.base.conf.js new file mode 100644 index 0000000..daa3589 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.base.conf.js @@ -0,0 +1,58 @@ +var path = require('path') +var utils = require('./utils') +var config = require('../config') +var vueLoaderConfig = require('./vue-loader.conf') + +function resolve (dir) { + return path.join(__dirname, '..', dir) +} + +module.exports = { + entry: { + app: './src/main.js' + }, + output: { + path: config.build.assetsRoot, + filename: '[name].js', + publicPath: process.env.NODE_ENV === 'production' + ? config.build.assetsPublicPath + : config.dev.assetsPublicPath + }, + resolve: { + extensions: ['.js', '.vue', '.json'], + alias: { + 'vue$': 'vue/dist/vue.esm.js', + '@': resolve('src') + } + }, + module: { + rules: [ + { + test: /\.vue$/, + loader: 'vue-loader', + options: vueLoaderConfig + }, + { + test: /\.js$/, + loader: 'babel-loader', + include: [resolve('src'), resolve('test')] + }, + { + test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('img/[name].[hash:7].[ext]') + } + }, + { + test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, + loader: 'url-loader', + options: { + limit: 10000, + name: utils.assetsPath('fonts/[name].[hash:7].[ext]') + } + } + ] + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.dev.conf.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.dev.conf.js new file mode 100644 index 0000000..5470402 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.dev.conf.js @@ -0,0 +1,35 @@ +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') + +// add hot-reload related code to entry chunks +Object.keys(baseWebpackConfig.entry).forEach(function (name) { + baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) +}) + +module.exports = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) + }, + // cheap-module-eval-source-map is faster for development + devtool: '#cheap-module-eval-source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': config.dev.env + }), + // https://github.com/glenjamin/webpack-hot-middleware#installation--usage + new webpack.HotModuleReplacementPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + // https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: 'index.html', + template: 'index.html', + inject: true + }), + new FriendlyErrorsPlugin() + ] +}) diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.prod.conf.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.prod.conf.js new file mode 100644 index 0000000..da44b65 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/build/webpack.prod.conf.js @@ -0,0 +1,120 @@ +var path = require('path') +var utils = require('./utils') +var webpack = require('webpack') +var config = require('../config') +var merge = require('webpack-merge') +var baseWebpackConfig = require('./webpack.base.conf') +var CopyWebpackPlugin = require('copy-webpack-plugin') +var HtmlWebpackPlugin = require('html-webpack-plugin') +var ExtractTextPlugin = require('extract-text-webpack-plugin') +var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') + +var env = config.build.env + +var webpackConfig = merge(baseWebpackConfig, { + module: { + rules: utils.styleLoaders({ + sourceMap: config.build.productionSourceMap, + extract: true + }) + }, + devtool: config.build.productionSourceMap ? '#source-map' : false, + output: { + path: config.build.assetsRoot, + filename: utils.assetsPath('js/[name].[chunkhash].js'), + chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') + }, + plugins: [ + // http://vuejs.github.io/vue-loader/en/workflow/production.html + new webpack.DefinePlugin({ + 'process.env': env + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + warnings: false + }, + sourceMap: true + }), + // extract css into its own file + new ExtractTextPlugin({ + filename: utils.assetsPath('css/[name].[contenthash].css') + }), + // Compress extracted CSS. We are using this plugin so that possible + // duplicated CSS from different components can be deduped. + new OptimizeCSSPlugin({ + cssProcessorOptions: { + safe: true + } + }), + // generate dist index.html with correct asset hash for caching. + // you can customize output by editing /index.html + // see https://github.com/ampedandwired/html-webpack-plugin + new HtmlWebpackPlugin({ + filename: config.build.index, + template: 'index.html', + inject: true, + minify: { + removeComments: true, + collapseWhitespace: true, + removeAttributeQuotes: true + // more options: + // https://github.com/kangax/html-minifier#options-quick-reference + }, + // necessary to consistently work with multiple chunks via CommonsChunkPlugin + chunksSortMode: 'dependency' + }), + // split vendor js into its own file + new webpack.optimize.CommonsChunkPlugin({ + name: 'vendor', + minChunks: function (module, count) { + // any required modules inside node_modules are extracted to vendor + return ( + module.resource && + /\.js$/.test(module.resource) && + module.resource.indexOf( + path.join(__dirname, '../node_modules') + ) === 0 + ) + } + }), + // extract webpack runtime and module manifest to its own file in order to + // prevent vendor hash from being updated whenever app bundle is updated + new webpack.optimize.CommonsChunkPlugin({ + name: 'manifest', + chunks: ['vendor'] + }), + // copy custom static assets + new CopyWebpackPlugin([ + { + from: path.resolve(__dirname, '../static'), + to: config.build.assetsSubDirectory, + ignore: ['.*'] + } + ]) + ] +}) + +if (config.build.productionGzip) { + var CompressionWebpackPlugin = require('compression-webpack-plugin') + + webpackConfig.plugins.push( + new CompressionWebpackPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: new RegExp( + '\\.(' + + config.build.productionGzipExtensions.join('|') + + ')$' + ), + threshold: 10240, + minRatio: 0.8 + }) + ) +} + +if (config.build.bundleAnalyzerReport) { + var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin + webpackConfig.plugins.push(new BundleAnalyzerPlugin()) +} + +module.exports = webpackConfig diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/dev.env.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/dev.env.js new file mode 100644 index 0000000..efead7c --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/dev.env.js @@ -0,0 +1,6 @@ +var merge = require('webpack-merge') +var prodEnv = require('./prod.env') + +module.exports = merge(prodEnv, { + NODE_ENV: '"development"' +}) diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/index.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/index.js new file mode 100644 index 0000000..196da1f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/index.js @@ -0,0 +1,38 @@ +// see http://vuejs-templates.github.io/webpack for documentation. +var path = require('path') + +module.exports = { + build: { + env: require('./prod.env'), + index: path.resolve(__dirname, '../dist/index.html'), + assetsRoot: path.resolve(__dirname, '../dist'), + assetsSubDirectory: 'static', + assetsPublicPath: '/', + productionSourceMap: true, + // Gzip off by default as many popular static hosts such as + // Surge or Netlify already gzip all static assets for you. + // Before setting to `true`, make sure to: + // npm install --save-dev compression-webpack-plugin + productionGzip: false, + productionGzipExtensions: ['js', 'css'], + // Run the build command with an extra argument to + // View the bundle analyzer report after build finishes: + // `npm run build --report` + // Set to `true` or `false` to always turn it on or off + bundleAnalyzerReport: process.env.npm_config_report + }, + dev: { + env: require('./dev.env'), + port: 8080, + autoOpenBrowser: true, + assetsSubDirectory: 'static', + assetsPublicPath: '/', + proxyTable: {}, + // CSS Sourcemaps off by default because relative paths are "buggy" + // with this option, according to the CSS-Loader README + // (https://github.com/webpack/css-loader#sourcemaps) + // In our experience, they generally work as expected, + // just be aware of this issue when enabling this option. + cssSourceMap: false + } +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/prod.env.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/prod.env.js new file mode 100644 index 0000000..773d263 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/config/prod.env.js @@ -0,0 +1,3 @@ +module.exports = { + NODE_ENV: '"production"' +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/index.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/index.html new file mode 100644 index 0000000..47ae14a --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/index.html @@ -0,0 +1,11 @@ + + + + + wangeditor-in-vue + + +
+ + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/package.json b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/package.json new file mode 100644 index 0000000..80cf68f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/package.json @@ -0,0 +1,60 @@ +{ + "name": "wangeditor-in-vue", + "version": "1.0.0", + "description": "A Vue.js project", + "author": "git ", + "private": true, + "scripts": { + "dev": "node build/dev-server.js", + "start": "node build/dev-server.js", + "build": "node build/build.js" + }, + "dependencies": { + "vue": "^2.3.3", + "wangeditor": ">=3.0.0" + }, + "devDependencies": { + "autoprefixer": "^6.7.2", + "babel-core": "^6.22.1", + "babel-loader": "^6.2.10", + "babel-plugin-transform-runtime": "^6.22.0", + "babel-preset-env": "^1.3.2", + "babel-preset-stage-2": "^6.22.0", + "babel-register": "^6.22.0", + "chalk": "^1.1.3", + "connect-history-api-fallback": "^1.3.0", + "copy-webpack-plugin": "^4.0.1", + "css-loader": "^0.28.0", + "eventsource-polyfill": "^0.9.6", + "express": "^4.14.1", + "extract-text-webpack-plugin": "^2.0.0", + "file-loader": "^0.11.1", + "friendly-errors-webpack-plugin": "^1.1.3", + "html-webpack-plugin": "^2.28.0", + "http-proxy-middleware": "^0.17.3", + "webpack-bundle-analyzer": "^2.2.1", + "semver": "^5.3.0", + "shelljs": "^0.7.6", + "opn": "^4.0.2", + "optimize-css-assets-webpack-plugin": "^1.3.0", + "ora": "^1.2.0", + "rimraf": "^2.6.0", + "url-loader": "^0.5.8", + "vue-loader": "^12.1.0", + "vue-style-loader": "^3.0.1", + "vue-template-compiler": "^2.3.3", + "webpack": "^2.6.1", + "webpack-dev-middleware": "^1.10.0", + "webpack-hot-middleware": "^2.18.0", + "webpack-merge": "^4.1.0" + }, + "engines": { + "node": ">= 4.0.0", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions", + "not ie <= 8" + ] +} diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/App.vue b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/App.vue new file mode 100644 index 0000000..27d15ff --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/App.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/assets/logo.png b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d2503fc2a44b5053b0837ebea6e87a2d339a43 GIT binary patch literal 6849 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 +
+
+ +
+ + + + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/components/Hello.vue b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/components/Hello.vue new file mode 100644 index 0000000..2d80539 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/components/Hello.vue @@ -0,0 +1,53 @@ + + + + + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/main.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/main.js new file mode 100644 index 0000000..7b7fec7 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/src/main.js @@ -0,0 +1,13 @@ +// The Vue build version to load with the `import` command +// (runtime-only or standalone) has been set in webpack.base.conf with an alias. +import Vue from 'vue' +import App from './App' + +Vue.config.productionTip = false + +/* eslint-disable no-new */ +new Vue({ + el: '#app', + template: '', + components: { App } +}) diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/static/.gitkeep b/novel-admin/src/main/resources/static/wangEditor/example/demo/in-vue/static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd-main.js b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd-main.js new file mode 100644 index 0000000..444b2da --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd-main.js @@ -0,0 +1,4 @@ +require(['/wangEditor.min.js'], function (E) { + var editor2 = new E('#div3') + editor2.create() +}) \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd.html new file mode 100644 index 0000000..6a3d666 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-amd.html @@ -0,0 +1,15 @@ + + + + + wangEditor 使用 AMD 加载 + + +

wangEditor 使用 AMD 加载

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-css-reset.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-css-reset.html new file mode 100644 index 0000000..c01a10d --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-css-reset.html @@ -0,0 +1,66 @@ + + + + + wangEditor css reset + + + +

wangEditor css reset

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-emot.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-emot.html new file mode 100644 index 0000000..02d8f7f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-emot.html @@ -0,0 +1,84 @@ + + + + + wangEditor 配置表情 + + +

wangEditor 配置表情

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-fullscreen.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-fullscreen.html new file mode 100644 index 0000000..cbbaa01 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-fullscreen.html @@ -0,0 +1,114 @@ + + + + + wangEditor 全屏 + + + +

wangEditor 全屏

+ + +
+ +
+
+
+ +
+
+ +
+

wangEditor 本身不包含“全屏”功能,不过可以很简单的开发出来

+

注意,全屏模式与max-height有冲突,尽量避免一起使用

+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-get-content.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-get-content.html new file mode 100644 index 0000000..012c81c --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-get-content.html @@ -0,0 +1,34 @@ + + + + + wangEditor 获取内容 + + +

wangEditor 获取内容

+
+

欢迎使用 wangEditor 富文本编辑器

+

欢迎使用 wangEditor 富文本编辑器

+
+
+ + +
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-getJSON.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-getJSON.html new file mode 100644 index 0000000..68cd155 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-getJSON.html @@ -0,0 +1,30 @@ + + + + + wangEditor demo getJSON + + +

获取 JSON

+
+

欢迎使用 wangEditor 富文本编辑器

+ +
+ + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-lang.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-lang.html new file mode 100644 index 0000000..6c77826 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-lang.html @@ -0,0 +1,31 @@ + + + + + wangEditor lang test + + +

多语言测试

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-menus.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-menus.html new file mode 100644 index 0000000..4afd45f --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-menus.html @@ -0,0 +1,26 @@ + + + + + wangEditor 菜单配置 + + +

wangEditor 自定义菜单配置

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-mult.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-mult.html new file mode 100644 index 0000000..bd6f7e1 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-mult.html @@ -0,0 +1,44 @@ + + + + + wangEditor 一个页面多个编辑器 + + + + +

第一个 demo(菜单和编辑器区域分开)

+
+
+
中间隔离带
+
+

请输入内容

+
+ +

第二个 demo(常规)

+
+

请输入内容

+
+ + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onblur.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onblur.html new file mode 100644 index 0000000..a6644bf --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onblur.html @@ -0,0 +1,23 @@ + + + + + wangEditor test onblur + + +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onchange.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onchange.html new file mode 100644 index 0000000..231de10 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onchange.html @@ -0,0 +1,24 @@ + + + + + wangEditor test onchange + + +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onfocus.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onfocus.html new file mode 100644 index 0000000..7d95de0 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-onfocus.html @@ -0,0 +1,22 @@ + + + + + wangEditor test onfocus + + +
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-paste.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-paste.html new file mode 100644 index 0000000..a3a7477 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-paste.html @@ -0,0 +1,25 @@ + + + + + wangEditor paste test + + +

wangEditor paste test

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-set-content.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-set-content.html new file mode 100644 index 0000000..42eff3b --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-set-content.html @@ -0,0 +1,35 @@ + + + + + wangEditor 设置内容 + + +

wangEditor 设置内容

+
+

欢迎使用 wangEditor 富文本编辑器

+
+
+ + +
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-sperate.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-sperate.html new file mode 100644 index 0000000..0d0b857 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-sperate.html @@ -0,0 +1,35 @@ + + + + + wangEditor 菜单和编辑器区域分离 + + + + +

wangEditor 菜单和编辑器区域分离

+
+
+
中间隔离带
+
+

请输入内容

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-textarea.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-textarea.html new file mode 100644 index 0000000..8e41119 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-textarea.html @@ -0,0 +1,33 @@ + + + + + wangEditor demo textarea + + +

编辑器

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +
+ +

textarea

+ + + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/demo/test-uploadimg.html b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-uploadimg.html new file mode 100644 index 0000000..97246ca --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/demo/test-uploadimg.html @@ -0,0 +1,58 @@ + + + + + wangEditor 上传图片 + + +

wangEditor 上传图片到服务器

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

wangEditor 以base64保存图片文件

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

wangEditor 自定义上传图片

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/favicon.ico b/novel-admin/src/main/resources/static/wangEditor/example/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6075775de436a980a4302ef7aef74de01c41b0c0 GIT binary patch literal 4286 zcmc&&O>B%&6n$7UQqdt=@z*v&Q?bzKLSoUO#6~PE5H>bMLPQ%IVu3WFL~PjjU9e$c zfk;F|M@mb`Y=jVGM%$!C!VrRx);ZI6larS>W7DtNoZk2D-+AwS_uX%%&P~8?eZAvf zcO~UqjdQLMfXb}^s4xD&HobBbqYCHF@12$fJ_32*-8g|<_tqZ#>?|C^uxf~-xLn|oXB z`M3t|{{_H0cm>P`lHUI+aOnQ4F`p02^xQ3w4}p8YBj6%13HaB#=D_DJ&;u}*Ia+}* zF60mGR}o|DGr261Cyd(_iMteuE62~e{}Rb_B9bTM?~25|hmJL`zUBD0OAgk=4?ky} zOa?TMju*rB$NS!deCx+NzYT!;mgB$e<9GOG(=^ zZ@UoK_#XM}ZEqlLZ883R-j8Q^9Z(LAkBNPW*x9QXLu?hj#q%zhoqd=37(e@*`R(z3 zmV>fM?8n7^+Uwaf*gG_a`ESCwPUhS{KL?s!@2SPByd%CAlY@@;K>c*g-fX^4FkT7RKFCkd z{sG|mXlyuM3;P403*ft!bsMW!jpN$R0(`dTfc3yvfSk%dfO$2bu}O@1#&#acKLmZO zzmD}9T1)T`LLa-sW6dA-AAA1ewg0gH7=J(ZZ#{af_pJGvOPoLX8vwpH_#Bb|KZ}+6 z{=q)NXV3?*7Lq^?dBg8t{G4a^VZ0@|vjBTw9 + + + + IcoMoon Demo + + + + + +
+

Font Name: icomoon (Glyphs: 27)

+
+
+

Grid Size: 14

+
+
+ + + + icon-close +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-remove +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-times +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-trash-o +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-terminal +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-header +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paint-brush +
+
+ + +
+
+ liga: + +
+
+
+
+

Grid Size: 16

+
+
+ + + + icon-pencil2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-image +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-play +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-location +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-undo +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-redo +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-quotes-left +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-list-numbered +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-list2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-upload2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-link +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-happy +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-cancel-circle +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-bold +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-underline +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-italic +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-strikethrough +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-page-break +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-table2 +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-left +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-center +
+
+ + +
+
+ liga: + +
+
+
+
+ + + + icon-paragraph-right +
+
+ + +
+
+ liga: + +
+
+
+ + +
+

Font Test Drive

+ + +
  +
+
+ +
+

Generated by IcoMoon

+
+ + + + diff --git a/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.eot b/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.eot new file mode 100644 index 0000000000000000000000000000000000000000..0d144fdfa4924f18e0bb237e911d7b4847e3f4fe GIT binary patch literal 5656 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 literal 0 HcmV?d00001 diff --git a/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.svg b/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.svg new file mode 100644 index 0000000..21be016 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.svg @@ -0,0 +1,37 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.ttf b/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..80be9ad27182d62f562b8181714691ace353524a GIT binary patch literal 5492 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 literal 0 HcmV?d00001 diff --git a/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.woff b/novel-admin/src/main/resources/static/wangEditor/example/icomoon/fonts/icomoon.woff new file mode 100644 index 0000000000000000000000000000000000000000..fa64c4d86cef0bd3aadc1906f25b2c0ce6bf96da GIT binary patch literal 5568 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 QY+$Fm0gCU~ + + + + wangEditor demo list + + + +
+

可访问 wangEditor 官网 了解更多内容

+
+

欢迎使用 wangEditor 富文本编辑器

+
+ +

wangEditor demo list(demo页面直接查看网页源代码即可)

+ + +

其他链接

+
+ +
+ +

向我捐赠

+ +
+ + + + + + \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/pay.png b/novel-admin/src/main/resources/static/wangEditor/example/pay.png new file mode 100644 index 0000000000000000000000000000000000000000..98efb8d042abe0e0dc1e3db9581ce383304f572f GIT binary patch literal 95517 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 literal 0 HcmV?d00001 diff --git a/novel-admin/src/main/resources/static/wangEditor/example/server/index.js b/novel-admin/src/main/resources/static/wangEditor/example/server/index.js new file mode 100644 index 0000000..28d8a60 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/server/index.js @@ -0,0 +1,88 @@ +const fs = require('fs') +const path = require('path') +const formidable = require('formidable') +const util = require('./util.js') + +const koa = require('koa') +const app = koa() + +// 捕获错误 +const onerror = require('koa-onerror') +onerror(app) + +// post body 解析 +const bodyParser = require('koa-bodyparser') +app.use(bodyParser()) + +// 静态文件服务,针对 html js css fonts 文件 +const staticCache = require('koa-static-cache') +function setStaticCache() { + const exampleDir = path.join(__dirname, '..', '..', 'example') + const releaseDir = path.join(__dirname, '..', '..', 'release') + app.use(staticCache(exampleDir)) + app.use(staticCache(releaseDir)) +} +setStaticCache() + +// 配置路由 +const router = require('koa-router')() + +// 保存上传的文件 +function saveFiles(req) { + return new Promise((resolve, reject) => { + const imgLinks = [] + const form = new formidable.IncomingForm() + form.parse(req, function (err, fields, files) { + if (err) { + reject('formidable, form.parse err', err.stack) + } + // 存储图片的文件夹 + const storePath = path.resolve(__dirname, '..', 'upload-files') + if (!fs.existsSync(storePath)) { + fs.mkdirSync(storePath) + } + + // 遍历所有上传来的图片 + util.objForEach(files, (name, file) => { + // 图片临时位置 + const tempFilePath = file.path + // 图片名称和路径 + const fileName = file.name + const fullFileName = path.join(storePath, fileName) + // 将临时文件保存为正式文件 + fs.renameSync(tempFilePath, fullFileName) + // 存储链接 + imgLinks.push('/upload-files/' + fileName) + }) + + // 重新设置静态文件缓存 + setStaticCache() + + // 返回结果 + resolve({ + errno: 0, + data: imgLinks + }) + }) + }) +} + +// 上传图片 +router.post('/upload-img', function* () { + const ctx = this + const req = ctx.req + const res = ctx.res + + // 获取数据 + const data = yield saveFiles(req) + + // 返回结果 + this.body = JSON.stringify(data) +}) +app.use(router.routes()).use(router.allowedMethods()); + +// 启动服务 +app.listen(3000) +console.log('listening on port %s', 3000) + +module.exports = app \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/example/server/util.js b/novel-admin/src/main/resources/static/wangEditor/example/server/util.js new file mode 100644 index 0000000..62477f2 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/example/server/util.js @@ -0,0 +1,14 @@ +module.exports = { + // 遍历对象 + objForEach: function (obj, fn) { + let key, result + for (key in obj) { + if (obj.hasOwnProperty(key)) { + result = fn.call(obj, key, obj[key]) + if (result === false) { + break + } + } + } + } +} \ No newline at end of file diff --git a/novel-admin/src/main/resources/static/wangEditor/gulpfile.js b/novel-admin/src/main/resources/static/wangEditor/gulpfile.js new file mode 100644 index 0000000..171e7e5 --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/gulpfile.js @@ -0,0 +1,122 @@ +const path = require('path') +const fs = require('fs') +const gulp = require('gulp') +const rollup = require('rollup') +const uglify = require('gulp-uglify') +const sourcemaps = require('gulp-sourcemaps') +const rename = require('gulp-rename') +const less = require('gulp-less') +const concat = require('gulp-concat') +const cssmin = require('gulp-cssmin') +const eslint = require('rollup-plugin-eslint') +const postcss = require('gulp-postcss') +const autoprefixer = require('autoprefixer') +const cssgrace = require('cssgrace') +const resolve = require('rollup-plugin-node-resolve') +const babel = require('rollup-plugin-babel') +const gulpReplace = require('gulp-replace') + +// 拷贝 fonts 文件 +gulp.task('copy-fonts', () => { + gulp.src('./src/fonts/*') + .pipe(gulp.dest('./release/fonts')) +}) + +// 处理 css +gulp.task('css', () => { + gulp.src('./src/less/**/*.less') + .pipe(less()) + // 产出的未压缩的文件名 + .pipe(concat('wangEditor.css')) + // 配置 postcss + .pipe(postcss([ + autoprefixer, + cssgrace + ])) + // 将 css 引用的字体文件转换为 base64 格式 + .pipe(gulpReplace( /'fonts\/w-e-icon\..+?'/gm, function (fontFile) { + // fontFile 例如 'fonts/w-e-icon.eot?paxlku' + fontFile = fontFile.slice(0, -1).slice(1) + fontFile = fontFile.split('?')[0] + var ext = fontFile.split('.')[1] + // 读取文件内容,转换为 base64 格式 + var filePath = path.resolve(__dirname, 'release', fontFile) + var content = fs.readFileSync(filePath) + var base64 = content.toString('base64') + // 返回 + return 'data:application/x-font-' + ext + ';charset=utf-8;base64,' + base64 + })) + // 产出文件的位置 + .pipe(gulp.dest('./release')) + // 产出的压缩后的文件名 + .pipe(rename('wangEditor.min.css')) + .pipe(cssmin()) + .pipe(gulp.dest('./release')) +}) + +// 处理 JS +gulp.task('script', () => { + // rollup 打包 js 模块 + return rollup.rollup({ + // 入口文件 + entry: './src/js/index.js', + plugins: [ + // 对原始文件启动 eslint 检查,配置参见 ./.eslintrc.json + eslint(), + resolve(), + babel({ + exclude: 'node_modules/**' // only transpile our source code + }) + ] + }).then(bundle => { + bundle.write({ + // 产出文件使用 umd 规范(即兼容 amd cjs 和 iife) + format: 'umd', + // iife 规范下的全局变量名称 + moduleName: 'wangEditor', + // 产出的未压缩的文件名 + dest: './release/wangEditor.js' + }).then(() => { + // 待 rollup 打包 js 完毕之后,再进行如下的处理: + gulp.src('./release/wangEditor.js') + // inline css + .pipe(gulpReplace(/__INLINE_CSS__/gm, function () { + // 读取 css 文件内容 + var filePath = path.resolve(__dirname, 'release', 'wangEditor.css') + var content = fs.readFileSync(filePath).toString('utf-8') + // 替换 \n \ ' 三个字符 + content = content.replace(/\n/g, '').replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + return content + })) + .pipe(gulp.dest('./release')) + .pipe(sourcemaps.init()) + // 压缩 + .pipe(uglify()) + // 产出的压缩的文件名 + .pipe(rename('wangEditor.min.js')) + // 生成 sourcemap + .pipe(sourcemaps.write('')) + .pipe(gulp.dest('./release')) + }) + }) +}) + + +// 默认任务配置 +gulp.task('default', () => { + gulp.run('copy-fonts', 'css', 'script') + + // 监听 js 原始文件的变化 + gulp.watch('./src/js/**/*.js', () => { + gulp.run('script') + }) + // 监听 css 原始文件的变化 + gulp.watch('./src/less/**/*.less', () => { + gulp.run('css', 'script') + }) + // 监听 icon.less 的变化,变化时重新拷贝 fonts 文件 + gulp.watch('./src/less/icon.less', () => { + gulp.run('copy-fonts') + }) +}) + diff --git a/novel-admin/src/main/resources/static/wangEditor/package.json b/novel-admin/src/main/resources/static/wangEditor/package.json new file mode 100644 index 0000000..b42d94d --- /dev/null +++ b/novel-admin/src/main/resources/static/wangEditor/package.json @@ -0,0 +1,60 @@ +{ + "name": "wangeditor", + "title": "wangEditor", + "version": "3.0.17", + "description": "wangEditor - 基于javascript和css开发的 web 富文本编辑器, 轻量、简洁、易用、开源免费", + "homepage": "http://wangeditor.github.io/", + "author": { + "name": "wangfupeng1988", + "url": "https://github.com/wangfupeng1988" + }, + "keywords": [ + "wangEditor", + "web 富文本编辑器" + ], + "main": "release/wangEditor.js", + "maintainers": [ + { + "name": "wangfupeng1988", + "web": "http://www.cnblogs.com/wangfupeng1988/default.html?OnlyTitle=1", + "mail": "wangfupeng1988@163.com" + } + ], + "repositories": [ + { + "type": "git", + "url": "https://github.com/wangfupeng1988/wangEditor" + } + ], + "scripts": { + "release": "gulp", + "win-example": "node ./example/server/index.js", + "example": "/bin/rm -rf ./example/upload-files && mkdir ./example/upload-files && npm run win-example" + }, + "devDependencies": { + "autoprefixer": "^6.7.7", + "babel-plugin-external-helpers": "^6.22.0", + "babel-preset-latest": "^6.24.0", + "cssgrace": "^3.0.0", + "formidable": "^1.1.1", + "gulp": "^3.9.1", + "gulp-concat": "^2.6.1", + "gulp-cssmin": "^0.1.7", + "gulp-less": "^3.3.0", + "gulp-postcss": "^6.4.0", + "gulp-rename": "^1.2.2", + "gulp-replace": "^0.5.4", + "gulp-sourcemaps": "^2.5.0", + "gulp-uglify": "^2.1.2", + "koa": "^1.2.4", + "koa-bodyparser": "^2.3.0", + "koa-onerror": "^3.1.0", + "koa-router": "^5.4.0", + "koa-static-cache": "^4.0.0", + "rollup": "^0.41.6", + "rollup-plugin-babel": "^2.7.1", + "rollup-plugin-eslint": "^3.0.0", + "rollup-plugin-node-resolve": "^3.0.0" + }, + "dependencies": {} +} diff --git a/novel-admin/src/main/resources/static/wangEditor/release/fonts/w-e-icon.woff b/novel-admin/src/main/resources/static/wangEditor/release/fonts/w-e-icon.woff new file mode 100644 index 0000000000000000000000000000000000000000..fa64c4d86cef0bd3aadc1906f25b2c0ce6bf96da GIT binary patch literal 5568 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 QY+$Fm0gCU~= 0 && matches.item(i) !== this) {} + return i > -1; + }; + } +}; + +/* + DOM 操作 API +*/ + +// 根据 html 代码片段创建 dom 对象 +function createElemByHTML(html) { + var div = void 0; + div = document.createElement('div'); + div.innerHTML = html; + return div.children; +} + +// 是否是 DOM List +function isDOMList(selector) { + if (!selector) { + return false; + } + if (selector instanceof HTMLCollection || selector instanceof NodeList) { + return true; + } + return false; +} + +// 封装 document.querySelectorAll +function querySelectorAll(selector) { + var result = document.querySelectorAll(selector); + if (isDOMList(result)) { + return result; + } else { + return [result]; + } +} + +// 记录所有的事件绑定 +var eventList = []; + +// 创建构造函数 +function DomElement(selector) { + if (!selector) { + return; + } + + // selector 本来就是 DomElement 对象,直接返回 + if (selector instanceof DomElement) { + return selector; + } + + this.selector = selector; + var nodeType = selector.nodeType; + + // 根据 selector 得出的结果(如 DOM,DOM List) + var selectorResult = []; + if (nodeType === 9) { + // document 节点 + selectorResult = [selector]; + } else if (nodeType === 1) { + // 单个 DOM 节点 + selectorResult = [selector]; + } else if (isDOMList(selector) || selector instanceof Array) { + // DOM List 或者数组 + selectorResult = selector; + } else if (typeof selector === 'string') { + // 字符串 + selector = selector.replace('/\n/mg', '').trim(); + if (selector.indexOf('<') === 0) { + // 如
+ selectorResult = createElemByHTML(selector); + } else { + // 如 #id .class + selectorResult = querySelectorAll(selector); + } + } + + var length = selectorResult.length; + if (!length) { + // 空数组 + return this; + } + + // 加入 DOM 节点 + var i = void 0; + for (i = 0; i < length; i++) { + this[i] = selectorResult[i]; + } + this.length = length; +} + +// 修改原型 +DomElement.prototype = { + constructor: DomElement, + + // 类数组,forEach + forEach: function forEach(fn) { + var i = void 0; + for (i = 0; i < this.length; i++) { + var elem = this[i]; + var result = fn.call(elem, elem, i); + if (result === false) { + break; + } + } + return this; + }, + + // clone + clone: function clone(deep) { + var cloneList = []; + this.forEach(function (elem) { + cloneList.push(elem.cloneNode(!!deep)); + }); + return $(cloneList); + }, + + // 获取第几个元素 + get: function get(index) { + var length = this.length; + if (index >= length) { + index = index % length; + } + return $(this[index]); + }, + + // 第一个 + first: function first() { + return this.get(0); + }, + + // 最后一个 + last: function last() { + var length = this.length; + return this.get(length - 1); + }, + + // 绑定事件 + on: function on(type, selector, fn) { + // selector 不为空,证明绑定事件要加代理 + if (!fn) { + fn = selector; + selector = null; + } + + // type 是否有多个 + var types = []; + types = type.split(/\s+/); + + return this.forEach(function (elem) { + types.forEach(function (type) { + if (!type) { + return; + } + + // 记录下,方便后面解绑 + eventList.push({ + elem: elem, + type: type, + fn: fn + }); + + if (!selector) { + // 无代理 + elem.addEventListener(type, fn); + return; + } + + // 有代理 + elem.addEventListener(type, function (e) { + var target = e.target; + if (target.matches(selector)) { + fn.call(target, e); + } + }); + }); + }); + }, + + // 取消事件绑定 + off: function off(type, fn) { + return this.forEach(function (elem) { + elem.removeEventListener(type, fn); + }); + }, + + // 获取/设置 属性 + attr: function attr(key, val) { + if (val == null) { + // 获取值 + return this[0].getAttribute(key); + } else { + // 设置值 + return this.forEach(function (elem) { + elem.setAttribute(key, val); + }); + } + }, + + // 添加 class + addClass: function addClass(className) { + if (!className) { + return this; + } + return this.forEach(function (elem) { + var arr = void 0; + if (elem.className) { + // 解析当前 className 转换为数组 + arr = elem.className.split(/\s/); + arr = arr.filter(function (item) { + return !!item.trim(); + }); + // 添加 class + if (arr.indexOf(className) < 0) { + arr.push(className); + } + // 修改 elem.class + elem.className = arr.join(' '); + } else { + elem.className = className; + } + }); + }, + + // 删除 class + removeClass: function removeClass(className) { + if (!className) { + return this; + } + return this.forEach(function (elem) { + var arr = void 0; + if (elem.className) { + // 解析当前 className 转换为数组 + arr = elem.className.split(/\s/); + arr = arr.filter(function (item) { + item = item.trim(); + // 删除 class + if (!item || item === className) { + return false; + } + return true; + }); + // 修改 elem.class + elem.className = arr.join(' '); + } + }); + }, + + // 修改 css + css: function css(key, val) { + var currentStyle = key + ':' + val + ';'; + return this.forEach(function (elem) { + var style = (elem.getAttribute('style') || '').trim(); + var styleArr = void 0, + resultArr = []; + if (style) { + // 将 style 按照 ; 拆分为数组 + styleArr = style.split(';'); + styleArr.forEach(function (item) { + // 对每项样式,按照 : 拆分为 key 和 value + var arr = item.split(':').map(function (i) { + return i.trim(); + }); + if (arr.length === 2) { + resultArr.push(arr[0] + ':' + arr[1]); + } + }); + // 替换或者新增 + resultArr = resultArr.map(function (item) { + if (item.indexOf(key) === 0) { + return currentStyle; + } else { + return item; + } + }); + if (resultArr.indexOf(currentStyle) < 0) { + resultArr.push(currentStyle); + } + // 结果 + elem.setAttribute('style', resultArr.join('; ')); + } else { + // style 无值 + elem.setAttribute('style', currentStyle); + } + }); + }, + + // 显示 + show: function show() { + return this.css('display', 'block'); + }, + + // 隐藏 + hide: function hide() { + return this.css('display', 'none'); + }, + + // 获取子节点 + children: function children() { + var elem = this[0]; + if (!elem) { + return null; + } + + return $(elem.children); + }, + + // 获取子节点(包括文本节点) + childNodes: function childNodes() { + var elem = this[0]; + if (!elem) { + return null; + } + + return $(elem.childNodes); + }, + + // 增加子节点 + append: function append($children) { + return this.forEach(function (elem) { + $children.forEach(function (child) { + elem.appendChild(child); + }); + }); + }, + + // 移除当前节点 + remove: function remove() { + return this.forEach(function (elem) { + if (elem.remove) { + elem.remove(); + } else { + var parent = elem.parentElement; + parent && parent.removeChild(elem); + } + }); + }, + + // 是否包含某个子节点 + isContain: function isContain($child) { + var elem = this[0]; + var child = $child[0]; + return elem.contains(child); + }, + + // 尺寸数据 + getSizeData: function getSizeData() { + var elem = this[0]; + return elem.getBoundingClientRect(); // 可得到 bottom height left right top width 的数据 + }, + + // 封装 nodeName + getNodeName: function getNodeName() { + var elem = this[0]; + return elem.nodeName; + }, + + // 从当前元素查找 + find: function find(selector) { + var elem = this[0]; + return $(elem.querySelectorAll(selector)); + }, + + // 获取当前元素的 text + text: function text(val) { + if (!val) { + // 获取 text + var elem = this[0]; + return elem.innerHTML.replace(/<.*?>/g, function () { + return ''; + }); + } else { + // 设置 text + return this.forEach(function (elem) { + elem.innerHTML = val; + }); + } + }, + + // 获取 html + html: function html(value) { + var elem = this[0]; + if (value == null) { + return elem.innerHTML; + } else { + elem.innerHTML = value; + return this; + } + }, + + // 获取 value + val: function val() { + var elem = this[0]; + return elem.value.trim(); + }, + + // focus + focus: function focus() { + return this.forEach(function (elem) { + elem.focus(); + }); + }, + + // parent + parent: function parent() { + var elem = this[0]; + return $(elem.parentElement); + }, + + // parentUntil 找到符合 selector 的父节点 + parentUntil: function parentUntil(selector, _currentElem) { + var results = document.querySelectorAll(selector); + var length = results.length; + if (!length) { + // 传入的 selector 无效 + return null; + } + + var elem = _currentElem || this[0]; + if (elem.nodeName === 'BODY') { + return null; + } + + var parent = elem.parentElement; + var i = void 0; + for (i = 0; i < length; i++) { + if (parent === results[i]) { + // 找到,并返回 + return $(parent); + } + } + + // 继续查找 + return this.parentUntil(selector, parent); + }, + + // 判断两个 elem 是否相等 + equal: function equal($elem) { + if ($elem.nodeType === 1) { + return this[0] === $elem; + } else { + return this[0] === $elem[0]; + } + }, + + // 将该元素插入到某个元素前面 + insertBefore: function insertBefore(selector) { + var $referenceNode = $(selector); + var referenceNode = $referenceNode[0]; + if (!referenceNode) { + return this; + } + return this.forEach(function (elem) { + var parent = referenceNode.parentNode; + parent.insertBefore(elem, referenceNode); + }); + }, + + // 将该元素插入到某个元素后面 + insertAfter: function insertAfter(selector) { + var $referenceNode = $(selector); + var referenceNode = $referenceNode[0]; + if (!referenceNode) { + return this; + } + return this.forEach(function (elem) { + var parent = referenceNode.parentNode; + if (parent.lastChild === referenceNode) { + // 最后一个元素 + parent.appendChild(elem); + } else { + // 不是最后一个元素 + parent.insertBefore(elem, referenceNode.nextSibling); + } + }); + } +}; + +// new 一个对象 +function $(selector) { + return new DomElement(selector); +} + +// 解绑所有事件,用于销毁编辑器 +$.offAll = function () { + eventList.forEach(function (item) { + var elem = item.elem; + var type = item.type; + var fn = item.fn; + // 解绑 + elem.removeEventListener(type, fn); + }); +}; + +/* + 配置信息 +*/ + +var config = { + + // 默认菜单配置 + menus: ['head', 'bold', 'italic', 'underline', 'strikeThrough', 'foreColor', 'backColor', 'link', 'list', 'justify', 'quote', 'emoticon', 'image', 'table', 'video', 'code', 'undo', 'redo'], + + colors: ['#000000', '#eeece0', '#1c487f', '#4d80bf', '#c24f4a', '#8baa4a', '#7b5ba1', '#46acc8', '#f9963b', '#ffffff'], + + // // 语言配置 + // lang: { + // '设置标题': 'title', + // '正文': 'p', + // '链接文字': 'link text', + // '链接': 'link', + // '插入': 'insert', + // '创建': 'init' + // }, + + // 表情 + emotions: [{ + // tab 的标题 + title: '默认', + // type -> 'emoji' / 'image' + type: 'image', + // content -> 数组 + content: [{ + alt: '[坏笑]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/50/pcmoren_huaixiao_org.png' + }, { + alt: '[舔屏]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/40/pcmoren_tian_org.png' + }, { + alt: '[污]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3c/pcmoren_wu_org.png' + }, { + alt: '[允悲]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/2c/moren_yunbei_org.png' + }, { + alt: '[笑而不语]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3a/moren_xiaoerbuyu_org.png' + }, { + alt: '[费解]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/3c/moren_feijie_org.png' + }, { + alt: '[憧憬]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/37/moren_chongjing_org.png' + }, { + alt: '[并不简单]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/fc/moren_bbjdnew_org.png' + }, { + alt: '[微笑]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/5c/huanglianwx_org.gif' + }, { + alt: '[酷]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/8a/pcmoren_cool2017_org.png' + }, { + alt: '[嘻嘻]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/tootha_org.gif' + }, { + alt: '[哈哈]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6a/laugh.gif' + }, { + alt: '[可爱]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/14/tza_org.gif' + }, { + alt: '[可怜]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/af/kl_org.gif' + }, { + alt: '[挖鼻]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/0b/wabi_org.gif' + }, { + alt: '[吃惊]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/f4/cj_org.gif' + }, { + alt: '[害羞]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6e/shamea_org.gif' + }, { + alt: '[挤眼]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/c3/zy_org.gif' + }, { + alt: '[闭嘴]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/29/bz_org.gif' + }, { + alt: '[鄙视]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/71/bs2_org.gif' + }, { + alt: '[爱你]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6d/lovea_org.gif' + }, { + alt: '[泪]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/9d/sada_org.gif' + }, { + alt: '[偷笑]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/19/heia_org.gif' + }, { + alt: '[亲亲]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/8f/qq_org.gif' + }, { + alt: '[生病]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/b6/sb_org.gif' + }, { + alt: '[太开心]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/58/mb_org.gif' + }, { + alt: '[白眼]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/d9/landeln_org.gif' + }, { + alt: '[右哼哼]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/98/yhh_org.gif' + }, { + alt: '[左哼哼]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/6d/zhh_org.gif' + }, { + alt: '[嘘]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/a6/x_org.gif' + }, { + alt: '[衰]', + src: 'http://img.t.sinajs.cn/t4/appstyle/expression/ext/normal/af/cry.gif' + }] + }, { + // tab 的标题 + title: '新浪', + // type -> 'emoji' / 'image' + type: 'image', + // content -> 数组 + content: [{ + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/7a/shenshou_thumb.gif', + alt: '[草泥马]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/60/horse2_thumb.gif', + alt: '[神马]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/fuyun_thumb.gif', + alt: '[浮云]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c9/geili_thumb.gif', + alt: '[给力]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/f2/wg_thumb.gif', + alt: '[围观]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/70/vw_thumb.gif', + alt: '[威武]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/6e/panda_thumb.gif', + alt: '[熊猫]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/81/rabbit_thumb.gif', + alt: '[兔子]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/bc/otm_thumb.gif', + alt: '[奥特曼]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/15/j_thumb.gif', + alt: '[囧]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/89/hufen_thumb.gif', + alt: '[互粉]' + }, { + src: 'http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c4/liwu_thumb.gif', + alt: '[礼物]' + }] + }, { + // tab 的标题 + title: 'emoji', + // type -> 'emoji' / 'image' + type: 'emoji', + // content -> 数组 + content: '😀 😃 😄 😁 😆 😅 😂 😊 😇 🙂 🙃 😉 😌 😍 😘 😗 😙 😚 😋 😜 😝 😛 🤑 🤗 🤓 😎 😏 😒 😞 😔 😟 😕 🙁 😣 😖 😫 😩 😤 😠 😡 😶 😐 😑 😯 😦 😧 😮 😲 😵 😳 😱 😨 😰 😢 😥 😭 😓 😪 😴 🙄 🤔 😬 🤐'.split(/\s/) + }], + + // 编辑区域的 z-index + zIndex: 10000, + + // 是否开启 debug 模式(debug 模式下错误会 throw error 形式抛出) + debug: false, + + // 插入链接时候的格式校验 + linkCheck: function linkCheck(text, link) { + // text 是插入的文字 + // link 是插入的链接 + return true; // 返回 true 即表示成功 + // return '校验失败' // 返回字符串即表示失败的提示信息 + }, + + // 插入网络图片的校验 + linkImgCheck: function linkImgCheck(src) { + // src 即图片的地址 + return true; // 返回 true 即表示成功 + // return '校验失败' // 返回字符串即表示失败的提示信息 + }, + + // 粘贴过滤样式,默认开启 + pasteFilterStyle: true, + + // 对粘贴的文字进行自定义处理,返回处理后的结果。编辑器会将处理后的结果粘贴到编辑区域中。 + // IE 暂时不支持 + pasteTextHandle: function pasteTextHandle(content) { + // content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回 + return content; + }, + + // onchange 事件 + // onchange: function (html) { + // // html 即变化之后的内容 + // console.log(html) + // }, + + // 是否显示添加网络图片的 tab + showLinkImg: true, + + // 插入网络图片的回调 + linkImgCallback: function linkImgCallback(url) { + // console.log(url) // url 即插入图片的地址 + }, + + // 默认上传图片 max size: 5M + uploadImgMaxSize: 5 * 1024 * 1024, + + // 配置一次最多上传几个图片 + // uploadImgMaxLength: 5, + + // 上传图片,是否显示 base64 格式 + uploadImgShowBase64: false, + + // 上传图片,server 地址(如果有值,则 base64 格式的配置则失效) + // uploadImgServer: '/upload', + + // 自定义配置 filename + uploadFileName: '', + + // 上传图片的自定义参数 + uploadImgParams: { + // token: 'abcdef12345' + }, + + // 上传图片的自定义header + uploadImgHeaders: { + // 'Accept': 'text/x-json' + }, + + // 配置 XHR withCredentials + withCredentials: false, + + // 自定义上传图片超时时间 ms + uploadImgTimeout: 10000, + + // 上传图片 hook + uploadImgHooks: { + // customInsert: function (insertLinkImg, result, editor) { + // console.log('customInsert') + // // 图片上传并返回结果,自定义插入图片的事件,而不是编辑器自动插入图片 + // const data = result.data1 || [] + // data.forEach(link => { + // insertLinkImg(link) + // }) + // }, + before: function before(xhr, editor, files) { + // 图片上传之前触发 + + // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 + // return { + // prevent: true, + // msg: '放弃上传' + // } + }, + success: function success(xhr, editor, result) { + // 图片上传并返回结果,图片插入成功之后触发 + }, + fail: function fail(xhr, editor, result) { + // 图片上传并返回结果,但图片插入错误时触发 + }, + error: function error(xhr, editor) { + // 图片上传出错时触发 + }, + timeout: function timeout(xhr, editor) { + // 图片上传超时时触发 + } + }, + + // 是否上传七牛云,默认为 false + qiniu: false + +}; + +/* + 工具 +*/ + +// 和 UA 相关的属性 +var UA = { + _ua: navigator.userAgent, + + // 是否 webkit + isWebkit: function isWebkit() { + var reg = /webkit/i; + return reg.test(this._ua); + }, + + // 是否 IE + isIE: function isIE() { + return 'ActiveXObject' in window; + } +}; + +// 遍历对象 +function objForEach(obj, fn) { + var key = void 0, + result = void 0; + for (key in obj) { + if (obj.hasOwnProperty(key)) { + result = fn.call(obj, key, obj[key]); + if (result === false) { + break; + } + } + } +} + +// 遍历类数组 +function arrForEach(fakeArr, fn) { + var i = void 0, + item = void 0, + result = void 0; + var length = fakeArr.length || 0; + for (i = 0; i < length; i++) { + item = fakeArr[i]; + result = fn.call(fakeArr, item, i); + if (result === false) { + break; + } + } +} + +// 获取随机数 +function getRandom(prefix) { + return prefix + Math.random().toString().slice(2); +} + +// 替换 html 特殊字符 +function replaceHtmlSymbol(html) { + if (html == null) { + return ''; + } + return html.replace(//gm, '>').replace(/"/gm, '"'); +} + +// 返回百分比的格式 + + +// 判断是不是 function +function isFunction(fn) { + return typeof fn === 'function'; +} + +/* + bold-menu +*/ +// 构造函数 +function Bold(editor) { + this.editor = editor; + this.$elem = $('
\n \n
'); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Bold.prototype = { + constructor: Bold, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 bold 命令 + editor.cmd.do('bold'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('bold')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 替换多语言 + */ + +var replaceLang = function (editor, str) { + var langArgs = editor.config.langArgs || []; + var result = str; + + langArgs.forEach(function (item) { + var reg = item.reg; + var val = item.val; + + if (reg.test(result)) { + result = result.replace(reg, function () { + return val; + }); + } + }); + + return result; +}; + +/* + droplist +*/ +var _emptyFn = function _emptyFn() {}; + +// 构造函数 +function DropList(menu, opt) { + var _this = this; + + // droplist 所依附的菜单 + var editor = menu.editor; + this.menu = menu; + this.opt = opt; + // 容器 + var $container = $('
'); + + // 标题 + var $title = opt.$title; + var titleHtml = void 0; + if ($title) { + // 替换多语言 + titleHtml = $title.html(); + titleHtml = replaceLang(editor, titleHtml); + $title.html(titleHtml); + + $title.addClass('w-e-dp-title'); + $container.append($title); + } + + var list = opt.list || []; + var type = opt.type || 'list'; // 'list' 列表形式(如“标题”菜单) / 'inline-block' 块状形式(如“颜色”菜单) + var onClick = opt.onClick || _emptyFn; + + // 加入 DOM 并绑定事件 + var $list = $('
    '); + $container.append($list); + list.forEach(function (item) { + var $elem = item.$elem; + + // 替换多语言 + var elemHtml = $elem.html(); + elemHtml = replaceLang(editor, elemHtml); + $elem.html(elemHtml); + + var value = item.value; + var $li = $('
  • '); + if ($elem) { + $li.append($elem); + $list.append($li); + $li.on('click', function (e) { + onClick(value); + + // 隐藏 + _this.hideTimeoutId = setTimeout(function () { + _this.hide(); + }, 0); + }); + } + }); + + // 绑定隐藏事件 + $container.on('mouseleave', function (e) { + _this.hideTimeoutId = setTimeout(function () { + _this.hide(); + }, 0); + }); + + // 记录属性 + this.$container = $container; + + // 基本属性 + this._rendered = false; + this._show = false; +} + +// 原型 +DropList.prototype = { + constructor: DropList, + + // 显示(插入DOM) + show: function show() { + if (this.hideTimeoutId) { + // 清除之前的定时隐藏 + clearTimeout(this.hideTimeoutId); + } + + var menu = this.menu; + var $menuELem = menu.$elem; + var $container = this.$container; + if (this._show) { + return; + } + if (this._rendered) { + // 显示 + $container.show(); + } else { + // 加入 DOM 之前先定位位置 + var menuHeight = $menuELem.getSizeData().height || 0; + var width = this.opt.width || 100; // 默认为 100 + $container.css('margin-top', menuHeight + 'px').css('width', width + 'px'); + + // 加入到 DOM + $menuELem.append($container); + this._rendered = true; + } + + // 修改属性 + this._show = true; + }, + + // 隐藏(移除DOM) + hide: function hide() { + if (this.showTimeoutId) { + // 清除之前的定时显示 + clearTimeout(this.showTimeoutId); + } + + var $container = this.$container; + if (!this._show) { + return; + } + // 隐藏并需改属性 + $container.hide(); + this._show = false; + } +}; + +/* + menu - header +*/ +// 构造函数 +function Head(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
    '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 100, + $title: $('

    设置标题

    '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $('

    H1

    '), value: '

    ' }, { $elem: $('

    H2

    '), value: '

    ' }, { $elem: $('

    H3

    '), value: '

    ' }, { $elem: $('

    H4

    '), value: '

    ' }, { $elem: $('

    H5
    '), value: '
    ' }, { $elem: $('

    正文

    '), value: '

    ' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 Head 对象 + _this._command(value); + } + }); +} + +// 原型 +Head.prototype = { + constructor: Head, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (editor.$textElem.equal($selectionElem)) { + // 不能选中多行来设置标题,否则会出现问题 + // 例如选中的是

    xxx

    yyy

    来设置标题,设置之后会成为

    xxx
    yyy

    不符合预期 + return; + } + + editor.cmd.do('formatBlock', value); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var reg = /^h/i; + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + if (reg.test(cmdValue)) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + panel +*/ + +var emptyFn = function emptyFn() {}; + +// 记录已经显示 panel 的菜单 +var _isCreatedPanelMenus = []; + +// 构造函数 +function Panel(menu, opt) { + this.menu = menu; + this.opt = opt; +} + +// 原型 +Panel.prototype = { + constructor: Panel, + + // 显示(插入DOM) + show: function show() { + var _this = this; + + var menu = this.menu; + if (_isCreatedPanelMenus.indexOf(menu) >= 0) { + // 该菜单已经创建了 panel 不能再创建 + return; + } + + var editor = menu.editor; + var $body = $('body'); + var $textContainerElem = editor.$textContainerElem; + var opt = this.opt; + + // panel 的容器 + var $container = $('
    '); + var width = opt.width || 300; // 默认 300px + $container.css('width', width + 'px').css('margin-left', (0 - width) / 2 + 'px'); + + // 添加关闭按钮 + var $closeBtn = $(''); + $container.append($closeBtn); + $closeBtn.on('click', function () { + _this.hide(); + }); + + // 准备 tabs 容器 + var $tabTitleContainer = $('
      '); + var $tabContentContainer = $('
      '); + $container.append($tabTitleContainer).append($tabContentContainer); + + // 设置高度 + var height = opt.height; + if (height) { + $tabContentContainer.css('height', height + 'px').css('overflow-y', 'auto'); + } + + // tabs + var tabs = opt.tabs || []; + var tabTitleArr = []; + var tabContentArr = []; + tabs.forEach(function (tab, tabIndex) { + if (!tab) { + return; + } + var title = tab.title || ''; + var tpl = tab.tpl || ''; + + // 替换多语言 + title = replaceLang(editor, title); + tpl = replaceLang(editor, tpl); + + // 添加到 DOM + var $title = $('
    • ' + title + '
    • '); + $tabTitleContainer.append($title); + var $content = $(tpl); + $tabContentContainer.append($content); + + // 记录到内存 + $title._index = tabIndex; + tabTitleArr.push($title); + tabContentArr.push($content); + + // 设置 active 项 + if (tabIndex === 0) { + $title._active = true; + $title.addClass('w-e-active'); + } else { + $content.hide(); + } + + // 绑定 tab 的事件 + $title.on('click', function (e) { + if ($title._active) { + return; + } + // 隐藏所有的 tab + tabTitleArr.forEach(function ($title) { + $title._active = false; + $title.removeClass('w-e-active'); + }); + tabContentArr.forEach(function ($content) { + $content.hide(); + }); + + // 显示当前的 tab + $title._active = true; + $title.addClass('w-e-active'); + $content.show(); + }); + }); + + // 绑定关闭事件 + $container.on('click', function (e) { + // 点击时阻止冒泡 + e.stopPropagation(); + }); + $body.on('click', function (e) { + _this.hide(); + }); + + // 添加到 DOM + $textContainerElem.append($container); + + // 绑定 opt 的事件,只有添加到 DOM 之后才能绑定成功 + tabs.forEach(function (tab, index) { + if (!tab) { + return; + } + var events = tab.events || []; + events.forEach(function (event) { + var selector = event.selector; + var type = event.type; + var fn = event.fn || emptyFn; + var $content = tabContentArr[index]; + $content.find(selector).on(type, function (e) { + e.stopPropagation(); + var needToHide = fn(e); + // 执行完事件之后,是否要关闭 panel + if (needToHide) { + _this.hide(); + } + }); + }); + }); + + // focus 第一个 elem + var $inputs = $container.find('input[type=text],textarea'); + if ($inputs.length) { + $inputs.get(0).focus(); + } + + // 添加到属性 + this.$container = $container; + + // 隐藏其他 panel + this._hideOtherPanels(); + // 记录该 menu 已经创建了 panel + _isCreatedPanelMenus.push(menu); + }, + + // 隐藏(移除DOM) + hide: function hide() { + var menu = this.menu; + var $container = this.$container; + if ($container) { + $container.remove(); + } + + // 将该 menu 记录中移除 + _isCreatedPanelMenus = _isCreatedPanelMenus.filter(function (item) { + if (item === menu) { + return false; + } else { + return true; + } + }); + }, + + // 一个 panel 展示时,隐藏其他 panel + _hideOtherPanels: function _hideOtherPanels() { + if (!_isCreatedPanelMenus.length) { + return; + } + _isCreatedPanelMenus.forEach(function (menu) { + var panel = menu.panel || {}; + if (panel.hide) { + panel.hide(); + } + }); + } +}; + +/* + menu - link +*/ +// 构造函数 +function Link(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Link.prototype = { + constructor: Link, + + // 点击事件 + onClick: function onClick(e) { + var editor = this.editor; + var $linkelem = void 0; + + if (this._active) { + // 当前选区在链接里面 + $linkelem = editor.selection.getSelectionContainerElem(); + if (!$linkelem) { + return; + } + // 将该元素都包含在选取之内,以便后面整体替换 + editor.selection.createRangeByElem($linkelem); + editor.selection.restoreSelection(); + // 显示 panel + this._createPanel($linkelem.text(), $linkelem.attr('href')); + } else { + // 当前选区不在链接里面 + if (editor.selection.isSelectionEmpty()) { + // 选区是空的,未选中内容 + this._createPanel('', ''); + } else { + // 选中内容了 + this._createPanel(editor.selection.getSelectionText(), ''); + } + } + }, + + // 创建 panel + _createPanel: function _createPanel(text, link) { + var _this = this; + + // panel 中需要用到的id + var inputLinkId = getRandom('input-link'); + var inputTextId = getRandom('input-text'); + var btnOkId = getRandom('btn-ok'); + var btnDelId = getRandom('btn-del'); + + // 是否显示“删除链接” + var delBtnDisplay = this._active ? 'inline-block' : 'none'; + + // 初始化并显示 panel + var panel = new Panel(this, { + width: 300, + // panel 中可包含多个 tab + tabs: [{ + // tab 的标题 + title: '链接', + // 模板 + tpl: '
      \n \n \n
      \n \n \n
      \n
      ', + // 事件绑定 + events: [ + // 插入链接 + { + selector: '#' + btnOkId, + type: 'click', + fn: function fn() { + // 执行插入链接 + var $link = $('#' + inputLinkId); + var $text = $('#' + inputTextId); + var link = $link.val(); + var text = $text.val(); + _this._insertLink(text, link); + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, + // 删除链接 + { + selector: '#' + btnDelId, + type: 'click', + fn: function fn() { + // 执行删除链接 + _this._delLink(); + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // tab end + ] // tabs end + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 删除当前链接 + _delLink: function _delLink() { + if (!this._active) { + return; + } + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var selectionText = editor.selection.getSelectionText(); + editor.cmd.do('insertHTML', '' + selectionText + ''); + }, + + // 插入链接 + _insertLink: function _insertLink(text, link) { + var editor = this.editor; + var config = editor.config; + var linkCheck = config.linkCheck; + var checkResult = true; // 默认为 true + if (linkCheck && typeof linkCheck === 'function') { + checkResult = linkCheck(text, link); + } + if (checkResult === true) { + editor.cmd.do('insertHTML', '' + text + ''); + } else { + alert(checkResult); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + if ($selectionELem.getNodeName() === 'A') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + italic-menu +*/ +// 构造函数 +function Italic(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Italic.prototype = { + constructor: Italic, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 italic 命令 + editor.cmd.do('italic'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('italic')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + redo-menu +*/ +// 构造函数 +function Redo(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Redo.prototype = { + constructor: Redo, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + + // 执行 redo 命令 + editor.cmd.do('redo'); + } +}; + +/* + strikeThrough-menu +*/ +// 构造函数 +function StrikeThrough(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +StrikeThrough.prototype = { + constructor: StrikeThrough, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 strikeThrough 命令 + editor.cmd.do('strikeThrough'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('strikeThrough')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + underline-menu +*/ +// 构造函数 +function Underline(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Underline.prototype = { + constructor: Underline, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + var isSeleEmpty = editor.selection.isSelectionEmpty(); + + if (isSeleEmpty) { + // 选区是空的,插入并选中一个“空白” + editor.selection.createEmptyRange(); + } + + // 执行 underline 命令 + editor.cmd.do('underline'); + + if (isSeleEmpty) { + // 需要将选取折叠起来 + editor.selection.collapseRange(); + editor.selection.restoreSelection(); + } + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('underline')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + undo-menu +*/ +// 构造函数 +function Undo(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Undo.prototype = { + constructor: Undo, + + // 点击事件 + onClick: function onClick(e) { + // 点击菜单将触发这里 + + var editor = this.editor; + + // 执行 undo 命令 + editor.cmd.do('undo'); + } +}; + +/* + menu - list +*/ +// 构造函数 +function List(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      设置列表

      '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $(' 有序列表'), value: 'insertOrderedList' }, { $elem: $(' 无序列表'), value: 'insertUnorderedList' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); + } + }); +} + +// 原型 +List.prototype = { + constructor: List, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + var $textElem = editor.$textElem; + editor.selection.restoreSelection(); + if (editor.cmd.queryCommandState(value)) { + return; + } + editor.cmd.do(value); + + // 验证列表是否被包裹在

      之内 + var $selectionElem = editor.selection.getSelectionContainerElem(); + if ($selectionElem.getNodeName() === 'LI') { + $selectionElem = $selectionElem.parent(); + } + if (/^ol|ul$/i.test($selectionElem.getNodeName()) === false) { + return; + } + if ($selectionElem.equal($textElem)) { + // 证明是顶级标签,没有被

      包裹 + return; + } + var $parent = $selectionElem.parent(); + if ($parent.equal($textElem)) { + // $parent 是顶级标签,不能删除 + return; + } + + $selectionElem.insertAfter($parent); + $parent.remove(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor.cmd.queryCommandState('insertUnOrderedList') || editor.cmd.queryCommandState('insertOrderedList')) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - justify +*/ +// 构造函数 +function Justify(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('

      '); + this.type = 'droplist'; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 100, + $title: $('

      对齐方式

      '), + type: 'list', // droplist 以列表形式展示 + list: [{ $elem: $(' 靠左'), value: 'justifyLeft' }, { $elem: $(' 居中'), value: 'justifyCenter' }, { $elem: $(' 靠右'), value: 'justifyRight' }], + onClick: function onClick(value) { + // 注意 this 是指向当前的 List 对象 + _this._command(value); + } + }); +} + +// 原型 +Justify.prototype = { + constructor: Justify, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do(value); + } +}; + +/* + menu - Forecolor +*/ +// 构造函数 +function ForeColor(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 获取配置的颜色 + var config = editor.config; + var colors = config.colors || []; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      文字颜色

      '), + type: 'inline-block', // droplist 内容以 block 形式展示 + list: colors.map(function (color) { + return { $elem: $(''), value: color }; + }), + onClick: function onClick(value) { + // 注意 this 是指向当前的 ForeColor 对象 + _this._command(value); + } + }); +} + +// 原型 +ForeColor.prototype = { + constructor: ForeColor, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do('foreColor', value); + } +}; + +/* + menu - BackColor +*/ +// 构造函数 +function BackColor(editor) { + var _this = this; + + this.editor = editor; + this.$elem = $('
      '); + this.type = 'droplist'; + + // 获取配置的颜色 + var config = editor.config; + var colors = config.colors || []; + + // 当前是否 active 状态 + this._active = false; + + // 初始化 droplist + this.droplist = new DropList(this, { + width: 120, + $title: $('

      背景色

      '), + type: 'inline-block', // droplist 内容以 block 形式展示 + list: colors.map(function (color) { + return { $elem: $(''), value: color }; + }), + onClick: function onClick(value) { + // 注意 this 是指向当前的 BackColor 对象 + _this._command(value); + } + }); +} + +// 原型 +BackColor.prototype = { + constructor: BackColor, + + // 执行命令 + _command: function _command(value) { + var editor = this.editor; + editor.cmd.do('backColor', value); + } +}; + +/* + menu - quote +*/ +// 构造函数 +function Quote(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'click'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Quote.prototype = { + constructor: Quote, + + onClick: function onClick(e) { + var editor = this.editor; + var $selectionElem = editor.selection.getSelectionContainerElem(); + var nodeName = $selectionElem.getNodeName(); + + if (!UA.isIE()) { + if (nodeName === 'BLOCKQUOTE') { + // 撤销 quote + editor.cmd.do('formatBlock', '

      '); + } else { + // 转换为 quote + editor.cmd.do('formatBlock', '

      '); + } + return; + } + + // IE 中不支持 formatBlock
      ,要用其他方式兼容 + var content = void 0, + $targetELem = void 0; + if (nodeName === 'P') { + // 将 P 转换为 quote + content = $selectionElem.text(); + $targetELem = $('
      ' + content + '
      '); + $targetELem.insertAfter($selectionElem); + $selectionElem.remove(); + return; + } + if (nodeName === 'BLOCKQUOTE') { + // 撤销 quote + content = $selectionElem.text(); + $targetELem = $('

      ' + content + '

      '); + $targetELem.insertAfter($selectionElem); + $selectionElem.remove(); + } + }, + + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var reg = /^BLOCKQUOTE$/i; + var cmdValue = editor.cmd.queryCommandValue('formatBlock'); + if (reg.test(cmdValue)) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - code +*/ +// 构造函数 +function Code(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Code.prototype = { + constructor: Code, + + onClick: function onClick(e) { + var editor = this.editor; + var $startElem = editor.selection.getSelectionStartElem(); + var $endElem = editor.selection.getSelectionEndElem(); + var isSeleEmpty = editor.selection.isSelectionEmpty(); + var selectionText = editor.selection.getSelectionText(); + var $code = void 0; + + if (!$startElem.equal($endElem)) { + // 跨元素选择,不做处理 + editor.selection.restoreSelection(); + return; + } + if (!isSeleEmpty) { + // 选取不是空,用 包裹即可 + $code = $('' + selectionText + ''); + editor.cmd.do('insertElem', $code); + editor.selection.createRangeByElem($code, false); + editor.selection.restoreSelection(); + return; + } + + // 选取是空,且没有夸元素选择,则插入
      
      +        if (this._active) {
      +            // 选中状态,将编辑内容
      +            this._createPanel($startElem.html());
      +        } else {
      +            // 未选中状态,将创建内容
      +            this._createPanel();
      +        }
      +    },
      +
      +    _createPanel: function _createPanel(value) {
      +        var _this = this;
      +
      +        // value - 要编辑的内容
      +        value = value || '';
      +        var type = !value ? 'new' : 'edit';
      +        var textId = getRandom('texxt');
      +        var btnId = getRandom('btn');
      +
      +        var panel = new Panel(this, {
      +            width: 500,
      +            // 一个 Panel 包含多个 tab
      +            tabs: [{
      +                // 标题
      +                title: '插入代码',
      +                // 模板
      +                tpl: '
      \n \n
      \n \n
      \n
      ', + // 事件绑定 + events: [ + // 插入代码 + { + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textId); + var text = $text.val() || $text.html(); + text = replaceHtmlSymbol(text); + if (type === 'new') { + // 新插入 + _this._insertCode(text); + } else { + // 编辑更新 + _this._updateCode(text); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // new Panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入代码 + _insertCode: function _insertCode(value) { + var editor = this.editor; + editor.cmd.do('insertHTML', '
      ' + value + '


      '); + }, + + // 更新代码 + _updateCode: function _updateCode(value) { + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + $selectionELem.html(value); + editor.selection.restoreSelection(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var $parentElem = $selectionELem.parent(); + if ($selectionELem.getNodeName() === 'CODE' && $parentElem.getNodeName() === 'PRE') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - emoticon +*/ +// 构造函数 +function Emoticon(editor) { + this.editor = editor; + this.$elem = $('
      \n \n
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Emoticon.prototype = { + constructor: Emoticon, + + onClick: function onClick() { + this._createPanel(); + }, + + _createPanel: function _createPanel() { + var _this = this; + + var editor = this.editor; + var config = editor.config; + // 获取表情配置 + var emotions = config.emotions || []; + + // 创建表情 dropPanel 的配置 + var tabConfig = []; + emotions.forEach(function (emotData) { + var emotType = emotData.type; + var content = emotData.content || []; + + // 这一组表情最终拼接出来的 html + var faceHtml = ''; + + // emoji 表情 + if (emotType === 'emoji') { + content.forEach(function (item) { + if (item) { + faceHtml += '' + item + ''; + } + }); + } + // 图片表情 + if (emotType === 'image') { + content.forEach(function (item) { + var src = item.src; + var alt = item.alt; + if (src) { + // 加一个 data-w-e 属性,点击图片的时候不再提示编辑图片 + faceHtml += '' + alt + ''; + } + }); + } + + tabConfig.push({ + title: emotData.title, + tpl: '
      ' + faceHtml + '
      ', + events: [{ + selector: 'span.w-e-item', + type: 'click', + fn: function fn(e) { + var target = e.target; + var $target = $(target); + var nodeName = $target.getNodeName(); + + var insertHtml = void 0; + if (nodeName === 'IMG') { + // 插入图片 + insertHtml = $target.parent().html(); + } else { + // 插入 emoji + insertHtml = '' + $target.html() + ''; + } + + _this._insert(insertHtml); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }); + }); + + var panel = new Panel(this, { + width: 300, + height: 200, + // 一个 Panel 包含多个 tab + tabs: tabConfig + }); + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表情 + _insert: function _insert(emotHtml) { + var editor = this.editor; + editor.cmd.do('insertHTML', emotHtml); + } +}; + +/* + menu - table +*/ +// 构造函数 +function Table(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Table.prototype = { + constructor: Table, + + onClick: function onClick() { + if (this._active) { + // 编辑现有表格 + this._createEditPanel(); + } else { + // 插入新表格 + this._createInsertPanel(); + } + }, + + // 创建插入新表格的 panel + _createInsertPanel: function _createInsertPanel() { + var _this = this; + + // 用到的 id + var btnInsertId = getRandom('btn'); + var textRowNum = getRandom('row'); + var textColNum = getRandom('col'); + + var panel = new Panel(this, { + width: 250, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '插入表格', + // 模板 + tpl: '
      \n

      \n \u521B\u5EFA\n \n \u884C\n \n \u5217\u7684\u8868\u683C\n

      \n
      \n \n
      \n
      ', + // 事件绑定 + events: [{ + // 点击按钮,插入表格 + selector: '#' + btnInsertId, + type: 'click', + fn: function fn() { + var rowNum = parseInt($('#' + textRowNum).val()); + var colNum = parseInt($('#' + textColNum).val()); + + if (rowNum && colNum && rowNum > 0 && colNum > 0) { + // form 数据有效 + _this._insert(rowNum, colNum); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 展示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入表格 + _insert: function _insert(rowNum, colNum) { + // 拼接 table 模板 + var r = void 0, + c = void 0; + var html = '
      '; + for (r = 0; r < rowNum; r++) { + html += ''; + if (r === 0) { + for (c = 0; c < colNum; c++) { + html += ''; + } + } else { + for (c = 0; c < colNum; c++) { + html += ''; + } + } + html += ''; + } + html += '
        


      '; + + // 执行命令 + var editor = this.editor; + editor.cmd.do('insertHTML', html); + + // 防止 firefox 下出现 resize 的控制点 + editor.cmd.do('enableObjectResizing', false); + editor.cmd.do('enableInlineTableEditing', false); + }, + + // 创建编辑表格的 panel + _createEditPanel: function _createEditPanel() { + var _this2 = this; + + // 可用的 id + var addRowBtnId = getRandom('add-row'); + var addColBtnId = getRandom('add-col'); + var delRowBtnId = getRandom('del-row'); + var delColBtnId = getRandom('del-col'); + var delTableBtnId = getRandom('del-table'); + + // 创建 panel 对象 + var panel = new Panel(this, { + width: 320, + // panel 包含多个 tab + tabs: [{ + // 标题 + title: '编辑表格', + // 模板 + tpl: '
      \n
      \n \n \n \n \n
      \n
      \n \n \n
      ', + // 事件绑定 + events: [{ + // 增加行 + selector: '#' + addRowBtnId, + type: 'click', + fn: function fn() { + _this2._addRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 增加列 + selector: '#' + addColBtnId, + type: 'click', + fn: function fn() { + _this2._addCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除行 + selector: '#' + delRowBtnId, + type: 'click', + fn: function fn() { + _this2._delRow(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除列 + selector: '#' + delColBtnId, + type: 'click', + fn: function fn() { + _this2._delCol(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + // 删除表格 + selector: '#' + delTableBtnId, + type: 'click', + fn: function fn() { + _this2._delTable(); + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }] + }); + // 显示 panel + panel.show(); + }, + + // 获取选中的单元格的位置信息 + _getLocationData: function _getLocationData() { + var result = {}; + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName !== 'TD' && nodeName !== 'TH') { + return; + } + + // 获取 td index + var $tr = $selectionELem.parent(); + var $tds = $tr.children(); + var tdLength = $tds.length; + $tds.forEach(function (td, index) { + if (td === $selectionELem[0]) { + // 记录并跳出循环 + result.td = { + index: index, + elem: td, + length: tdLength + }; + return false; + } + }); + + // 获取 tr index + var $tbody = $tr.parent(); + var $trs = $tbody.children(); + var trLength = $trs.length; + $trs.forEach(function (tr, index) { + if (tr === $tr[0]) { + // 记录并跳出循环 + result.tr = { + index: index, + elem: tr, + length: trLength + }; + return false; + } + }); + + // 返回结果 + return result; + }, + + // 增加行 + _addRow: function _addRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + var tdData = locationData.td; + var tdLength = tdData.length; + + // 拼接即将插入的字符串 + var newTr = document.createElement('tr'); + var tpl = '', + i = void 0; + for (i = 0; i < tdLength; i++) { + tpl += ' '; + } + newTr.innerHTML = tpl; + // 插入 + $(newTr).insertAfter($currentTr); + }, + + // 增加列 + _addCol: function _addCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + var name = $currentTd.getNodeName().toLowerCase(); + + // new 一个 td,并插入 + var newTd = document.createElement(name); + $(newTd).insertAfter($currentTd); + }); + }, + + // 删除行 + _delRow: function _delRow() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var $currentTr = $(trData.elem); + $currentTr.remove(); + }, + + // 删除列 + _delCol: function _delCol() { + // 获取当前单元格的位置信息 + var locationData = this._getLocationData(); + if (!locationData) { + return; + } + var trData = locationData.tr; + var tdData = locationData.td; + var tdIndex = tdData.index; + var $currentTr = $(trData.elem); + var $trParent = $currentTr.parent(); + var $trs = $trParent.children(); + + // 遍历所有行 + $trs.forEach(function (tr) { + var $tr = $(tr); + var $tds = $tr.children(); + var $currentTd = $tds.get(tdIndex); + // 删除 + $currentTd.remove(); + }); + }, + + // 删除表格 + _delTable: function _delTable() { + var editor = this.editor; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var $table = $selectionELem.parentUntil('table'); + if (!$table) { + return; + } + $table.remove(); + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + var $selectionELem = editor.selection.getSelectionContainerElem(); + if (!$selectionELem) { + return; + } + var nodeName = $selectionELem.getNodeName(); + if (nodeName === 'TD' || nodeName === 'TH') { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + menu - video +*/ +// 构造函数 +function Video(editor) { + this.editor = editor; + this.$elem = $('
      '); + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Video.prototype = { + constructor: Video, + + onClick: function onClick() { + this._createPanel(); + }, + + _createPanel: function _createPanel() { + var _this = this; + + // 创建 id + var textValId = getRandom('text-val'); + var btnId = getRandom('btn'); + + // 创建 panel + var panel = new Panel(this, { + width: 350, + // 一个 panel 多个 tab + tabs: [{ + // 标题 + title: '插入视频', + // 模板 + tpl: '
      \n \n
      \n \n
      \n
      ', + // 事件绑定 + events: [{ + selector: '#' + btnId, + type: 'click', + fn: function fn() { + var $text = $('#' + textValId); + var val = $text.val().trim(); + + // 测试用视频地址 + // + + if (val) { + // 插入视频 + _this._insert(val); + } + + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + } // first tab end + ] // tabs end + }); // panel end + + // 显示 panel + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 插入视频 + _insert: function _insert(val) { + var editor = this.editor; + editor.cmd.do('insertHTML', val + '


      '); + } +}; + +/* + menu - img +*/ +// 构造函数 +function Image(editor) { + this.editor = editor; + var imgMenuId = getRandom('w-e-img'); + this.$elem = $('
      '); + editor.imgMenuId = imgMenuId; + this.type = 'panel'; + + // 当前是否 active 状态 + this._active = false; +} + +// 原型 +Image.prototype = { + constructor: Image, + + onClick: function onClick() { + var editor = this.editor; + var config = editor.config; + if (config.qiniu) { + return; + } + if (this._active) { + this._createEditPanel(); + } else { + this._createInsertPanel(); + } + }, + + _createEditPanel: function _createEditPanel() { + var editor = this.editor; + + // id + var width30 = getRandom('width-30'); + var width50 = getRandom('width-50'); + var width100 = getRandom('width-100'); + var delBtn = getRandom('del-btn'); + + // tab 配置 + var tabsConfig = [{ + title: '编辑图片', + tpl: '
      \n
      \n \u6700\u5927\u5BBD\u5EA6\uFF1A\n \n \n \n
      \n
      \n \n \n
      ', + events: [{ + selector: '#' + width30, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '30%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width50, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '50%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + width100, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.css('max-width', '100%'); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }, { + selector: '#' + delBtn, + type: 'click', + fn: function fn() { + var $img = editor._selectedImg; + if ($img) { + $img.remove(); + } + // 返回 true,表示该事件执行完之后,panel 要关闭。否则 panel 不会关闭 + return true; + } + }] + }]; + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfig + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + _createInsertPanel: function _createInsertPanel() { + var editor = this.editor; + var uploadImg = editor.uploadImg; + var config = editor.config; + + // id + var upTriggerId = getRandom('up-trigger'); + var upFileId = getRandom('up-file'); + var linkUrlId = getRandom('link-url'); + var linkBtnId = getRandom('link-btn'); + + // tabs 的配置 + var tabsConfig = [{ + title: '上传图片', + tpl: '
      \n
      \n \n
      \n
      \n \n
      \n
      ', + events: [{ + // 触发选择图片 + selector: '#' + upTriggerId, + type: 'click', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (fileElem) { + fileElem.click(); + } else { + // 返回 true 可关闭 panel + return true; + } + } + }, { + // 选择图片完毕 + selector: '#' + upFileId, + type: 'change', + fn: function fn() { + var $file = $('#' + upFileId); + var fileElem = $file[0]; + if (!fileElem) { + // 返回 true 可关闭 panel + return true; + } + + // 获取选中的 file 对象列表 + var fileList = fileElem.files; + if (fileList.length) { + uploadImg.uploadImg(fileList); + } + + // 返回 true 可关闭 panel + return true; + } + }] + }, // first tab end + { + title: '网络图片', + tpl: '
      \n \n
      \n \n
      \n
      ', + events: [{ + selector: '#' + linkBtnId, + type: 'click', + fn: function fn() { + var $linkUrl = $('#' + linkUrlId); + var url = $linkUrl.val().trim(); + + if (url) { + uploadImg.insertLinkImg(url); + } + + // 返回 true 表示函数执行结束之后关闭 panel + return true; + } + }] + } // second tab end + ]; // tabs end + + // 判断 tabs 的显示 + var tabsConfigResult = []; + if ((config.uploadImgShowBase64 || config.uploadImgServer || config.customUploadImg) && window.FileReader) { + // 显示“上传图片” + tabsConfigResult.push(tabsConfig[0]); + } + if (config.showLinkImg) { + // 显示“网络图片” + tabsConfigResult.push(tabsConfig[1]); + } + + // 创建 panel 并显示 + var panel = new Panel(this, { + width: 300, + tabs: tabsConfigResult + }); + panel.show(); + + // 记录属性 + this.panel = panel; + }, + + // 试图改变 active 状态 + tryChangeActive: function tryChangeActive(e) { + var editor = this.editor; + var $elem = this.$elem; + if (editor._selectedImg) { + this._active = true; + $elem.addClass('w-e-active'); + } else { + this._active = false; + $elem.removeClass('w-e-active'); + } + } +}; + +/* + 所有菜单的汇总 +*/ + +// 存储菜单的构造函数 +var MenuConstructors = {}; + +MenuConstructors.bold = Bold; + +MenuConstructors.head = Head; + +MenuConstructors.link = Link; + +MenuConstructors.italic = Italic; + +MenuConstructors.redo = Redo; + +MenuConstructors.strikeThrough = StrikeThrough; + +MenuConstructors.underline = Underline; + +MenuConstructors.undo = Undo; + +MenuConstructors.list = List; + +MenuConstructors.justify = Justify; + +MenuConstructors.foreColor = ForeColor; + +MenuConstructors.backColor = BackColor; + +MenuConstructors.quote = Quote; + +MenuConstructors.code = Code; + +MenuConstructors.emoticon = Emoticon; + +MenuConstructors.table = Table; + +MenuConstructors.video = Video; + +MenuConstructors.image = Image; + +/* + 菜单集合 +*/ +// 构造函数 +function Menus(editor) { + this.editor = editor; + this.menus = {}; +} + +// 修改原型 +Menus.prototype = { + constructor: Menus, + + // 初始化菜单 + init: function init() { + var _this = this; + + var editor = this.editor; + var config = editor.config || {}; + var configMenus = config.menus || []; // 获取配置中的菜单 + + // 根据配置信息,创建菜单 + configMenus.forEach(function (menuKey) { + var MenuConstructor = MenuConstructors[menuKey]; + if (MenuConstructor && typeof MenuConstructor === 'function') { + // 创建单个菜单 + _this.menus[menuKey] = new MenuConstructor(editor); + } + }); + + // 添加到菜单栏 + this._addToToolbar(); + + // 绑定事件 + this._bindEvent(); + }, + + // 添加到菜单栏 + _addToToolbar: function _addToToolbar() { + var editor = this.editor; + var $toolbarElem = editor.$toolbarElem; + var menus = this.menus; + var config = editor.config; + // config.zIndex 是配置的编辑区域的 z-index,菜单的 z-index 得在其基础上 +1 + var zIndex = config.zIndex + 1; + objForEach(menus, function (key, menu) { + var $elem = menu.$elem; + if ($elem) { + // 设置 z-index + $elem.css('z-index', zIndex); + $toolbarElem.append($elem); + } + }); + }, + + // 绑定菜单 click mouseenter 事件 + _bindEvent: function _bindEvent() { + var menus = this.menus; + var editor = this.editor; + objForEach(menus, function (key, menu) { + var type = menu.type; + if (!type) { + return; + } + var $elem = menu.$elem; + var droplist = menu.droplist; + var panel = menu.panel; + + // 点击类型,例如 bold + if (type === 'click' && menu.onClick) { + $elem.on('click', function (e) { + if (editor.selection.getRange() == null) { + return; + } + menu.onClick(e); + }); + } + + // 下拉框,例如 head + if (type === 'droplist' && droplist) { + $elem.on('mouseenter', function (e) { + if (editor.selection.getRange() == null) { + return; + } + // 显示 + droplist.showTimeoutId = setTimeout(function () { + droplist.show(); + }, 200); + }).on('mouseleave', function (e) { + // 隐藏 + droplist.hideTimeoutId = setTimeout(function () { + droplist.hide(); + }, 0); + }); + } + + // 弹框类型,例如 link + if (type === 'panel' && menu.onClick) { + $elem.on('click', function (e) { + e.stopPropagation(); + if (editor.selection.getRange() == null) { + return; + } + // 在自定义事件中显示 panel + menu.onClick(e); + }); + } + }); + }, + + // 尝试修改菜单状态 + changeActive: function changeActive() { + var menus = this.menus; + objForEach(menus, function (key, menu) { + if (menu.tryChangeActive) { + setTimeout(function () { + menu.tryChangeActive(); + }, 100); + } + }); + } +}; + +/* + 粘贴信息的处理 +*/ + +// 获取粘贴的纯文本 +function getPasteText(e) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + } + + return replaceHtmlSymbol(pasteText); +} + +// 获取粘贴的html +function getPasteHtml(e, filterStyle) { + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData; + var pasteText = void 0, + pasteHtml = void 0; + if (clipboardData == null) { + pasteText = window.clipboardData && window.clipboardData.getData('text'); + } else { + pasteText = clipboardData.getData('text/plain'); + pasteHtml = clipboardData.getData('text/html'); + } + if (!pasteHtml && pasteText) { + pasteHtml = '

      ' + replaceHtmlSymbol(pasteText) + '

      '; + } + if (!pasteHtml) { + return; + } + + // 过滤word中状态过来的无用字符 + var docSplitHtml = pasteHtml.split(''); + if (docSplitHtml.length === 2) { + pasteHtml = docSplitHtml[0]; + } + + // 过滤无用标签 + pasteHtml = pasteHtml.replace(/<(meta|script|link).+?>/igm, ''); + // 去掉注释 + pasteHtml = pasteHtml.replace(//mg, ''); + // 过滤 data-xxx 属性 + pasteHtml = pasteHtml.replace(/\s?data-.+?=('|").+?('|")/igm, ''); + + if (filterStyle) { + // 过滤样式 + pasteHtml = pasteHtml.replace(/\s?(class|style)=('|").+?('|")/igm, ''); + } else { + // 保留样式 + pasteHtml = pasteHtml.replace(/\s?class=('|").+?('|")/igm, ''); + } + + return pasteHtml; +} + +// 获取粘贴的图片文件 +function getPasteImgs(e) { + var result = []; + var txt = getPasteText(e); + if (txt) { + // 有文字,就忽略图片 + return result; + } + + var clipboardData = e.clipboardData || e.originalEvent && e.originalEvent.clipboardData || {}; + var items = clipboardData.items; + if (!items) { + return result; + } + + objForEach(items, function (key, value) { + var type = value.type; + if (/image/i.test(type)) { + result.push(value.getAsFile()); + } + }); + + return result; +} + +/* + 编辑区域 +*/ + +// 获取一个 elem.childNodes 的 JSON 数据 +function getChildrenJSON($elem) { + var result = []; + var $children = $elem.childNodes() || []; // 注意 childNodes() 可以获取文本节点 + $children.forEach(function (curElem) { + var elemResult = void 0; + var nodeType = curElem.nodeType; + + // 文本节点 + if (nodeType === 3) { + elemResult = curElem.textContent; + } + + // 普通 DOM 节点 + if (nodeType === 1) { + elemResult = {}; + + // tag + elemResult.tag = curElem.nodeName.toLowerCase(); + // attr + var attrData = []; + var attrList = curElem.attributes || {}; + var attrListLength = attrList.length || 0; + for (var i = 0; i < attrListLength; i++) { + var attr = attrList[i]; + attrData.push({ + name: attr.name, + value: attr.value + }); + } + elemResult.attrs = attrData; + // children(递归) + elemResult.children = getChildrenJSON($(curElem)); + } + + result.push(elemResult); + }); + return result; +} + +// 构造函数 +function Text(editor) { + this.editor = editor; +} + +// 修改原型 +Text.prototype = { + constructor: Text, + + // 初始化 + init: function init() { + // 绑定事件 + this._bindEvent(); + }, + + // 清空内容 + clear: function clear() { + this.html('


      '); + }, + + // 获取 设置 html + html: function html(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + var html = void 0; + if (val == null) { + html = $textElem.html(); + // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 ​ ,这里替换掉 + html = html.replace(/\u200b/gm, ''); + return html; + } else { + $textElem.html(val); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 获取 JSON + getJSON: function getJSON() { + var editor = this.editor; + var $textElem = editor.$textElem; + return getChildrenJSON($textElem); + }, + + // 获取 设置 text + text: function text(val) { + var editor = this.editor; + var $textElem = editor.$textElem; + var text = void 0; + if (val == null) { + text = $textElem.text(); + // 未选中任何内容的时候点击“加粗”或者“斜体”等按钮,就得需要一个空的占位符 ​ ,这里替换掉 + text = text.replace(/\u200b/gm, ''); + return text; + } else { + $textElem.text('

      ' + val + '

      '); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + } + }, + + // 追加内容 + append: function append(html) { + var editor = this.editor; + var $textElem = editor.$textElem; + $textElem.append($(html)); + + // 初始化选取,将光标定位到内容尾部 + editor.initSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // 实时保存选取 + this._saveRangeRealTime(); + + // 按回车建时的特殊处理 + this._enterKeyHandle(); + + // 清空时保留


      + this._clearHandle(); + + // 粘贴事件(粘贴文字,粘贴图片) + this._pasteHandle(); + + // tab 特殊处理 + this._tabHandle(); + + // img 点击 + this._imgHandle(); + + // 拖拽事件 + this._dragHandle(); + }, + + // 实时保存选取 + _saveRangeRealTime: function _saveRangeRealTime() { + var editor = this.editor; + var $textElem = editor.$textElem; + + // 保存当前的选区 + function saveRange(e) { + // 随时保存选区 + editor.selection.saveRange(); + // 更新按钮 ative 状态 + editor.menus.changeActive(); + } + // 按键后保存 + $textElem.on('keyup', saveRange); + $textElem.on('mousedown', function (e) { + // mousedown 状态下,鼠标滑动到编辑区域外面,也需要保存选区 + $textElem.on('mouseleave', saveRange); + }); + $textElem.on('mouseup', function (e) { + saveRange(); + // 在编辑器区域之内完成点击,取消鼠标滑动到编辑区外面的事件 + $textElem.off('mouseleave', saveRange); + }); + }, + + // 按回车键时的特殊处理 + _enterKeyHandle: function _enterKeyHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + function insertEmptyP($selectionElem) { + var $p = $('


      '); + $p.insertBefore($selectionElem); + editor.selection.createRangeByElem($p, true); + editor.selection.restoreSelection(); + $selectionElem.remove(); + } + + // 将回车之后生成的非

      的顶级标签,改为

      + function pHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + var $parentElem = $selectionElem.parent(); + + if ($parentElem.html() === '
      ') { + // 回车之前光标所在一个

      .....

      ,忽然回车生成一个空的


      + // 而且继续回车跳不出去,因此只能特殊处理 + insertEmptyP($selectionElem); + return; + } + + if (!$parentElem.equal($textElem)) { + // 不是顶级标签 + return; + } + + var nodeName = $selectionElem.getNodeName(); + if (nodeName === 'P') { + // 当前的标签是 P ,不用做处理 + return; + } + + if ($selectionElem.text()) { + // 有内容,不做处理 + return; + } + + // 插入

      ,并将选取定位到

      ,删除当前标签 + insertEmptyP($selectionElem); + } + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + return; + } + // 将回车之后生成的非

      的顶级标签,改为

      + pHandle(e); + }); + + //

      回车时 特殊处理 + function codeHandle(e) { + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName !== 'CODE' || parentNodeName !== 'PRE') { + // 不符合要求 忽略 + return; + } + + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + + // 处理:光标定位到代码末尾,联系点击两次回车,即跳出代码块 + if (editor._willBreakCode === true) { + // 此时可以跳出代码块 + // 插入

      ,并将选取定位到

      + var $p = $('


      '); + $p.insertAfter($parentElem); + editor.selection.createRangeByElem($p, true); + editor.selection.restoreSelection(); + + // 修改状态 + editor._willBreakCode = false; + + e.preventDefault(); + return; + } + + var _startOffset = editor.selection.getRange().startOffset; + + // 处理:回车时,不能插入
      而是插入 \n ,因为是在 pre 标签里面 + editor.cmd.do('insertHTML', '\n'); + editor.selection.saveRange(); + if (editor.selection.getRange().startOffset === _startOffset) { + // 没起作用,再来一遍 + editor.cmd.do('insertHTML', '\n'); + } + + var codeLength = $selectionElem.html().length; + if (editor.selection.getRange().startOffset + 1 === codeLength) { + // 说明光标在代码最后的位置,执行了回车操作 + // 记录下来,以便下次回车时候跳出 code + editor._willBreakCode = true; + } + + // 阻止默认行为 + e.preventDefault(); + } + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 13) { + // 不是回车键 + // 取消即将跳转代码块的记录 + editor._willBreakCode = false; + return; + } + //
      回车时 特殊处理 + codeHandle(e); + }); + }, + + // 清空时保留


      + _clearHandle: function _clearHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 8) { + return; + } + var txtHtml = $textElem.html().toLowerCase().trim(); + if (txtHtml === '


      ') { + // 最后剩下一个空行,就不再删除了 + e.preventDefault(); + return; + } + }); + + $textElem.on('keyup', function (e) { + if (e.keyCode !== 8) { + return; + } + var $p = void 0; + var txtHtml = $textElem.html().toLowerCase().trim(); + + // firefox 时用 txtHtml === '
      ' 判断,其他用 !txtHtml 判断 + if (!txtHtml || txtHtml === '
      ') { + // 内容空了 + $p = $('


      '); + $textElem.html(''); // 一定要先清空,否则在 firefox 下有问题 + $textElem.append($p); + editor.selection.createRangeByElem($p, false, true); + editor.selection.restoreSelection(); + } + }); + }, + + // 粘贴事件(粘贴文字 粘贴图片) + _pasteHandle: function _pasteHandle() { + var editor = this.editor; + var config = editor.config; + var pasteFilterStyle = config.pasteFilterStyle; + var pasteTextHandle = config.pasteTextHandle; + var $textElem = editor.$textElem; + + // 粘贴图片、文本的事件,每次只能执行一个 + // 判断该次粘贴事件是否可以执行 + var pasteTime = 0; + function canDo() { + var now = Date.now(); + var flag = false; + if (now - pasteTime >= 500) { + // 间隔大于 500 ms ,可以执行 + flag = true; + } + pasteTime = now; + return flag; + } + function resetTime() { + pasteTime = 0; + } + + // 粘贴文字 + $textElem.on('paste', function (e) { + if (UA.isIE()) { + return; + } else { + // 阻止默认行为,使用 execCommand 的粘贴命令 + e.preventDefault(); + } + + // 粘贴图片和文本,只能同时使用一个 + if (!canDo()) { + return; + } + + // 获取粘贴的文字 + var pasteHtml = getPasteHtml(e, pasteFilterStyle); + var pasteText = getPasteText(e); + pasteText = pasteText.replace(/\n/gm, '
      '); + + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中只能粘贴纯文本 + if (nodeName === 'CODE' || nodeName === 'PRE') { + if (pasteTextHandle && isFunction(pasteTextHandle)) { + // 用户自定义过滤处理粘贴内容 + pasteText = '' + (pasteTextHandle(pasteText) || ''); + } + editor.cmd.do('insertHTML', '

      ' + pasteText + '

      '); + return; + } + + // 先放开注释,有问题再追查 ———— + // // 表格中忽略,可能会出现异常问题 + // if (nodeName === 'TD' || nodeName === 'TH') { + // return + // } + + if (!pasteHtml) { + // 没有内容,可继续执行下面的图片粘贴 + resetTime(); + return; + } + try { + // firefox 中,获取的 pasteHtml 可能是没有
        包裹的
      • + // 因此执行 insertHTML 会报错 + if (pasteTextHandle && isFunction(pasteTextHandle)) { + // 用户自定义过滤处理粘贴内容 + pasteHtml = '' + (pasteTextHandle(pasteHtml) || ''); + } + editor.cmd.do('insertHTML', pasteHtml); + } catch (ex) { + // 此时使用 pasteText 来兼容一下 + if (pasteTextHandle && isFunction(pasteTextHandle)) { + // 用户自定义过滤处理粘贴内容 + pasteText = '' + (pasteTextHandle(pasteText) || ''); + } + editor.cmd.do('insertHTML', '

        ' + pasteText + '

        '); + } + }); + + // 粘贴图片 + $textElem.on('paste', function (e) { + if (UA.isIE()) { + return; + } else { + e.preventDefault(); + } + + // 粘贴图片和文本,只能同时使用一个 + if (!canDo()) { + return; + } + + // 获取粘贴的图片 + var pasteFiles = getPasteImgs(e); + if (!pasteFiles || !pasteFiles.length) { + return; + } + + // 获取当前的元素 + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var nodeName = $selectionElem.getNodeName(); + + // code 中粘贴忽略 + if (nodeName === 'CODE' || nodeName === 'PRE') { + return; + } + + // 上传图片 + var uploadImg = editor.uploadImg; + uploadImg.uploadImg(pasteFiles); + }); + }, + + // tab 特殊处理 + _tabHandle: function _tabHandle() { + var editor = this.editor; + var $textElem = editor.$textElem; + + $textElem.on('keydown', function (e) { + if (e.keyCode !== 9) { + return; + } + if (!editor.cmd.queryCommandSupported('insertHTML')) { + // 必须原生支持 insertHTML 命令 + return; + } + var $selectionElem = editor.selection.getSelectionContainerElem(); + if (!$selectionElem) { + return; + } + var $parentElem = $selectionElem.parent(); + var selectionNodeName = $selectionElem.getNodeName(); + var parentNodeName = $parentElem.getNodeName(); + + if (selectionNodeName === 'CODE' && parentNodeName === 'PRE') { + //
         里面
        +                editor.cmd.do('insertHTML', '    ');
        +            } else {
        +                // 普通文字
        +                editor.cmd.do('insertHTML', '    ');
        +            }
        +
        +            e.preventDefault();
        +        });
        +    },
        +
        +    // img 点击
        +    _imgHandle: function _imgHandle() {
        +        var editor = this.editor;
        +        var $textElem = editor.$textElem;
        +
        +        // 为图片增加 selected 样式
        +        $textElem.on('click', 'img', function (e) {
        +            var img = this;
        +            var $img = $(img);
        +
        +            if ($img.attr('data-w-e') === '1') {
        +                // 是表情图片,忽略
        +                return;
        +            }
        +
        +            // 记录当前点击过的图片
        +            editor._selectedImg = $img;
        +
        +            // 修改选区并 restore ,防止用户此时点击退格键,会删除其他内容
        +            editor.selection.createRangeByElem($img);
        +            editor.selection.restoreSelection();
        +        });
        +
        +        // 去掉图片的 selected 样式
        +        $textElem.on('click  keyup', function (e) {
        +            if (e.target.matches('img')) {
        +                // 点击的是图片,忽略
        +                return;
        +            }
        +            // 删除记录
        +            editor._selectedImg = null;
        +        });
        +    },
        +
        +    // 拖拽事件
        +    _dragHandle: function _dragHandle() {
        +        var editor = this.editor;
        +
        +        // 禁用 document 拖拽事件
        +        var $document = $(document);
        +        $document.on('dragleave drop dragenter dragover', function (e) {
        +            e.preventDefault();
        +        });
        +
        +        // 添加编辑区域拖拽事件
        +        var $textElem = editor.$textElem;
        +        $textElem.on('drop', function (e) {
        +            e.preventDefault();
        +            var files = e.dataTransfer && e.dataTransfer.files;
        +            if (!files || !files.length) {
        +                return;
        +            }
        +
        +            // 上传图片
        +            var uploadImg = editor.uploadImg;
        +            uploadImg.uploadImg(files);
        +        });
        +    }
        +};
        +
        +/*
        +    命令,封装 document.execCommand
        +*/
        +
        +// 构造函数
        +function Command(editor) {
        +    this.editor = editor;
        +}
        +
        +// 修改原型
        +Command.prototype = {
        +    constructor: Command,
        +
        +    // 执行命令
        +    do: function _do(name, value) {
        +        var editor = this.editor;
        +
        +        // 使用 styleWithCSS
        +        if (!editor._useStyleWithCSS) {
        +            document.execCommand('styleWithCSS', null, true);
        +            editor._useStyleWithCSS = true;
        +        }
        +
        +        // 如果无选区,忽略
        +        if (!editor.selection.getRange()) {
        +            return;
        +        }
        +
        +        // 恢复选取
        +        editor.selection.restoreSelection();
        +
        +        // 执行
        +        var _name = '_' + name;
        +        if (this[_name]) {
        +            // 有自定义事件
        +            this[_name](value);
        +        } else {
        +            // 默认 command
        +            this._execCommand(name, value);
        +        }
        +
        +        // 修改菜单状态
        +        editor.menus.changeActive();
        +
        +        // 最后,恢复选取保证光标在原来的位置闪烁
        +        editor.selection.saveRange();
        +        editor.selection.restoreSelection();
        +
        +        // 触发 onchange
        +        editor.change && editor.change();
        +    },
        +
        +    // 自定义 insertHTML 事件
        +    _insertHTML: function _insertHTML(html) {
        +        var editor = this.editor;
        +        var range = editor.selection.getRange();
        +
        +        if (this.queryCommandSupported('insertHTML')) {
        +            // W3C
        +            this._execCommand('insertHTML', html);
        +        } else if (range.insertNode) {
        +            // IE
        +            range.deleteContents();
        +            range.insertNode($(html)[0]);
        +        } else if (range.pasteHTML) {
        +            // IE <= 10
        +            range.pasteHTML(html);
        +        }
        +    },
        +
        +    // 插入 elem
        +    _insertElem: function _insertElem($elem) {
        +        var editor = this.editor;
        +        var range = editor.selection.getRange();
        +
        +        if (range.insertNode) {
        +            range.deleteContents();
        +            range.insertNode($elem[0]);
        +        }
        +    },
        +
        +    // 封装 execCommand
        +    _execCommand: function _execCommand(name, value) {
        +        document.execCommand(name, false, value);
        +    },
        +
        +    // 封装 document.queryCommandValue
        +    queryCommandValue: function queryCommandValue(name) {
        +        return document.queryCommandValue(name);
        +    },
        +
        +    // 封装 document.queryCommandState
        +    queryCommandState: function queryCommandState(name) {
        +        return document.queryCommandState(name);
        +    },
        +
        +    // 封装 document.queryCommandSupported
        +    queryCommandSupported: function queryCommandSupported(name) {
        +        return document.queryCommandSupported(name);
        +    }
        +};
        +
        +/*
        +    selection range API
        +*/
        +
        +// 构造函数
        +function API(editor) {
        +    this.editor = editor;
        +    this._currentRange = null;
        +}
        +
        +// 修改原型
        +API.prototype = {
        +    constructor: API,
        +
        +    // 获取 range 对象
        +    getRange: function getRange() {
        +        return this._currentRange;
        +    },
        +
        +    // 保存选区
        +    saveRange: function saveRange(_range) {
        +        if (_range) {
        +            // 保存已有选区
        +            this._currentRange = _range;
        +            return;
        +        }
        +
        +        // 获取当前的选区
        +        var selection = window.getSelection();
        +        if (selection.rangeCount === 0) {
        +            return;
        +        }
        +        var range = selection.getRangeAt(0);
        +
        +        // 判断选区内容是否在编辑内容之内
        +        var $containerElem = this.getSelectionContainerElem(range);
        +        if (!$containerElem) {
        +            return;
        +        }
        +        var editor = this.editor;
        +        var $textElem = editor.$textElem;
        +        if ($textElem.isContain($containerElem)) {
        +            // 是编辑内容之内的
        +            this._currentRange = range;
        +        }
        +    },
        +
        +    // 折叠选区
        +    collapseRange: function collapseRange(toStart) {
        +        if (toStart == null) {
        +            // 默认为 false
        +            toStart = false;
        +        }
        +        var range = this._currentRange;
        +        if (range) {
        +            range.collapse(toStart);
        +        }
        +    },
        +
        +    // 选中区域的文字
        +    getSelectionText: function getSelectionText() {
        +        var range = this._currentRange;
        +        if (range) {
        +            return this._currentRange.toString();
        +        } else {
        +            return '';
        +        }
        +    },
        +
        +    // 选区的 $Elem
        +    getSelectionContainerElem: function getSelectionContainerElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.commonAncestorContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +    getSelectionStartElem: function getSelectionStartElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.startContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +    getSelectionEndElem: function getSelectionEndElem(range) {
        +        range = range || this._currentRange;
        +        var elem = void 0;
        +        if (range) {
        +            elem = range.endContainer;
        +            return $(elem.nodeType === 1 ? elem : elem.parentNode);
        +        }
        +    },
        +
        +    // 选区是否为空
        +    isSelectionEmpty: function isSelectionEmpty() {
        +        var range = this._currentRange;
        +        if (range && range.startContainer) {
        +            if (range.startContainer === range.endContainer) {
        +                if (range.startOffset === range.endOffset) {
        +                    return true;
        +                }
        +            }
        +        }
        +        return false;
        +    },
        +
        +    // 恢复选区
        +    restoreSelection: function restoreSelection() {
        +        var selection = window.getSelection();
        +        selection.removeAllRanges();
        +        selection.addRange(this._currentRange);
        +    },
        +
        +    // 创建一个空白(即 ​ 字符)选区
        +    createEmptyRange: function createEmptyRange() {
        +        var editor = this.editor;
        +        var range = this.getRange();
        +        var $elem = void 0;
        +
        +        if (!range) {
        +            // 当前无 range
        +            return;
        +        }
        +        if (!this.isSelectionEmpty()) {
        +            // 当前选区必须没有内容才可以
        +            return;
        +        }
        +
        +        try {
        +            // 目前只支持 webkit 内核
        +            if (UA.isWebkit()) {
        +                // 插入 ​
        +                editor.cmd.do('insertHTML', '​');
        +                // 修改 offset 位置
        +                range.setEnd(range.endContainer, range.endOffset + 1);
        +                // 存储
        +                this.saveRange(range);
        +            } else {
        +                $elem = $('');
        +                editor.cmd.do('insertElem', $elem);
        +                this.createRangeByElem($elem, true);
        +            }
        +        } catch (ex) {
        +            // 部分情况下会报错,兼容一下
        +        }
        +    },
        +
        +    // 根据 $Elem 设置选区
        +    createRangeByElem: function createRangeByElem($elem, toStart, isContent) {
        +        // $elem - 经过封装的 elem
        +        // toStart - true 开始位置,false 结束位置
        +        // isContent - 是否选中Elem的内容
        +        if (!$elem.length) {
        +            return;
        +        }
        +
        +        var elem = $elem[0];
        +        var range = document.createRange();
        +
        +        if (isContent) {
        +            range.selectNodeContents(elem);
        +        } else {
        +            range.selectNode(elem);
        +        }
        +
        +        if (typeof toStart === 'boolean') {
        +            range.collapse(toStart);
        +        }
        +
        +        // 存储 range
        +        this.saveRange(range);
        +    }
        +};
        +
        +/*
        +    上传进度条
        +*/
        +
        +function Progress(editor) {
        +    this.editor = editor;
        +    this._time = 0;
        +    this._isShow = false;
        +    this._isRender = false;
        +    this._timeoutId = 0;
        +    this.$textContainer = editor.$textContainerElem;
        +    this.$bar = $('
        '); +} + +Progress.prototype = { + constructor: Progress, + + show: function show(progress) { + var _this = this; + + // 状态处理 + if (this._isShow) { + return; + } + this._isShow = true; + + // 渲染 + var $bar = this.$bar; + if (!this._isRender) { + var $textContainer = this.$textContainer; + $textContainer.append($bar); + } else { + this._isRender = true; + } + + // 改变进度(节流,100ms 渲染一次) + if (Date.now() - this._time > 100) { + if (progress <= 1) { + $bar.css('width', progress * 100 + '%'); + this._time = Date.now(); + } + } + + // 隐藏 + var timeoutId = this._timeoutId; + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(function () { + _this._hide(); + }, 500); + }, + + _hide: function _hide() { + var $bar = this.$bar; + $bar.remove(); + + // 修改状态 + this._time = 0; + this._isShow = false; + this._isRender = false; + } +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + +/* + 上传图片 +*/ + +// 构造函数 +function UploadImg(editor) { + this.editor = editor; +} + +// 原型 +UploadImg.prototype = { + constructor: UploadImg, + + // 根据 debug 弹出不同的信息 + _alert: function _alert(alertInfo, debugInfo) { + var editor = this.editor; + var debug = editor.config.debug; + var customAlert = editor.config.customAlert; + + if (debug) { + throw new Error('wangEditor: ' + (debugInfo || alertInfo)); + } else { + if (customAlert && typeof customAlert === 'function') { + customAlert(alertInfo); + } else { + alert(alertInfo); + } + } + }, + + // 根据链接插入图片 + insertLinkImg: function insertLinkImg(link) { + var _this2 = this; + + if (!link) { + return; + } + var editor = this.editor; + var config = editor.config; + + // 校验格式 + var linkImgCheck = config.linkImgCheck; + var checkResult = void 0; + if (linkImgCheck && typeof linkImgCheck === 'function') { + checkResult = linkImgCheck(link); + if (typeof checkResult === 'string') { + // 校验失败,提示信息 + alert(checkResult); + return; + } + } + + editor.cmd.do('insertHTML', ''); + + // 验证图片 url 是否有效,无效的话给出提示 + var img = document.createElement('img'); + img.onload = function () { + var callback = config.linkImgCallback; + if (callback && typeof callback === 'function') { + callback(link); + } + + img = null; + }; + img.onerror = function () { + img = null; + // 无法成功下载图片 + _this2._alert('插入图片错误', 'wangEditor: \u63D2\u5165\u56FE\u7247\u51FA\u9519\uFF0C\u56FE\u7247\u94FE\u63A5\u662F "' + link + '"\uFF0C\u4E0B\u8F7D\u8BE5\u94FE\u63A5\u5931\u8D25'); + return; + }; + img.onabort = function () { + img = null; + }; + img.src = link; + }, + + // 上传图片 + uploadImg: function uploadImg(files) { + var _this3 = this; + + if (!files || !files.length) { + return; + } + + // ------------------------------ 获取配置信息 ------------------------------ + var editor = this.editor; + var config = editor.config; + var uploadImgServer = config.uploadImgServer; + var uploadImgShowBase64 = config.uploadImgShowBase64; + + var maxSize = config.uploadImgMaxSize; + var maxSizeM = maxSize / 1024 / 1024; + var maxLength = config.uploadImgMaxLength || 10000; + var uploadFileName = config.uploadFileName || ''; + var uploadImgParams = config.uploadImgParams || {}; + var uploadImgParamsWithUrl = config.uploadImgParamsWithUrl; + var uploadImgHeaders = config.uploadImgHeaders || {}; + var hooks = config.uploadImgHooks || {}; + var timeout = config.uploadImgTimeout || 3000; + var withCredentials = config.withCredentials; + if (withCredentials == null) { + withCredentials = false; + } + var customUploadImg = config.customUploadImg; + + if (!customUploadImg) { + // 没有 customUploadImg 的情况下,需要如下两个配置才能继续进行图片上传 + if (!uploadImgServer && !uploadImgShowBase64) { + return; + } + } + + // ------------------------------ 验证文件信息 ------------------------------ + var resultFiles = []; + var errInfo = []; + arrForEach(files, function (file) { + var name = file.name; + var size = file.size; + + // chrome 低版本 name === undefined + if (!name || !size) { + return; + } + + if (/\.(jpg|jpeg|png|bmp|gif)$/i.test(name) === false) { + // 后缀名不合法,不是图片 + errInfo.push('\u3010' + name + '\u3011\u4E0D\u662F\u56FE\u7247'); + return; + } + if (maxSize < size) { + // 上传图片过大 + errInfo.push('\u3010' + name + '\u3011\u5927\u4E8E ' + maxSizeM + 'M'); + return; + } + + // 验证通过的加入结果列表 + resultFiles.push(file); + }); + // 抛出验证信息 + if (errInfo.length) { + this._alert('图片验证未通过: \n' + errInfo.join('\n')); + return; + } + if (resultFiles.length > maxLength) { + this._alert('一次最多上传' + maxLength + '张图片'); + return; + } + + // ------------------------------ 自定义上传 ------------------------------ + if (customUploadImg && typeof customUploadImg === 'function') { + customUploadImg(resultFiles, this.insertLinkImg.bind(this)); + + // 阻止以下代码执行 + return; + } + + // 添加图片数据 + var formdata = new FormData(); + arrForEach(resultFiles, function (file) { + var name = uploadFileName || file.name; + formdata.append(name, file); + }); + + // ------------------------------ 上传图片 ------------------------------ + if (uploadImgServer && typeof uploadImgServer === 'string') { + // 添加参数 + var uploadImgServerArr = uploadImgServer.split('#'); + uploadImgServer = uploadImgServerArr[0]; + var uploadImgServerHash = uploadImgServerArr[1] || ''; + objForEach(uploadImgParams, function (key, val) { + val = encodeURIComponent(val); + + // 第一,将参数拼接到 url 中 + if (uploadImgParamsWithUrl) { + if (uploadImgServer.indexOf('?') > 0) { + uploadImgServer += '&'; + } else { + uploadImgServer += '?'; + } + uploadImgServer = uploadImgServer + key + '=' + val; + } + + // 第二,将参数添加到 formdata 中 + formdata.append(key, val); + }); + if (uploadImgServerHash) { + uploadImgServer += '#' + uploadImgServerHash; + } + + // 定义 xhr + var xhr = new XMLHttpRequest(); + xhr.open('POST', uploadImgServer); + + // 设置超时 + xhr.timeout = timeout; + xhr.ontimeout = function () { + // hook - timeout + if (hooks.timeout && typeof hooks.timeout === 'function') { + hooks.timeout(xhr, editor); + } + + _this3._alert('上传图片超时'); + }; + + // 监控 progress + if (xhr.upload) { + xhr.upload.onprogress = function (e) { + var percent = void 0; + // 进度条 + var progressBar = new Progress(editor); + if (e.lengthComputable) { + percent = e.loaded / e.total; + progressBar.show(percent); + } + }; + } + + // 返回数据 + xhr.onreadystatechange = function () { + var result = void 0; + if (xhr.readyState === 4) { + if (xhr.status < 200 || xhr.status >= 300) { + // hook - error + if (hooks.error && typeof hooks.error === 'function') { + hooks.error(xhr, editor); + } + + // xhr 返回状态错误 + _this3._alert('上传图片发生错误', '\u4E0A\u4F20\u56FE\u7247\u53D1\u751F\u9519\u8BEF\uFF0C\u670D\u52A1\u5668\u8FD4\u56DE\u72B6\u6001\u662F ' + xhr.status); + return; + } + + result = xhr.responseText; + if ((typeof result === 'undefined' ? 'undefined' : _typeof(result)) !== 'object') { + try { + result = JSON.parse(result); + } catch (ex) { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果是: ' + result); + return; + } + } + if (!hooks.customInsert && result.errno != '0') { + // hook - fail + if (hooks.fail && typeof hooks.fail === 'function') { + hooks.fail(xhr, editor, result); + } + + // 数据错误 + _this3._alert('上传图片失败', '上传图片返回结果错误,返回结果 errno=' + result.errno); + } else { + if (hooks.customInsert && typeof hooks.customInsert === 'function') { + // 使用者自定义插入方法 + hooks.customInsert(_this3.insertLinkImg.bind(_this3), result, editor); + } else { + // 将图片插入编辑器 + var data = result.data || []; + data.forEach(function (link) { + _this3.insertLinkImg(link); + }); + } + + // hook - success + if (hooks.success && typeof hooks.success === 'function') { + hooks.success(xhr, editor, result); + } + } + } + }; + + // hook - before + if (hooks.before && typeof hooks.before === 'function') { + var beforeResult = hooks.before(xhr, editor, resultFiles); + if (beforeResult && (typeof beforeResult === 'undefined' ? 'undefined' : _typeof(beforeResult)) === 'object') { + if (beforeResult.prevent) { + // 如果返回的结果是 {prevent: true, msg: 'xxxx'} 则表示用户放弃上传 + this._alert(beforeResult.msg); + return; + } + } + } + + // 自定义 headers + objForEach(uploadImgHeaders, function (key, val) { + xhr.setRequestHeader(key, val); + }); + + // 跨域传 cookie + xhr.withCredentials = withCredentials; + + // 发送请求 + xhr.send(formdata); + + // 注意,要 return 。不去操作接下来的 base64 显示方式 + return; + } + + // ------------------------------ 显示 base64 格式 ------------------------------ + if (uploadImgShowBase64) { + arrForEach(files, function (file) { + var _this = _this3; + var reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = function () { + _this.insertLinkImg(this.result); + }; + }); + } + } +}; + +/* + 编辑器构造函数 +*/ + +// id,累加 +var editorId = 1; + +// 构造函数 +function Editor(toolbarSelector, textSelector) { + if (toolbarSelector == null) { + // 没有传入任何参数,报错 + throw new Error('错误:初始化编辑器时候未传入任何参数,请查阅文档'); + } + // id,用以区分单个页面不同的编辑器对象 + this.id = 'wangEditor-' + editorId++; + + this.toolbarSelector = toolbarSelector; + this.textSelector = textSelector; + + // 自定义配置 + this.customConfig = {}; +} + +// 修改原型 +Editor.prototype = { + constructor: Editor, + + // 初始化配置 + _initConfig: function _initConfig() { + // _config 是默认配置,this.customConfig 是用户自定义配置,将它们 merge 之后再赋值 + var target = {}; + this.config = Object.assign(target, config, this.customConfig); + + // 将语言配置,生成正则表达式 + var langConfig = this.config.lang || {}; + var langArgs = []; + objForEach(langConfig, function (key, val) { + // key 即需要生成正则表达式的规则,如“插入链接” + // val 即需要被替换成的语言,如“insert link” + langArgs.push({ + reg: new RegExp(key, 'img'), + val: val + + }); + }); + this.config.langArgs = langArgs; + }, + + // 初始化 DOM + _initDom: function _initDom() { + var _this = this; + + var toolbarSelector = this.toolbarSelector; + var $toolbarSelector = $(toolbarSelector); + var textSelector = this.textSelector; + + var config$$1 = this.config; + var zIndex = config$$1.zIndex; + + // 定义变量 + var $toolbarElem = void 0, + $textContainerElem = void 0, + $textElem = void 0, + $children = void 0; + + if (textSelector == null) { + // 只传入一个参数,即是容器的选择器或元素,toolbar 和 text 的元素自行创建 + $toolbarElem = $('
        '); + $textContainerElem = $('
        '); + + // 将编辑器区域原有的内容,暂存起来 + $children = $toolbarSelector.children(); + + // 添加到 DOM 结构中 + $toolbarSelector.append($toolbarElem).append($textContainerElem); + + // 自行创建的,需要配置默认的样式 + $toolbarElem.css('background-color', '#f1f1f1').css('border', '1px solid #ccc'); + $textContainerElem.css('border', '1px solid #ccc').css('border-top', 'none').css('height', '300px'); + } else { + // toolbar 和 text 的选择器都有值,记录属性 + $toolbarElem = $toolbarSelector; + $textContainerElem = $(textSelector); + // 将编辑器区域原有的内容,暂存起来 + $children = $textContainerElem.children(); + } + + // 编辑区域 + $textElem = $('
        '); + $textElem.attr('contenteditable', 'true').css('width', '100%').css('height', '100%'); + + // 初始化编辑区域内容 + if ($children && $children.length) { + $textElem.append($children); + } else { + $textElem.append($('


        ')); + } + + // 编辑区域加入DOM + $textContainerElem.append($textElem); + + // 设置通用的 class + $toolbarElem.addClass('w-e-toolbar'); + $textContainerElem.addClass('w-e-text-container'); + $textContainerElem.css('z-index', zIndex); + $textElem.addClass('w-e-text'); + + // 添加 ID + var toolbarElemId = getRandom('toolbar-elem'); + $toolbarElem.attr('id', toolbarElemId); + var textElemId = getRandom('text-elem'); + $textElem.attr('id', textElemId); + + // 记录属性 + this.$toolbarElem = $toolbarElem; + this.$textContainerElem = $textContainerElem; + this.$textElem = $textElem; + this.toolbarElemId = toolbarElemId; + this.textElemId = textElemId; + + // 记录输入法的开始和结束 + var compositionEnd = true; + $textContainerElem.on('compositionstart', function () { + // 输入法开始输入 + compositionEnd = false; + }); + $textContainerElem.on('compositionend', function () { + // 输入法结束输入 + compositionEnd = true; + }); + + // 绑定 onchange + $textContainerElem.on('click keyup', function () { + // 输入法结束才出发 onchange + compositionEnd && _this.change && _this.change(); + }); + $toolbarElem.on('click', function () { + this.change && this.change(); + }); + + //绑定 onfocus 与 onblur 事件 + if (config$$1.onfocus || config$$1.onblur) { + // 当前编辑器是否是焦点状态 + this.isFocus = false; + + $(document).on('click', function (e) { + //判断当前点击元素是否在编辑器内 + var isChild = $textElem.isContain($(e.target)); + + //判断当前点击元素是否为工具栏 + var isToolbar = $toolbarElem.isContain($(e.target)); + var isMenu = $toolbarElem[0] == e.target ? true : false; + + if (!isChild) { + //若为选择工具栏中的功能,则不视为成blur操作 + if (isToolbar && !isMenu) { + return; + } + + if (_this.isFocus) { + _this.onblur && _this.onblur(); + } + _this.isFocus = false; + } else { + if (!_this.isFocus) { + _this.onfocus && _this.onfocus(); + } + _this.isFocus = true; + } + }); + } + }, + + // 封装 command + _initCommand: function _initCommand() { + this.cmd = new Command(this); + }, + + // 封装 selection range API + _initSelectionAPI: function _initSelectionAPI() { + this.selection = new API(this); + }, + + // 添加图片上传 + _initUploadImg: function _initUploadImg() { + this.uploadImg = new UploadImg(this); + }, + + // 初始化菜单 + _initMenus: function _initMenus() { + this.menus = new Menus(this); + this.menus.init(); + }, + + // 添加 text 区域 + _initText: function _initText() { + this.txt = new Text(this); + this.txt.init(); + }, + + // 初始化选区,将光标定位到内容尾部 + initSelection: function initSelection(newLine) { + var $textElem = this.$textElem; + var $children = $textElem.children(); + if (!$children.length) { + // 如果编辑器区域无内容,添加一个空行,重新设置选区 + $textElem.append($('


        ')); + this.initSelection(); + return; + } + + var $last = $children.last(); + + if (newLine) { + // 新增一个空行 + var html = $last.html().toLowerCase(); + var nodeName = $last.getNodeName(); + if (html !== '
        ' && html !== '' || nodeName !== 'P') { + // 最后一个元素不是


        ,添加一个空行,重新设置选区 + $textElem.append($('


        ')); + this.initSelection(); + return; + } + } + + this.selection.createRangeByElem($last, false, true); + this.selection.restoreSelection(); + }, + + // 绑定事件 + _bindEvent: function _bindEvent() { + // -------- 绑定 onchange 事件 -------- + var onChangeTimeoutId = 0; + var beforeChangeHtml = this.txt.html(); + var config$$1 = this.config; + + // onchange 触发延迟时间 + var onchangeTimeout = config$$1.onchangeTimeout; + onchangeTimeout = parseInt(onchangeTimeout, 10); + if (!onchangeTimeout || onchangeTimeout <= 0) { + onchangeTimeout = 200; + } + + var onchange = config$$1.onchange; + if (onchange && typeof onchange === 'function') { + // 触发 change 的有三个场景: + // 1. $textContainerElem.on('click keyup') + // 2. $toolbarElem.on('click') + // 3. editor.cmd.do() + this.change = function () { + // 判断是否有变化 + var currentHtml = this.txt.html(); + + if (currentHtml.length === beforeChangeHtml.length) { + // 需要比较每一个字符 + if (currentHtml === beforeChangeHtml) { + return; + } + } + + // 执行,使用节流 + if (onChangeTimeoutId) { + clearTimeout(onChangeTimeoutId); + } + onChangeTimeoutId = setTimeout(function () { + // 触发配置的 onchange 函数 + onchange(currentHtml); + beforeChangeHtml = currentHtml; + }, onchangeTimeout); + }; + } + + // -------- 绑定 onblur 事件 -------- + var onblur = config$$1.onblur; + if (onblur && typeof onblur === 'function') { + this.onblur = function () { + var currentHtml = this.txt.html(); + onblur(currentHtml); + }; + } + + // -------- 绑定 onfocus 事件 -------- + var onfocus = config$$1.onfocus; + if (onfocus && typeof onfocus === 'function') { + this.onfocus = function () { + onfocus(); + }; + } + }, + + // 创建编辑器 + create: function create() { + // 初始化配置信息 + this._initConfig(); + + // 初始化 DOM + this._initDom(); + + // 封装 command API + this._initCommand(); + + // 封装 selection range API + this._initSelectionAPI(); + + // 添加 text + this._initText(); + + // 初始化菜单 + this._initMenus(); + + // 添加 图片上传 + this._initUploadImg(); + + // 初始化选区,将光标定位到内容尾部 + this.initSelection(true); + + // 绑定事件 + this._bindEvent(); + }, + + // 解绑所有事件(暂时不对外开放) + _offAllEvent: function _offAllEvent() { + $.offAll(); + } +}; + +// 检验是否浏览器环境 +try { + document; +} catch (ex) { + throw new Error('请在浏览器环境下运行'); +} + +// polyfill +polyfill(); + +// 这里的 `inlinecss` 将被替换成 css 代码的内容,详情可去 ./gulpfile.js 中搜索 `inlinecss` 关键字 +var inlinecss = '.w-e-toolbar,.w-e-text-container,.w-e-menu-panel { padding: 0; margin: 0; box-sizing: border-box;}.w-e-toolbar *,.w-e-text-container *,.w-e-menu-panel * { padding: 0; margin: 0; box-sizing: border-box;}.w-e-clear-fix:after { content: ""; display: table; clear: both;}.w-e-toolbar .w-e-droplist { position: absolute; left: 0; top: 0; background-color: #fff; border: 1px solid #f1f1f1; border-right-color: #ccc; border-bottom-color: #ccc;}.w-e-toolbar .w-e-droplist .w-e-dp-title { text-align: center; color: #999; line-height: 2; border-bottom: 1px solid #f1f1f1; font-size: 13px;}.w-e-toolbar .w-e-droplist ul.w-e-list { list-style: none; line-height: 1;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item { color: #333; padding: 5px 0;}.w-e-toolbar .w-e-droplist ul.w-e-list li.w-e-item:hover { background-color: #f1f1f1;}.w-e-toolbar .w-e-droplist ul.w-e-block { list-style: none; text-align: left; padding: 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item { display: inline-block; *display: inline; *zoom: 1; padding: 3px 5px;}.w-e-toolbar .w-e-droplist ul.w-e-block li.w-e-item:hover { background-color: #f1f1f1;}@font-face { font-family: \'w-e-icon\'; src: url(data:application/x-font-woff;charset=utf-8;base64,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) format(\'truetype\'); font-weight: normal; font-style: normal;}[class^="w-e-icon-"],[class*=" w-e-icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: \'w-e-icon\' !important; speak: none; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;}.w-e-icon-close:before { content: "\\f00d";}.w-e-icon-upload2:before { content: "\\e9c6";}.w-e-icon-trash-o:before { content: "\\f014";}.w-e-icon-header:before { content: "\\f1dc";}.w-e-icon-pencil2:before { content: "\\e906";}.w-e-icon-paint-brush:before { content: "\\f1fc";}.w-e-icon-image:before { content: "\\e90d";}.w-e-icon-play:before { content: "\\e912";}.w-e-icon-location:before { content: "\\e947";}.w-e-icon-undo:before { content: "\\e965";}.w-e-icon-redo:before { content: "\\e966";}.w-e-icon-quotes-left:before { content: "\\e977";}.w-e-icon-list-numbered:before { content: "\\e9b9";}.w-e-icon-list2:before { content: "\\e9bb";}.w-e-icon-link:before { content: "\\e9cb";}.w-e-icon-happy:before { content: "\\e9df";}.w-e-icon-bold:before { content: "\\ea62";}.w-e-icon-underline:before { content: "\\ea63";}.w-e-icon-italic:before { content: "\\ea64";}.w-e-icon-strikethrough:before { content: "\\ea65";}.w-e-icon-table2:before { content: "\\ea71";}.w-e-icon-paragraph-left:before { content: "\\ea77";}.w-e-icon-paragraph-center:before { content: "\\ea78";}.w-e-icon-paragraph-right:before { content: "\\ea79";}.w-e-icon-terminal:before { content: "\\f120";}.w-e-icon-page-break:before { content: "\\ea68";}.w-e-icon-cancel-circle:before { content: "\\ea0d";}.w-e-toolbar { display: -webkit-box; display: -ms-flexbox; display: flex; padding: 0 5px; /* flex-wrap: wrap; */ /* 单个菜单 */}.w-e-toolbar .w-e-menu { position: relative; text-align: center; padding: 5px 10px; cursor: pointer;}.w-e-toolbar .w-e-menu i { color: #999;}.w-e-toolbar .w-e-menu:hover i { color: #333;}.w-e-toolbar .w-e-active i { color: #1e88e5;}.w-e-toolbar .w-e-active:hover i { color: #1e88e5;}.w-e-text-container .w-e-panel-container { position: absolute; top: 0; left: 50%; border: 1px solid #ccc; border-top: 0; box-shadow: 1px 1px 2px #ccc; color: #333; background-color: #fff; /* 为 emotion panel 定制的样式 */ /* 上传图片的 panel 定制样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-close { position: absolute; right: 0; top: 0; padding: 5px; margin: 2px 5px 0 0; cursor: pointer; color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-close:hover { color: #333;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title { list-style: none; display: -webkit-box; display: -ms-flexbox; display: flex; font-size: 14px; margin: 2px 10px 0 10px; border-bottom: 1px solid #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-item { padding: 3px 5px; color: #999; cursor: pointer; margin: 0 3px; position: relative; top: 1px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-title .w-e-active { color: #333; border-bottom: 1px solid #333; cursor: default; font-weight: 700;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content { padding: 10px 15px 10px 15px; font-size: 16px; /* 输入框的样式 */ /* 按钮的样式 */}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus,.w-e-text-container .w-e-panel-container .w-e-panel-tab-content button:focus { outline: none;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea { width: 100%; border: 1px solid #ccc; padding: 5px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content textarea:focus { border-color: #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text] { border: none; border-bottom: 1px solid #ccc; font-size: 14px; height: 20px; color: #333; text-align: left;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].small { width: 30px; text-align: center;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text].block { display: block; width: 100%; margin: 10px 0;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content input[type=text]:focus { border-bottom: 2px solid #1e88e5;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button { font-size: 14px; color: #1e88e5; border: none; padding: 5px 10px; background-color: #fff; cursor: pointer; border-radius: 3px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.left { float: left; margin-right: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.right { float: right; margin-left: 10px;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.gray { color: #999;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button.red { color: #c24f4a;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container button:hover { background-color: #f1f1f1;}.w-e-text-container .w-e-panel-container .w-e-panel-tab-content .w-e-button-container:after { content: ""; display: table; clear: both;}.w-e-text-container .w-e-panel-container .w-e-emoticon-container .w-e-item { cursor: pointer; font-size: 18px; padding: 0 3px; display: inline-block; *display: inline; *zoom: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container { text-align: center;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn { display: inline-block; *display: inline; *zoom: 1; color: #999; cursor: pointer; font-size: 60px; line-height: 1;}.w-e-text-container .w-e-panel-container .w-e-up-img-container .w-e-up-btn:hover { color: #333;}.w-e-text-container { position: relative;}.w-e-text-container .w-e-progress { position: absolute; background-color: #1e88e5; bottom: 0; left: 0; height: 1px;}.w-e-text { padding: 0 10px; overflow-y: scroll;}.w-e-text p,.w-e-text h1,.w-e-text h2,.w-e-text h3,.w-e-text h4,.w-e-text h5,.w-e-text table,.w-e-text pre { margin: 10px 0; line-height: 1.5;}.w-e-text ul,.w-e-text ol { margin: 10px 0 10px 20px;}.w-e-text blockquote { display: block; border-left: 8px solid #d0e5f2; padding: 5px 10px; margin: 10px 0; line-height: 1.4; font-size: 100%; background-color: #f1f1f1;}.w-e-text code { display: inline-block; *display: inline; *zoom: 1; background-color: #f1f1f1; border-radius: 3px; padding: 3px 5px; margin: 0 3px;}.w-e-text pre code { display: block;}.w-e-text table { border-top: 1px solid #ccc; border-left: 1px solid #ccc;}.w-e-text table td,.w-e-text table th { border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; padding: 3px 5px;}.w-e-text table th { border-bottom: 2px solid #ccc; text-align: center;}.w-e-text:focus { outline: none;}.w-e-text img { cursor: pointer;}.w-e-text img:hover { box-shadow: 0 0 5px #333;}'; + +// 将 css 代码添加到