From 14b8e0b1e732f37550da4f02dcdb2e2713f3a531 Mon Sep 17 00:00:00 2001 From: Ronie Martinez Date: Sun, 10 Nov 2019 22:01:10 +0800 Subject: [PATCH 1/4] Update packages - Fix tests - Fix cssmin warnings --- Gruntfile.js | 4 +- dist/css/medium-editor.css | 73 ++++-------- dist/css/medium-editor.min.css | 2 +- dist/css/themes/beagle.css | 6 +- dist/css/themes/beagle.min.css | 2 +- dist/css/themes/bootstrap.css | 3 +- dist/css/themes/bootstrap.min.css | 2 +- dist/css/themes/default.css | 14 +-- dist/css/themes/default.min.css | 2 +- dist/css/themes/flat.css | 21 ++-- dist/css/themes/flat.min.css | 2 +- dist/css/themes/mani.css | 39 +++---- dist/css/themes/mani.min.css | 2 +- dist/css/themes/roman.css | 41 ++++--- dist/css/themes/roman.min.css | 2 +- dist/css/themes/tim.css | 3 +- dist/css/themes/tim.min.css | 2 +- dist/js/medium-editor.js | 135 +++++++++++++++++++---- dist/js/medium-editor.min.js | 5 +- package.json | 49 ++++---- spec/core-api.spec.js | 2 +- spec/full-content.spec.js | 3 +- spec/toolbar.spec.js | 10 +- src/js/core.js | 4 +- src/js/extensions/paste.js | 18 +-- src/sass/components/_anchor-preview.scss | 2 +- src/sass/components/_placeholder.scss | 10 +- src/sass/components/_toolbar-form.scss | 2 +- src/sass/components/_toolbar.scss | 2 +- src/sass/medium-editor.scss | 2 +- src/sass/themes/beagle.scss | 38 ++++--- src/sass/themes/bootstrap.scss | 22 ++-- src/sass/themes/default.scss | 10 +- src/sass/themes/flat.scss | 31 +++--- src/sass/themes/mani.scss | 25 +++-- src/sass/themes/roman.scss | 25 +++-- src/sass/themes/tim.scss | 22 ++-- 37 files changed, 347 insertions(+), 290 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 8544542de..2525de6e8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -126,7 +126,7 @@ module.exports = function (grunt) { specs: ['spec/*.spec.js'], helpers: 'spec/helpers/*.js', vendor: [ - 'node_modules/lodash/index.js', + 'node_modules/lodash/lodash.js', 'spec/vendor/jasmine-jsreporter.js', 'spec/vendor/jasmine-jsreporter-script.js' ], @@ -342,7 +342,7 @@ module.exports = function (grunt) { grunt.registerTask('sauce', ['connect', 'saucelabs-jasmine']); grunt.registerTask('js', ['jshint', 'jscs', 'concat', 'jasmine:suite', 'uglify']); grunt.registerTask('css', ['sass', 'autoprefixer', 'cssmin', 'csslint']); - grunt.registerTask('default', ['js', 'css']); + grunt.registerTask('default', ['css', 'js']); grunt.registerTask('spec', 'Runs a task on a specified file', function (taskName, fileName) { globalConfig.file = fileName; diff --git a/dist/css/medium-editor.css b/dist/css/medium-editor.css index de9813251..a1c058acf 100644 --- a/dist/css/medium-editor.css +++ b/dist/css/medium-editor.css @@ -1,52 +1,21 @@ -@-webkit-keyframes medium-editor-image-loading { - 0% { - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - -webkit-transform: scale(1); - transform: scale(1); } } - @keyframes medium-editor-image-loading { 0% { - -webkit-transform: scale(0); - transform: scale(0); } - 100% { - -webkit-transform: scale(1); - transform: scale(1); } } - -@-webkit-keyframes medium-editor-pop-upwards { - 0% { - opacity: 0; - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); } - 20% { - opacity: .7; - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); } - 40% { - opacity: 1; - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); } + transform: scale(0); } 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); } } + transform: scale(1); } } @keyframes medium-editor-pop-upwards { 0% { opacity: 0; - -webkit-transform: matrix(0.97, 0, 0, 1, 0, 12); - transform: matrix(0.97, 0, 0, 1, 0, 12); } + transform: matrix(0.97, 0, 0, 1, 0, 12); } 20% { opacity: .7; - -webkit-transform: matrix(0.99, 0, 0, 1, 0, 2); - transform: matrix(0.99, 0, 0, 1, 0, 2); } + transform: matrix(0.99, 0, 0, 1, 0, 2); } 40% { opacity: 1; - -webkit-transform: matrix(1, 0, 0, 1, 0, -1); - transform: matrix(1, 0, 0, 1, 0, -1); } + transform: matrix(1, 0, 0, 1, 0, -1); } 100% { - -webkit-transform: matrix(1, 0, 0, 1, 0, 0); - transform: matrix(1, 0, 0, 1, 0, 0); } } + transform: matrix(1, 0, 0, 1, 0, 0); } } .medium-editor-anchor-preview { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; @@ -57,9 +26,9 @@ position: absolute; text-align: center; top: 0; + visibility: hidden; word-break: break-all; word-wrap: break-word; - visibility: hidden; z-index: 2000; } .medium-editor-anchor-preview a { color: #fff; @@ -73,8 +42,7 @@ background: #ddd; } .medium-editor-image-loading { - -webkit-animation: medium-editor-image-loading 1s infinite ease-in-out; - animation: medium-editor-image-loading 1s infinite ease-in-out; + animation: medium-editor-image-loading 1s infinite ease-in-out; background-color: #333; border-radius: 100%; display: inline-block; @@ -86,22 +54,22 @@ .medium-editor-placeholder:after { content: attr(data-placeholder) !important; font-style: italic; - position: absolute; left: 0; - top: 0; - white-space: pre; + margin: inherit; padding: inherit; - margin: inherit; } + position: absolute; + top: 0; + white-space: pre; } .medium-editor-placeholder-relative { position: relative; } .medium-editor-placeholder-relative:after { content: attr(data-placeholder) !important; font-style: italic; - position: relative; - white-space: pre; + margin: inherit; padding: inherit; - margin: inherit; } + position: relative; + white-space: pre; } .medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before { border-style: solid; @@ -166,8 +134,7 @@ position: relative; } .medium-editor-toolbar-active.medium-editor-stalker-toolbar { - -webkit-animation: medium-editor-pop-upwards 160ms forwards linear; - animation: medium-editor-pop-upwards 160ms forwards linear; } + animation: medium-editor-pop-upwards 160ms forwards linear; } .medium-editor-action-bold { font-weight: bolder; } @@ -188,11 +155,11 @@ .medium-editor-toolbar-form label { border: none; box-sizing: border-box; + display: inline-block; font-size: 14px; margin: 0; padding: 6px; - width: 316px; - display: inline-block; } + width: 316px; } .medium-editor-toolbar-form .medium-editor-toolbar-input:focus, .medium-editor-toolbar-form label:focus { -webkit-appearance: none; @@ -217,8 +184,8 @@ display: table; } .medium-editor-element { - word-wrap: break-word; - min-height: 30px; } + min-height: 30px; + word-wrap: break-word; } .medium-editor-element img { max-width: 100%; } .medium-editor-element sub { diff --git a/dist/css/medium-editor.min.css b/dist/css/medium-editor.min.css index e46f81c02..697afe214 100644 --- a/dist/css/medium-editor.min.css +++ b/dist/css/medium-editor.min.css @@ -1 +1 @@ -.medium-editor-anchor-preview,.medium-editor-toolbar{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;z-index:2000}@-webkit-keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes medium-editor-image-loading{0%{-webkit-transform:scale(0);transform:scale(0)}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}100%{-webkit-transform:matrix(1,0,0,1,0,0);transform:matrix(1,0,0,1,0,0)}}@keyframes medium-editor-pop-upwards{0%{opacity:0;-webkit-transform:matrix(.97,0,0,1,0,12);transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;-webkit-transform:matrix(.99,0,0,1,0,2);transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;-webkit-transform:matrix(1,0,0,1,0,-1);transform:matrix(1,0,0,1,0,-1)}100%{-webkit-transform:matrix(1,0,0,1,0,0);transform:matrix(1,0,0,1,0,0)}}.medium-editor-anchor-preview{left:0;line-height:1.4;max-width:280px;position:absolute;text-align:center;top:0;word-break:break-all;word-wrap:break-word;visibility:hidden}.medium-editor-anchor-preview a{color:#fff;display:inline-block;margin:5px 5px 10px}.medium-editor-placeholder-relative:after,.medium-editor-placeholder:after{content:attr(data-placeholder)!important;white-space:pre;padding:inherit;margin:inherit;font-style:italic}.medium-editor-anchor-preview-active{visibility:visible}.medium-editor-dragover{background:#ddd}.medium-editor-image-loading{-webkit-animation:medium-editor-image-loading 1s infinite ease-in-out;animation:medium-editor-image-loading 1s infinite ease-in-out;background-color:#333;border-radius:100%;display:inline-block;height:40px;width:40px}.medium-editor-placeholder{position:relative}.medium-editor-placeholder:after{position:absolute;left:0;top:0}.medium-editor-placeholder-relative,.medium-editor-placeholder-relative:after{position:relative}.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{border-style:solid;content:'';display:block;height:0;left:50%;margin-left:-8px;position:absolute;width:0}.medium-toolbar-arrow-under:after{border-width:8px 8px 0}.medium-toolbar-arrow-over:before{border-width:0 8px 8px;top:-8px}.medium-editor-toolbar{left:0;position:absolute;top:0;visibility:hidden}.medium-editor-toolbar ul{margin:0;padding:0}.medium-editor-toolbar li{float:left;list-style:none;margin:0;padding:0}.medium-editor-toolbar li button{box-sizing:border-box;cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}.medium-editor-toolbar li button:focus{outline:0}.medium-editor-toolbar li .medium-editor-action-underline{text-decoration:underline}.medium-editor-toolbar li .medium-editor-action-pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;font-weight:100;padding:15px 0}.medium-editor-toolbar-active{visibility:visible}.medium-editor-sticky-toolbar{position:fixed;top:1px}.medium-editor-relative-toolbar{position:relative}.medium-editor-toolbar-active.medium-editor-stalker-toolbar{-webkit-animation:medium-editor-pop-upwards 160ms forwards linear;animation:medium-editor-pop-upwards 160ms forwards linear}.medium-editor-action-bold{font-weight:bolder}.medium-editor-action-italic{font-style:italic}.medium-editor-toolbar-form{display:none}.medium-editor-toolbar-form a,.medium-editor-toolbar-form input{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.medium-editor-toolbar-form .medium-editor-toolbar-form-row{line-height:14px;margin-left:5px;padding-bottom:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input,.medium-editor-toolbar-form label{border:none;box-sizing:border-box;font-size:14px;margin:0;padding:6px;width:316px;display:inline-block}.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,.medium-editor-toolbar-form label:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-shadow:none;outline:0}.medium-editor-toolbar-form a{display:inline-block;font-size:24px;font-weight:bolder;margin:0 10px;text-decoration:none}.medium-editor-toolbar-form-active{display:block}.medium-editor-toolbar-actions:after{clear:both;content:"";display:table}.medium-editor-element{word-wrap:break-word;min-height:30px}.medium-editor-element img{max-width:100%}.medium-editor-element sub{vertical-align:sub}.medium-editor-element sup{vertical-align:super}.medium-editor-hidden{display:none} \ No newline at end of file +@keyframes medium-editor-image-loading{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes medium-editor-pop-upwards{0%{opacity:0;transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;transform:matrix(1,0,0,1,0,-1)}100%{transform:matrix(1,0,0,1,0,0)}}.medium-editor-anchor-preview{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;left:0;line-height:1.4;max-width:280px;position:absolute;text-align:center;top:0;visibility:hidden;word-break:break-all;word-wrap:break-word;z-index:2000}.medium-editor-anchor-preview a{color:#fff;display:inline-block;margin:5px 5px 10px}.medium-editor-anchor-preview-active{visibility:visible}.medium-editor-dragover{background:#ddd}.medium-editor-image-loading{animation:medium-editor-image-loading 1s infinite ease-in-out;background-color:#333;border-radius:100%;display:inline-block;height:40px;width:40px}.medium-editor-placeholder{position:relative}.medium-editor-placeholder:after{content:attr(data-placeholder)!important;font-style:italic;left:0;margin:inherit;padding:inherit;position:absolute;top:0;white-space:pre}.medium-editor-placeholder-relative{position:relative}.medium-editor-placeholder-relative:after{content:attr(data-placeholder)!important;font-style:italic;margin:inherit;padding:inherit;position:relative;white-space:pre}.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{border-style:solid;content:'';display:block;height:0;left:50%;margin-left:-8px;position:absolute;width:0}.medium-toolbar-arrow-under:after{border-width:8px 8px 0 8px}.medium-toolbar-arrow-over:before{border-width:0 8px 8px 8px;top:-8px}.medium-editor-toolbar{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;left:0;position:absolute;top:0;visibility:hidden;z-index:2000}.medium-editor-toolbar ul{margin:0;padding:0}.medium-editor-toolbar li{float:left;list-style:none;margin:0;padding:0}.medium-editor-toolbar li button{box-sizing:border-box;cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}.medium-editor-toolbar li button:focus{outline:0}.medium-editor-toolbar li .medium-editor-action-underline{text-decoration:underline}.medium-editor-toolbar li .medium-editor-action-pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;font-weight:100;padding:15px 0}.medium-editor-toolbar-active{visibility:visible}.medium-editor-sticky-toolbar{position:fixed;top:1px}.medium-editor-relative-toolbar{position:relative}.medium-editor-toolbar-active.medium-editor-stalker-toolbar{animation:medium-editor-pop-upwards 160ms forwards linear}.medium-editor-action-bold{font-weight:bolder}.medium-editor-action-italic{font-style:italic}.medium-editor-toolbar-form{display:none}.medium-editor-toolbar-form a,.medium-editor-toolbar-form input{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.medium-editor-toolbar-form .medium-editor-toolbar-form-row{line-height:14px;margin-left:5px;padding-bottom:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input,.medium-editor-toolbar-form label{border:none;box-sizing:border-box;display:inline-block;font-size:14px;margin:0;padding:6px;width:316px}.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,.medium-editor-toolbar-form label:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-shadow:none;outline:0}.medium-editor-toolbar-form a{display:inline-block;font-size:24px;font-weight:bolder;margin:0 10px;text-decoration:none}.medium-editor-toolbar-form-active{display:block}.medium-editor-toolbar-actions:after{clear:both;content:"";display:table}.medium-editor-element{min-height:30px;word-wrap:break-word}.medium-editor-element img{max-width:100%}.medium-editor-element sub{vertical-align:sub}.medium-editor-element sup{vertical-align:super}.medium-editor-hidden{display:none} \ No newline at end of file diff --git a/dist/css/themes/beagle.css b/dist/css/themes/beagle.css index edab17ee9..1135bf85c 100644 --- a/dist/css/themes/beagle.css +++ b/dist/css/themes/beagle.css @@ -17,8 +17,7 @@ height: 40px; min-width: 40px; padding: 5px 12px; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } + transition: background-color .2s ease-in, color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #000; color: #a2d7c7; } @@ -63,8 +62,7 @@ color: rgba(248, 245, 243, 0.8); } .medium-editor-toolbar-form a { color: #ccc; - -webkit-transform: translateY(2px); - transform: translateY(2px); } + transform: translateY(2px); } .medium-editor-toolbar-form .medium-editor-toolbar-close { margin-right: 16px; } diff --git a/dist/css/themes/beagle.min.css b/dist/css/themes/beagle.min.css index 5afbb9743..86489ff7f 100644 --- a/dist/css/themes/beagle.min.css +++ b/dist/css/themes/beagle.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-under:after{border-color:#000 transparent transparent;top:40px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #000}.medium-editor-toolbar{background-color:#000;border:none;border-radius:50px}.medium-editor-toolbar li button{background-color:transparent;border:none;box-sizing:border-box;color:#ccc;height:40px;min-width:40px;padding:5px 12px;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li .medium-editor-button-active,.medium-editor-toolbar li button:hover{background-color:#000;color:#a2d7c7}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:24px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:50px;border-right:none;border-top-right-radius:50px;padding-right:24px}.medium-editor-toolbar-form{background:#000;border-radius:50px;color:#ccc;overflow:hidden}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#000;box-sizing:border-box;color:#ccc;height:40px;padding-left:16px;width:220px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form a{color:#ccc;-webkit-transform:translateY(2px);transform:translateY(2px)}.medium-editor-toolbar-form .medium-editor-toolbar-close{margin-right:16px}.medium-editor-toolbar-anchor-preview{background:#000;border-radius:50px;padding:5px 12px}.medium-editor-anchor-preview a{color:#ccc;text-decoration:none}.medium-editor-toolbar-actions button,.medium-editor-toolbar-actions li{border-radius:50px} \ No newline at end of file +.medium-toolbar-arrow-under:after{border-color:#000 transparent transparent transparent;top:40px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #000 transparent}.medium-editor-toolbar{background-color:#000;border:none;border-radius:50px}.medium-editor-toolbar li button{background-color:transparent;border:none;box-sizing:border-box;color:#ccc;height:40px;min-width:40px;padding:5px 12px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#000;color:#a2d7c7}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:24px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:50px;border-right:none;border-top-right-radius:50px;padding-right:24px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#000;color:#a2d7c7}.medium-editor-toolbar-form{background:#000;border-radius:50px;color:#ccc;overflow:hidden}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#000;box-sizing:border-box;color:#ccc;height:40px;padding-left:16px;width:220px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form a{color:#ccc;transform:translateY(2px)}.medium-editor-toolbar-form .medium-editor-toolbar-close{margin-right:16px}.medium-editor-toolbar-anchor-preview{background:#000;border-radius:50px;padding:5px 12px}.medium-editor-anchor-preview a{color:#ccc;text-decoration:none}.medium-editor-toolbar-actions button,.medium-editor-toolbar-actions li{border-radius:50px} \ No newline at end of file diff --git a/dist/css/themes/bootstrap.css b/dist/css/themes/bootstrap.css index a1f4ef28a..929f1faaa 100644 --- a/dist/css/themes/bootstrap.css +++ b/dist/css/themes/bootstrap.css @@ -17,8 +17,7 @@ color: #fff; height: 60px; min-width: 60px; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } + transition: background-color .2s ease-in, color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #3276b1; color: #fff; } diff --git a/dist/css/themes/bootstrap.min.css b/dist/css/themes/bootstrap.min.css index 4063c7505..f36daf480 100644 --- a/dist/css/themes/bootstrap.min.css +++ b/dist/css/themes/bootstrap.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-under:after{border-color:#428bca transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #428bca}.medium-editor-toolbar{background-color:#428bca;border:1px solid #357ebd;border-radius:4px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #357ebd;box-sizing:border-box;color:#fff;height:60px;min-width:60px;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li .medium-editor-button-active,.medium-editor-toolbar li button:hover{background-color:#3276b1;color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:4px;border-top-left-radius:4px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:4px;border-right:none;border-top-right-radius:4px}.medium-editor-toolbar-form{background:#428bca;border-radius:4px;color:#fff}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#428bca;color:#fff;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#428bca;border-radius:4px;color:#fff}.medium-editor-placeholder:after{color:#357ebd} \ No newline at end of file +.medium-toolbar-arrow-under:after{border-color:#428bca transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #428bca transparent}.medium-editor-toolbar{background-color:#428bca;border:1px solid #357ebd;border-radius:4px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #357ebd;box-sizing:border-box;color:#fff;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#3276b1;color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:4px;border-top-left-radius:4px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:4px;border-right:none;border-top-right-radius:4px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#3276b1;color:#fff}.medium-editor-toolbar-form{background:#428bca;border-radius:4px;color:#fff}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#428bca;color:#fff;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#428bca;border-radius:4px;color:#fff}.medium-editor-placeholder:after{color:#357ebd} \ No newline at end of file diff --git a/dist/css/themes/default.css b/dist/css/themes/default.css index 02668c6b9..a30157655 100644 --- a/dist/css/themes/default.css +++ b/dist/css/themes/default.css @@ -7,26 +7,23 @@ top: -8px; } .medium-editor-toolbar { - background-color: #242424; - background: -webkit-linear-gradient(top, #242424, rgba(36, 36, 36, 0.75)); background: linear-gradient(to bottom, #242424, rgba(36, 36, 36, 0.75)); + background-color: #242424; border: 1px solid #000; border-radius: 5px; box-shadow: 0 0 3px #000; } .medium-editor-toolbar li button { - background-color: #242424; - background: -webkit-linear-gradient(top, #242424, rgba(36, 36, 36, 0.89)); background: linear-gradient(to bottom, #242424, rgba(36, 36, 36, 0.89)); + background-color: #242424; border: 0; - border-right: 1px solid #000; border-left: 1px solid #333; border-left: 1px solid rgba(255, 255, 255, 0.1); + border-right: 1px solid #000; box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3); color: #fff; height: 50px; min-width: 50px; - -webkit-transition: background-color .2s ease-in; - transition: background-color .2s ease-in; } + transition: background-color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #000; color: yellow; } @@ -37,9 +34,8 @@ border-bottom-right-radius: 5px; border-top-right-radius: 5px; } .medium-editor-toolbar li .medium-editor-button-active { - background-color: #000; - background: -webkit-linear-gradient(top, #242424, rgba(0, 0, 0, 0.89)); background: linear-gradient(to bottom, #242424, rgba(0, 0, 0, 0.89)); + background-color: #000; color: #fff; } .medium-editor-toolbar-form { diff --git a/dist/css/themes/default.min.css b/dist/css/themes/default.min.css index ac5847843..bad2e21bd 100644 --- a/dist/css/themes/default.min.css +++ b/dist/css/themes/default.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-under:after{border-color:#242424 transparent transparent;top:50px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #242424;top:-8px}.medium-editor-toolbar{background-color:#242424;background:-webkit-linear-gradient(top,#242424,rgba(36,36,36,.75));background:linear-gradient(to bottom,#242424,rgba(36,36,36,.75));border:1px solid #000;border-radius:5px;box-shadow:0 0 3px #000}.medium-editor-toolbar li button{background-color:#242424;background:-webkit-linear-gradient(top,#242424,rgba(36,36,36,.89));background:linear-gradient(to bottom,#242424,rgba(36,36,36,.89));border:0;border-right:1px solid #000;border-left:1px solid #333;border-left:1px solid rgba(255,255,255,.1);box-shadow:0 2px 2px rgba(0,0,0,.3);color:#fff;height:50px;min-width:50px;-webkit-transition:background-color .2s ease-in;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#000;color:#ff0}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:5px;border-top-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:5px;border-top-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#000;background:-webkit-linear-gradient(top,#242424,rgba(0,0,0,.89));background:linear-gradient(to bottom,#242424,rgba(0,0,0,.89));color:#fff}.medium-editor-toolbar-form{background:#242424;border-radius:5px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#242424;box-sizing:border-box;color:#ccc;height:50px}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#242424;border-radius:5px;color:#fff}.medium-editor-placeholder:after{color:#b3b3b1} \ No newline at end of file +.medium-toolbar-arrow-under:after{border-color:#242424 transparent transparent transparent;top:50px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #242424 transparent;top:-8px}.medium-editor-toolbar{background:linear-gradient(to bottom,#242424,rgba(36,36,36,.75));background-color:#242424;border:1px solid #000;border-radius:5px;box-shadow:0 0 3px #000}.medium-editor-toolbar li button{background:linear-gradient(to bottom,#242424,rgba(36,36,36,.89));background-color:#242424;border:0;border-left:1px solid #333;border-left:1px solid rgba(255,255,255,.1);border-right:1px solid #000;box-shadow:0 2px 2px rgba(0,0,0,.3);color:#fff;height:50px;min-width:50px;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#000;color:#ff0}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:5px;border-top-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:5px;border-top-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to bottom,#242424,rgba(0,0,0,.89));background-color:#000;color:#fff}.medium-editor-toolbar-form{background:#242424;border-radius:5px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#242424;box-sizing:border-box;color:#ccc;height:50px}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#242424;border-radius:5px;color:#fff}.medium-editor-placeholder:after{color:#b3b3b1} \ No newline at end of file diff --git a/dist/css/themes/flat.css b/dist/css/themes/flat.css index ae809d489..fdd608133 100644 --- a/dist/css/themes/flat.css +++ b/dist/css/themes/flat.css @@ -1,24 +1,23 @@ .medium-toolbar-arrow-under:after { - top: 60px; - border-color: #57ad68 transparent transparent transparent; } + border-color: #57ad68 transparent transparent transparent; + top: 60px; } .medium-toolbar-arrow-over:before { - top: -8px; - border-color: transparent transparent #57ad68 transparent; } + border-color: transparent transparent #57ad68 transparent; + top: -8px; } .medium-editor-toolbar { background-color: #57ad68; } .medium-editor-toolbar li { padding: 0; } .medium-editor-toolbar li button { - min-width: 60px; - height: 60px; + background-color: transparent; border: none; border-right: 1px solid #9ccea6; - background-color: transparent; color: #fff; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } + height: 60px; + min-width: 60px; + transition: background-color .2s ease-in, color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #346a3f; color: #fff; } @@ -29,9 +28,9 @@ border-right: none; } .medium-editor-toolbar-form .medium-editor-toolbar-input { - height: 60px; background: #57ad68; - color: #fff; } + color: #fff; + height: 60px; } .medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { color: #fff; color: rgba(255, 255, 255, 0.8); } diff --git a/dist/css/themes/flat.min.css b/dist/css/themes/flat.min.css index b97ec7d97..ccfeb5da3 100644 --- a/dist/css/themes/flat.min.css +++ b/dist/css/themes/flat.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-under:after{top:60px;border-color:#57ad68 transparent transparent}.medium-toolbar-arrow-over:before{top:-8px;border-color:transparent transparent #57ad68}.medium-editor-toolbar{background-color:#57ad68}.medium-editor-toolbar li{padding:0}.medium-editor-toolbar li button{min-width:60px;height:60px;border:none;border-right:1px solid #9ccea6;background-color:transparent;color:#fff;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#346a3f;color:#fff}.medium-editor-toolbar li .medium-editor-button-active{background-color:#23482a;color:#fff}.medium-editor-toolbar li .medium-editor-button-last{border-right:none}.medium-editor-toolbar-form .medium-editor-toolbar-input{height:60px;background:#57ad68;color:#fff}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#57ad68;color:#fff}.medium-editor-placeholder:after{color:#9ccea6} \ No newline at end of file +.medium-toolbar-arrow-under:after{border-color:#57ad68 transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #57ad68 transparent;top:-8px}.medium-editor-toolbar{background-color:#57ad68}.medium-editor-toolbar li{padding:0}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #9ccea6;color:#fff;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#346a3f;color:#fff}.medium-editor-toolbar li .medium-editor-button-active{background-color:#23482a;color:#fff}.medium-editor-toolbar li .medium-editor-button-last{border-right:none}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#57ad68;color:#fff;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#57ad68;color:#fff}.medium-editor-placeholder:after{color:#9ccea6} \ No newline at end of file diff --git a/dist/css/themes/mani.css b/dist/css/themes/mani.css index 1b1567de8..498db2ab1 100644 --- a/dist/css/themes/mani.css +++ b/dist/css/themes/mani.css @@ -3,55 +3,52 @@ display: none; } .medium-editor-toolbar { - border: 1px solid #cdd6e0; + background: linear-gradient(to top, #dee7f0, white); background-color: #dee7f0; background-color: rgba(222, 231, 240, 0.95); - background: -webkit-linear-gradient(bottom, #dee7f0, white); - background: linear-gradient(to top, #dee7f0, white); + border: 1px solid #cdd6e0; border-radius: 2px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45); } .medium-editor-toolbar li button { - min-width: 50px; - height: 50px; + background-color: transparent; border: none; border-right: 1px solid #cdd6e0; - background-color: transparent; color: #40648a; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } + height: 50px; + min-width: 50px; + transition: background-color .2s ease-in, color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #5c90c7; background-color: rgba(92, 144, 199, 0.45); color: #fff; } .medium-editor-toolbar li .medium-editor-button-first { - border-top-left-radius: 2px; - border-bottom-left-radius: 2px; } + border-bottom-left-radius: 2px; + border-top-left-radius: 2px; } .medium-editor-toolbar li .medium-editor-button-last { - border-top-right-radius: 2px; - border-bottom-right-radius: 2px; } + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; } .medium-editor-toolbar li .medium-editor-button-active { + background: linear-gradient(to bottom, #dee7f0, rgba(0, 0, 0, 0.1)); background-color: #5c90c7; background-color: rgba(92, 144, 199, 0.45); - color: #000; - background: -webkit-linear-gradient(top, #dee7f0, rgba(0, 0, 0, 0.1)); - background: linear-gradient(to bottom, #dee7f0, rgba(0, 0, 0, 0.1)); } + color: #000; } .medium-editor-toolbar-form { background: #dee7f0; - color: #999; - border-radius: 2px; } + border-radius: 2px; + color: #999; } .medium-editor-toolbar-form .medium-editor-toolbar-input { - height: 50px; background: #dee7f0; + box-sizing: border-box; color: #40648a; - box-sizing: border-box; } + height: 50px; } .medium-editor-toolbar-form a { color: #40648a; } .medium-editor-toolbar-anchor-preview { background: #dee7f0; - color: #40648a; - border-radius: 2px; } + border-radius: 2px; + color: #40648a; } .medium-editor-placeholder:after { color: #cdd6e0; } diff --git a/dist/css/themes/mani.min.css b/dist/css/themes/mani.min.css index 7a10b7a5d..3911e304a 100644 --- a/dist/css/themes/mani.min.css +++ b/dist/css/themes/mani.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{border:1px solid #cdd6e0;background-color:#dee7f0;background-color:rgba(222,231,240,.95);background:-webkit-linear-gradient(bottom,#dee7f0,#fff);background:linear-gradient(to top,#dee7f0,#fff);border-radius:2px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{min-width:50px;height:50px;border:none;border-right:1px solid #cdd6e0;background-color:transparent;color:#40648a;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-top-left-radius:2px;border-bottom-left-radius:2px}.medium-editor-toolbar li .medium-editor-button-last{border-top-right-radius:2px;border-bottom-right-radius:2px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#000;background:-webkit-linear-gradient(top,#dee7f0,rgba(0,0,0,.1));background:linear-gradient(to bottom,#dee7f0,rgba(0,0,0,.1))}.medium-editor-toolbar-form{background:#dee7f0;color:#999;border-radius:2px}.medium-editor-toolbar-form .medium-editor-toolbar-input{height:50px;background:#dee7f0;color:#40648a;box-sizing:border-box}.medium-editor-toolbar-form a{color:#40648a}.medium-editor-toolbar-anchor-preview{background:#dee7f0;color:#40648a;border-radius:2px}.medium-editor-placeholder:after{color:#cdd6e0} \ No newline at end of file +.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{background:linear-gradient(to top,#dee7f0,#fff);background-color:#dee7f0;background-color:rgba(222,231,240,.95);border:1px solid #cdd6e0;border-radius:2px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #cdd6e0;color:#40648a;height:50px;min-width:50px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:2px;border-top-left-radius:2px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:2px;border-top-right-radius:2px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to bottom,#dee7f0,rgba(0,0,0,.1));background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#000}.medium-editor-toolbar-form{background:#dee7f0;border-radius:2px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#dee7f0;box-sizing:border-box;color:#40648a;height:50px}.medium-editor-toolbar-form a{color:#40648a}.medium-editor-toolbar-anchor-preview{background:#dee7f0;border-radius:2px;color:#40648a}.medium-editor-placeholder:after{color:#cdd6e0} \ No newline at end of file diff --git a/dist/css/themes/roman.css b/dist/css/themes/roman.css index 1929119fb..136b15c00 100644 --- a/dist/css/themes/roman.css +++ b/dist/css/themes/roman.css @@ -8,51 +8,48 @@ border-radius: 5px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45); } .medium-editor-toolbar li button { - min-width: 50px; - height: 50px; + background: linear-gradient(to bottom, #fff, rgba(0, 0, 0, 0.2)); + background-color: transparent; border: none; border-right: 1px solid #a8a8a8; - background-color: transparent; - color: #889aac; box-shadow: inset 0 0 3px #f8f8e6; - background: -webkit-linear-gradient(top, #fff, rgba(0, 0, 0, 0.2)); - background: linear-gradient(to bottom, #fff, rgba(0, 0, 0, 0.2)); + color: #889aac; + height: 50px; + min-width: 50px; text-shadow: 1px 4px 6px #def, 0 0 0 #000, 1px 4px 6px #def; - -webkit-transition: background-color .2s ease-in; - transition: background-color .2s ease-in; } + transition: background-color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #fff; color: #fff; color: rgba(0, 0, 0, 0.8); } .medium-editor-toolbar li .medium-editor-button-first { - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; } + border-bottom-left-radius: 5px; + border-top-left-radius: 5px; } .medium-editor-toolbar li .medium-editor-button-last { - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; } + border-bottom-right-radius: 5px; + border-top-right-radius: 5px; } .medium-editor-toolbar li .medium-editor-button-active { + background: linear-gradient(to top, #fff, rgba(0, 0, 0, 0.1)); background-color: #ccc; color: #000; - color: rgba(0, 0, 0, 0.8); - background: -webkit-linear-gradient(bottom, #fff, rgba(0, 0, 0, 0.1)); - background: linear-gradient(to top, #fff, rgba(0, 0, 0, 0.1)); } + color: rgba(0, 0, 0, 0.8); } .medium-editor-toolbar-form { background: #fff; - color: #999; - border-radius: 5px; } + border-radius: 5px; + color: #999; } .medium-editor-toolbar-form .medium-editor-toolbar-input { - margin: 0; - height: 50px; background: #fff; - color: #a8a8a8; } + color: #a8a8a8; + height: 50px; + margin: 0; } .medium-editor-toolbar-form a { color: #889aac; } .medium-editor-toolbar-anchor-preview { background: #fff; - color: #889aac; - border-radius: 5px; } + border-radius: 5px; + color: #889aac; } .medium-editor-placeholder:after { color: #a8a8a8; } diff --git a/dist/css/themes/roman.min.css b/dist/css/themes/roman.min.css index 1568f684c..ca567eeb3 100644 --- a/dist/css/themes/roman.min.css +++ b/dist/css/themes/roman.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{background-color:#fff;background-color:rgba(255,255,255,.95);border-radius:5px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{min-width:50px;height:50px;border:none;border-right:1px solid #a8a8a8;background-color:transparent;color:#889aac;box-shadow:inset 0 0 3px #f8f8e6;background:-webkit-linear-gradient(top,#fff,rgba(0,0,0,.2));background:linear-gradient(to bottom,#fff,rgba(0,0,0,.2));text-shadow:1px 4px 6px #def,0 0 0 #000,1px 4px 6px #def;-webkit-transition:background-color .2s ease-in;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#fff;color:#fff;color:rgba(0,0,0,.8)}.medium-editor-toolbar li .medium-editor-button-first{border-top-left-radius:5px;border-bottom-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-top-right-radius:5px;border-bottom-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#ccc;color:#000;color:rgba(0,0,0,.8);background:-webkit-linear-gradient(bottom,#fff,rgba(0,0,0,.1));background:linear-gradient(to top,#fff,rgba(0,0,0,.1))}.medium-editor-toolbar-form{background:#fff;color:#999;border-radius:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input{margin:0;height:50px;background:#fff;color:#a8a8a8}.medium-editor-toolbar-form a{color:#889aac}.medium-editor-toolbar-anchor-preview{background:#fff;color:#889aac;border-radius:5px}.medium-editor-placeholder:after{color:#a8a8a8} \ No newline at end of file +.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{background-color:#fff;background-color:rgba(255,255,255,.95);border-radius:5px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{background:linear-gradient(to bottom,#fff,rgba(0,0,0,.2));background-color:transparent;border:none;border-right:1px solid #a8a8a8;box-shadow:inset 0 0 3px #f8f8e6;color:#889aac;height:50px;min-width:50px;text-shadow:1px 4px 6px #def,0 0 0 #000,1px 4px 6px #def;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#fff;color:#fff;color:rgba(0,0,0,.8)}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:5px;border-top-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:5px;border-top-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to top,#fff,rgba(0,0,0,.1));background-color:#ccc;color:#000;color:rgba(0,0,0,.8)}.medium-editor-toolbar-form{background:#fff;border-radius:5px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#fff;color:#a8a8a8;height:50px;margin:0}.medium-editor-toolbar-form a{color:#889aac}.medium-editor-toolbar-anchor-preview{background:#fff;border-radius:5px;color:#889aac}.medium-editor-placeholder:after{color:#a8a8a8} \ No newline at end of file diff --git a/dist/css/themes/tim.css b/dist/css/themes/tim.css index a3576bd0f..0e122d3e6 100644 --- a/dist/css/themes/tim.css +++ b/dist/css/themes/tim.css @@ -17,8 +17,7 @@ color: #ffedd5; height: 60px; min-width: 60px; - -webkit-transition: background-color .2s ease-in, color .2s ease-in; - transition: background-color .2s ease-in, color .2s ease-in; } + transition: background-color .2s ease-in, color .2s ease-in; } .medium-editor-toolbar li button:hover { background-color: #030200; color: #ffedd5; } diff --git a/dist/css/themes/tim.min.css b/dist/css/themes/tim.min.css index 882dfe3f6..c6f62d257 100644 --- a/dist/css/themes/tim.min.css +++ b/dist/css/themes/tim.min.css @@ -1 +1 @@ -.medium-toolbar-arrow-under:after{border-color:#2f1e07 transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #2f1e07}.medium-editor-toolbar{background-color:#2f1e07;border:1px solid #5b3a0e;border-radius:6px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #5b3a0e;box-sizing:border-box;color:#ffedd5;height:60px;min-width:60px;-webkit-transition:background-color .2s ease-in,color .2s ease-in;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li .medium-editor-button-active,.medium-editor-toolbar li button:hover{background-color:#030200;color:#ffedd5}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:6px;border-top-left-radius:6px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:6px;border-right:none;border-top-right-radius:6px}.medium-editor-toolbar-form{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#2f1e07;color:#ffedd5;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form a{color:#ffedd5}.medium-editor-toolbar-anchor-preview{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-placeholder:after{color:#5b3a0e} \ No newline at end of file +.medium-toolbar-arrow-under:after{border-color:#2f1e07 transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #2f1e07 transparent}.medium-editor-toolbar{background-color:#2f1e07;border:1px solid #5b3a0e;border-radius:6px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #5b3a0e;box-sizing:border-box;color:#ffedd5;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#030200;color:#ffedd5}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:6px;border-top-left-radius:6px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:6px;border-right:none;border-top-right-radius:6px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#030200;color:#ffedd5}.medium-editor-toolbar-form{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#2f1e07;color:#ffedd5;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form a{color:#ffedd5}.medium-editor-toolbar-anchor-preview{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-placeholder:after{color:#5b3a0e} \ No newline at end of file diff --git a/dist/js/medium-editor.js b/dist/js/medium-editor.js index 568fa893c..c9b046d65 100644 --- a/dist/js/medium-editor.js +++ b/dist/js/medium-editor.js @@ -1079,19 +1079,90 @@ MediumEditor.extensions = {}; return false; }, + findFirstTextNodeInSelection: function (selection) { + if (selection.anchorNode.nodeType === 3) { + return selection.anchorNode; + } + + var node = selection.anchorNode.firstChild; + + while (node) { + if (selection.containsNode(node, true)) { + if (node.nodeType === 3) { + return node; + } else { + node = node.firstChild; + } + } else { + node = node.nextSibling; + } + } + + return null; + }, + cleanListDOM: function (ownerDocument, element) { if (element.nodeName.toLowerCase() !== 'li') { - return; - } + if (this.isIE || this.isEdge) { + return; + } - var list = element.parentElement; + var selection = ownerDocument.getSelection(), + newRange = ownerDocument.createRange(), + oldRange = selection.getRangeAt(0), + startContainer = oldRange.startContainer, + startOffset = oldRange.startOffset, + endContainer = oldRange.endContainer, + endOffset = oldRange.endOffset, + node, newNode, nextNode, moveEndOffset; + + if (element.nodeName.toLowerCase() === 'span') { + // Chrome & Safari unwraps removed li elements into a span + node = element; + moveEndOffset = false; + } else { + // FF leaves them as text nodes + node = this.findFirstTextNodeInSelection(selection); + moveEndOffset = startContainer.nodeType !== 3; + } - if (list.parentElement.nodeName.toLowerCase() === 'p') { // yes we need to clean up - Util.unwrap(list.parentElement, ownerDocument); + while (node) { + if (node.nodeName.toLowerCase() !== 'span' && node.nodeType !== 3) { + break; + } + + if (node.nextSibling && node.nextSibling.nodeName.toLowerCase() === 'br') { + node.nextSibling.remove(); - // move cursor at the end of the text inside the list - // for some unknown reason, the cursor is moved to end of the "visual" line - MediumEditor.selection.moveCursor(ownerDocument, element.firstChild, element.firstChild.textContent.length); + if (moveEndOffset) { + endOffset--; + } + } + + nextNode = node.nextSibling; + + newNode = ownerDocument.createElement('p'); + node.parentNode.replaceChild(newNode, node); + newNode.appendChild(node); + + node = nextNode; + } + + // Restore selection + newRange.setStart(startContainer, startOffset); + newRange.setEnd(endContainer, endOffset); + selection.removeAllRanges(); + selection.addRange(newRange); + } else { + var list = element.parentElement; + + if (list.parentElement.nodeName.toLowerCase() === 'p') { // yes we need to clean up + Util.unwrap(list.parentElement, ownerDocument); + + // move cursor at the end of the text inside the list + // for some unknown reason, the cursor is moved to end of the "visual" line + MediumEditor.selection.moveCursor(ownerDocument, element.firstChild, element.firstChild.textContent.length); + } } }, @@ -3593,7 +3664,7 @@ MediumEditor.extensions = {}; return false; }, - /* hideForm: [function ()] + /* showForm: [function ()] * * This function should show the form element inside * the toolbar container @@ -3788,8 +3859,8 @@ MediumEditor.extensions = {}; // and provide similar access to a `fa-` icon default. template.push( '
', - '', - '
' @@ -5155,12 +5226,11 @@ MediumEditor.extensions = {}; }; /*jslint regexp: true*/ - /* - jslint does not allow character negation, because the negation - will not match any unicode characters. In the regexes in this - block, negation is used specifically to match the end of an html - tag, and in fact unicode characters *should* be allowed. - */ + + // jslint does not allow character negation, because the negation + // will not match any unicode characters. In the regexes in this + // block, negation is used specifically to match the end of an html + // tag, and in fact unicode characters *should* be allowed. function createReplacements() { return [ // Remove anything but the contents within the BODY element @@ -5176,7 +5246,7 @@ MediumEditor.extensions = {}; [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ''], [new RegExp(/<\/b>(]*>)?$/gi), ''], - // un-html spaces and newlines inserted by OS X + // un-html spaces and newlines inserted by OS X [new RegExp(/\s+<\/span>/g), ' '], [new RegExp(/
/g), '
'], @@ -5189,10 +5259,10 @@ MediumEditor.extensions = {}; //[replace google docs bolds with a span to be replaced once the html is inserted [new RegExp(/]*font-weight:(bold|700)[^>]*>/gi), ''], - // replace manually entered b/i/a tags with real ones + // replace manually entered b/i/a tags with real ones [new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], - // replace manually a tags with real ones, converting smart-quotes from google docs + // replace manually a tags with real ones, converting smart-quotes from google docs [new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi), ''], // Newlines between paragraphs in html have no syntactic value, @@ -5207,6 +5277,7 @@ MediumEditor.extensions = {}; [new RegExp(/(((?!/gi), '$1'] ]; } + /*jslint regexp: false*/ /** @@ -6742,6 +6813,12 @@ MediumEditor.extensions = {}; this.options.ownerDocument.execCommand('formatBlock', false, 'p'); } + // https://github.com/yabwe/medium-editor/issues/1455 + // if somehow we have the BR as the selected element, typing does nothing, so move the cursor + if (node.nodeName === 'BR') { + MediumEditor.selection.moveCursor(this.options.ownerDocument, node.parentElement); + } + // https://github.com/yabwe/medium-editor/issues/834 // https://github.com/yabwe/medium-editor/pull/382 // Don't call format block if this is a block element (ie h1, figCaption, etc.) @@ -6755,6 +6832,14 @@ MediumEditor.extensions = {}; this.options.ownerDocument.execCommand('unlink', false, null); } else if (!event.shiftKey && !event.ctrlKey) { this.options.ownerDocument.execCommand('formatBlock', false, 'p'); + // https://github.com/yabwe/medium-editor/issues/1455 + // firefox puts the focus on the br - so we need to move the cursor to the newly created p + if (MediumEditor.util.isFF) { + var newParagraph = node.querySelector('p'); + if (newParagraph) { + MediumEditor.selection.moveCursor(this.options.ownerDocument, newParagraph); + } + } } } } @@ -6966,6 +7051,8 @@ MediumEditor.extensions = {}; return div; } + var initialContent = {}; + function initElement(element, editorId) { if (!element.getAttribute('data-medium-editor-element')) { if (element.nodeName.toLowerCase() === 'textarea') { @@ -7232,8 +7319,6 @@ MediumEditor.extensions = {}; } } - var initialContent = {}; - MediumEditor.prototype = { // NOT DOCUMENTED - exposed for backwards compatability init: function (elements, options) { @@ -7504,6 +7589,12 @@ MediumEditor.extensions = {}; MediumEditor.util.cleanListDOM(this.options.ownerDocument, this.getSelectedParentElement()); } + // https://github.com/yabwe/medium-editor/issues/1496 + // ensure the focus remains in the editor for Firefox + if (MediumEditor.util.isFF) { + MediumEditor.util.getContainerEditorElement(this.getSelectedParentElement()).focus(); + } + this.checkSelection(); return result; }, diff --git a/dist/js/medium-editor.min.js b/dist/js/medium-editor.min.js index 386781608..3f9cc1851 100644 --- a/dist/js/medium-editor.min.js +++ b/dist/js/medium-editor.min.js @@ -1,4 +1 @@ -"classList"in document.createElement("_")||!function(a){"use strict";if("Element"in a){var b="classList",c="prototype",d=a.Element[c],e=Object,f=String[c].trim||function(){return this.replace(/^\s+|\s+$/g,"")},g=Array[c].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1},h=function(a,b){this.name=a,this.code=DOMException[a],this.message=b},i=function(a,b){if(""===b)throw new h("SYNTAX_ERR","An invalid or illegal string was specified");if(/\s/.test(b))throw new h("INVALID_CHARACTER_ERR","String contains an invalid character");return g.call(a,b)},j=function(a){for(var b=f.call(a.getAttribute("class")||""),c=b?b.split(/\s+/):[],d=0,e=c.length;e>d;d++)this.push(c[d]);this._updateClassName=function(){a.setAttribute("class",this.toString())}},k=j[c]=[],l=function(){return new j(this)};if(h[c]=Error[c],k.item=function(a){return this[a]||null},k.contains=function(a){return a+="",-1!==i(this,a)},k.add=function(){var a,b=arguments,c=0,d=b.length,e=!1;do a=b[c]+"",-1===i(this,a)&&(this.push(a),e=!0);while(++ci;i++)e+=String.fromCharCode(f[i]);c.push(e)}else if("Blob"===b(a)||"File"===b(a)){if(!g)throw new h("NOT_READABLE_ERR");var k=new g;c.push(k.readAsBinaryString(a))}else a instanceof d?"base64"===a.encoding&&p?c.push(p(a.data)):"URI"===a.encoding?c.push(decodeURIComponent(a.data)):"raw"===a.encoding&&c.push(a.data):("string"!=typeof a&&(a+=""),c.push(unescape(encodeURIComponent(a))))},e.getBlob=function(a){return arguments.length||(a=null),new d(this.data.join(""),a,"raw")},e.toString=function(){return"[object BlobBuilder]"},f.slice=function(a,b,c){var e=arguments.length;return 3>e&&(c=null),new d(this.data.slice(a,e>1?b:this.data.length),c,this.encoding)},f.toString=function(){return"[object Blob]"},f.close=function(){this.size=0,delete this.data},c}(a);a.Blob=function(a,b){var d=b?b.type||"":"",e=new c;if(a)for(var f=0,g=a.length;g>f;f++)Uint8Array&&a[f]instanceof Uint8Array?e.append(a[f].buffer):e.append(a[f]);var h=e.getBlob(d);return!h.slice&&h.webkitSlice&&(h.slice=h.webkitSlice),h};var d=Object.getPrototypeOf||function(a){return a.__proto__};a.Blob.prototype=d(new a.Blob)}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content||this),function(a,b){"use strict";var c="object"==typeof module&&"undefined"!=typeof process&&process&&process.versions&&process.versions.electron;c||"object"!=typeof module?"function"==typeof define&&define.amd?define(function(){return b}):a.MediumEditor=b:module.exports=b}(this,function(){"use strict";function a(a,b){return this.init(a,b)}return a.extensions={},function(b){function c(a,b){var c,d=Array.prototype.slice.call(arguments,2);b=b||{};for(var e=0;e-1,isMac:b.navigator.platform.toUpperCase().indexOf("MAC")>=0,keyCode:{BACKSPACE:8,TAB:9,ENTER:13,ESCAPE:27,SPACE:32,DELETE:46,K:75,M:77,V:86},isMetaCtrlKey:function(a){return!!(h.isMac&&a.metaKey||!h.isMac&&a.ctrlKey)},isKey:function(a,b){var c=h.getKeyCode(a);return!1===Array.isArray(b)?c===b:-1!==b.indexOf(c)},getKeyCode:function(a){var b=a.which;return null===b&&(b=null!==a.charCode?a.charCode:a.keyCode),b},blockContainerElementNames:["p","h1","h2","h3","h4","h5","h6","blockquote","pre","ul","li","ol","address","article","aside","audio","canvas","dd","dl","dt","fieldset","figcaption","figure","footer","form","header","hgroup","main","nav","noscript","output","section","video","table","thead","tbody","tfoot","tr","th","td"],emptyElementNames:["br","col","colgroup","hr","img","input","source","wbr"],extend:function(){var a=[!0].concat(Array.prototype.slice.call(arguments));return c.apply(this,a)},defaults:function(){var a=[!1].concat(Array.prototype.slice.call(arguments));return c.apply(this,a)},createLink:function(a,b,c,d){var e=a.createElement("a");return h.moveTextRangeIntoElement(b[0],b[b.length-1],e),e.setAttribute("href",c),d&&("_blank"===d&&e.setAttribute("rel","noopener noreferrer"),e.setAttribute("target",d)),e},findOrCreateMatchingTextNodes:function(a,b,c){for(var d=a.createTreeWalker(b,NodeFilter.SHOW_ALL,null,!1),e=[],f=0,g=!1,i=null,j=null;null!==(i=d.nextNode());)if(!(i.nodeType>3))if(3===i.nodeType){if(!g&&c.startc.end+1)throw new Error("PerformLinking overshot the target!");g&&e.push(j||i),f+=i.nodeValue.length,null!==j&&(f+=j.nodeValue.length,d.nextNode()),j=null}else"img"===i.tagName.toLowerCase()&&(!g&&c.start<=f&&(g=!0),g&&e.push(i));return e},splitStartNodeIfNeeded:function(a,b,c){return b!==c?a.splitText(b-c):null},splitEndNodeIfNeeded:function(a,b,c,d){var e,f;e=d+a.nodeValue.length+(b?b.nodeValue.length:0)-1,f=c-d-(b?a.nodeValue.length:0),e>=c&&d!==e&&0!==f&&(b||a).splitText(f)},splitByBlockElements:function(b){if(3!==b.nodeType&&1!==b.nodeType)return[];var c=[],d=a.util.blockContainerElementNames.join(",");if(3===b.nodeType||0===b.querySelectorAll(d).length)return[b];for(var e=0;e0)break;d=f.nextNode()}return d},findPreviousSibling:function(a){if(!a||h.isMediumEditorElement(a))return!1;for(var b=a.previousSibling;!b&&!h.isMediumEditorElement(a.parentNode);)a=a.parentNode,b=a.previousSibling;return b},isDescendant:function(a,b,c){if(!a||!b)return!1;if(a===b)return!!c;if(1!==a.nodeType)return!1;if(d||3!==b.nodeType)return a.contains(b);for(var e=b.parentNode;null!==e;){if(e===a)return!0;e=e.parentNode}return!1},isElement:function(a){return!(!a||1!==a.nodeType)},throttle:function(a,b){var c,d,e,f=50,g=null,h=0,i=function(){h=Date.now(),g=null,e=a.apply(c,d),g||(c=d=null)};return b||0===b||(b=f),function(){var f=Date.now(),j=b-(f-h);return c=this,d=arguments,0>=j||j>b?(g&&(clearTimeout(g),g=null),h=f,e=a.apply(c,d),g||(c=d=null)):g||(g=setTimeout(i,j)),e}},traverseUp:function(a,b){if(!a)return!1;do{if(1===a.nodeType){if(b(a))return a;if(h.isMediumEditorElement(a))return!1}a=a.parentNode}while(a);return!1},htmlEntities:function(a){return String(a).replace(/&/g,"&").replace(//g,">").replace(/"/g,""")},insertHTMLCommand:function(b,c){var d,e,f,g,i,j,k,l=!1,m=["insertHTML",!1,c];if(!a.util.isEdge&&b.queryCommandSupported("insertHTML"))try{return b.execCommand.apply(b,m)}catch(n){}if(d=b.getSelection(),d.rangeCount){if(e=d.getRangeAt(0),k=e.commonAncestorContainer,h.isMediumEditorElement(k)&&!k.firstChild)e.selectNode(k.appendChild(b.createTextNode("")));else if(3===k.nodeType&&0===e.startOffset&&e.endOffset===k.nodeValue.length||3!==k.nodeType&&k.innerHTML===e.toString()){for(;!h.isMediumEditorElement(k)&&k.parentNode&&1===k.parentNode.childNodes.length&&!h.isMediumEditorElement(k.parentNode);)k=k.parentNode;e.selectNode(k)}for(e.deleteContents(),f=b.createElement("div"),f.innerHTML=c,g=b.createDocumentFragment();f.firstChild;)i=f.firstChild,j=g.appendChild(i);e.insertNode(g),j&&(e=e.cloneRange(),e.setStartAfter(j),e.collapse(!0),a.selection.selectRange(b,e)),l=!0}return b.execCommand.callListeners&&b.execCommand.callListeners(m,l),l},execFormatBlock:function(b,c){var d,e=h.getTopBlockContainer(a.selection.getSelectionStart(b));if("blockquote"===c){if(e&&(d=Array.prototype.slice.call(e.childNodes),d.some(function(a){return h.isBlockContainer(a)})))return b.execCommand("outdent",!1,null);if(h.isIE)return b.execCommand("indent",!1,c)}if(e&&c===e.nodeName.toLowerCase()&&(c="p"),h.isIE&&(c="<"+c+">"),e&&"blockquote"===e.nodeName.toLowerCase()){if(h.isIE&&"

"===c)return b.execCommand("outdent",!1,c);if((h.isFF||h.isEdge)&&"p"===c)return d=Array.prototype.slice.call(e.childNodes),d.some(function(a){return!h.isBlockContainer(a)})&&b.execCommand("formatBlock",!1,c),b.execCommand("outdent",!1,c)}return b.execCommand("formatBlock",!1,c)},setTargetBlank:function(a,b){var c,d=b||!1;if("a"===a.nodeName.toLowerCase())a.target="_blank",a.rel="noopener noreferrer";else for(a=a.getElementsByTagName("a"),c=0;cd?(e=e.parentNode,c-=1):(f=f.parentNode,d-=1);for(;e!==f;)e=e.parentNode,f=f.parentNode;return e},isElementAtBeginningOfBlock:function(a){for(var b,c;!h.isBlockContainer(a)&&!h.isMediumEditorElement(a);){for(c=a;c=c.previousSibling;)if(b=3===c.nodeType?c.nodeValue:c.textContent,b.length>0)return!1;a=a.parentNode}return!0},isMediumEditorElement:function(a){return a&&a.getAttribute&&!!a.getAttribute("data-medium-editor-element")},getContainerEditorElement:function(a){return h.traverseUp(a,function(a){return h.isMediumEditorElement(a)})},isBlockContainer:function(a){return a&&3!==a.nodeType&&-1!==h.blockContainerElementNames.indexOf(a.nodeName.toLowerCase())},getClosestBlockContainer:function(a){return h.traverseUp(a,function(a){return h.isBlockContainer(a)||h.isMediumEditorElement(a)})},getTopBlockContainer:function(a){var b=h.isBlockContainer(a)?a:!1;return h.traverseUp(a,function(a){return h.isBlockContainer(a)&&(b=a),!b&&h.isMediumEditorElement(a)?(b=a,!0):!1}),b},getFirstSelectableLeafNode:function(a){for(;a&&a.firstChild;)a=a.firstChild;if(a=h.traverseUp(a,function(a){return-1===h.emptyElementNames.indexOf(a.nodeName.toLowerCase())}),"table"===a.nodeName.toLowerCase()){var b=a.querySelector("th, td");b&&(a=b)}return a},getFirstTextNode:function(a){return h.warn("getFirstTextNode is deprecated and will be removed in version 6.0.0"),h._getFirstTextNode(a)},_getFirstTextNode:function(a){if(3===a.nodeType)return a;for(var b=0;b0){var e,f=d.getRangeAt(0),g=f.cloneRange();g.selectNodeContents(a),g.setEnd(f.startContainer,f.startOffset),e=g.toString().length,c={start:e,end:e+f.toString().length},this.doesRangeStartWithImages(f,b)&&(c.startsWithImage=!0);var h=this.getTrailingImageCount(a,c,f.endContainer,f.endOffset);if(h&&(c.trailingImageCount=h),0!==e){var i=this.getIndexRelativeToAdjacentEmptyBlocks(b,a,f.startContainer,f.startOffset);-1!==i&&(c.emptyBlocksIndex=i)}}return c},importSelection:function(a,b,c,d){if(a&&b){var e=c.createRange();e.setStart(b,0),e.collapse(!0);var f,g=b,h=[],i=0,j=!1,k=!1,l=0,m=!1,n=!1,o=null;for((d||a.startsWithImage||"undefined"!=typeof a.emptyBlocksIndex)&&(n=!0);!m&&g;)if(g.nodeType>3)g=h.pop();else{if(3!==g.nodeType||k){if(a.trailingImageCount&&k&&("img"===g.nodeName.toLowerCase()&&l++,l===a.trailingImageCount)){for(var p=0;g.parentNode.childNodes[p]!==g;)p++;e.setEnd(g.parentNode,p+1),m=!0}if(!m&&1===g.nodeType)for(var q=g.childNodes.length-1;q>=0;)h.push(g.childNodes[q]),q-=1}else f=i+g.length,!j&&a.start>=i&&a.start<=f&&(n||a.start=i&&a.end<=f&&(a.trailingImageCount?k=!0:(e.setEnd(g,a.end-i),m=!0)),i=f;m||(g=h.pop())}!j&&o&&(e.setStart(o,o.length),e.setEnd(o,o.length)),"undefined"!=typeof a.emptyBlocksIndex&&(e=this.importSelectionMoveCursorPastBlocks(c,b,a.emptyBlocksIndex,e)),d&&(e=this.importSelectionMoveCursorPastAnchor(a,e)),this.selectRange(c,e)}},importSelectionMoveCursorPastAnchor:function(b,c){var d=function(a){return"a"===a.nodeName.toLowerCase()};if(b.start===b.end&&3===c.startContainer.nodeType&&c.startOffset===c.startContainer.nodeValue.length&&a.util.traverseUp(c.startContainer,d)){for(var e=c.startContainer,f=c.startContainer.parentNode;null!==f&&"a"!==f.nodeName.toLowerCase();)f.childNodes[f.childNodes.length-1]!==e?f=null:(e=f,f=f.parentNode);if(null!==f&&"a"===f.nodeName.toLowerCase()){for(var g=null,h=0;null===g&&h0)break}else g===i.currentNode&&(h=i.currentNode);return h||(h=g),f.setStart(a.util.getFirstSelectableLeafNode(h),0),f},getIndexRelativeToAdjacentEmptyBlocks:function(c,d,e,f){if(e.textContent.length>0&&f>0)return-1;var g=e;if(3!==g.nodeType&&(g=e.childNodes[f]),g){if(!a.util.isElementAtBeginningOfBlock(g))return-1;var h=a.util.findPreviousSibling(g);if(!h)return-1;if(h.nodeValue)return-1}for(var i=a.util.getClosestBlockContainer(e),j=c.createTreeWalker(d,NodeFilter.SHOW_ELEMENT,b,!1),k=0;j.nextNode();){var l=""===j.currentNode.textContent;if((l||k>0)&&(k+=1),j.currentNode===i)return k;l||(k=0)}return k},doesRangeStartWithImages:function(a,b){if(0!==a.startOffset||1!==a.startContainer.nodeType)return!1;if("img"===a.startContainer.nodeName.toLowerCase())return!0;var c=a.startContainer.querySelector("img");if(!c)return!1;for(var d=b.createTreeWalker(a.startContainer,NodeFilter.SHOW_ALL,null,!1);d.nextNode();){var e=d.currentNode;if(e===c)break;if(e.nodeValue)return!1}return!0},getTrailingImageCount:function(a,b,c,d){if(0===d||1!==c.nodeType)return 0;if("img"!==c.nodeName.toLowerCase()&&!c.querySelector("img"))return 0;for(var e=c.childNodes[d-1];e.hasChildNodes();)e=e.lastChild;for(var f,g=a,h=[],i=0,j=!1,k=!1,l=!1,m=0;!l&&g;)if(g.nodeType>3)g=h.pop();else{if(3!==g.nodeType||k){if("img"===g.nodeName.toLowerCase()&&m++,g===e)l=!0;else if(1===g.nodeType)for(var n=g.childNodes.length-1;n>=0;)h.push(g.childNodes[n]),n-=1}else m=0,f=i+g.length,!j&&b.start>=i&&b.start<=f&&(j=!0),j&&b.end>=i&&b.end<=f&&(k=!0),i=f;l||(g=h.pop())}return m},selectionContainsContent:function(a){var b=a.getSelection();if(!b||b.isCollapsed||!b.rangeCount)return!1;if(""!==b.toString().trim())return!0;var c=this.getSelectedParentElement(b.getRangeAt(0));return!(!c||!("img"===c.nodeName.toLowerCase()||1===c.nodeType&&c.querySelector("img")))},selectionInContentEditableFalse:function(a){var b,c=this.findMatchingSelectionParent(function(a){var c=a&&a.getAttribute("contenteditable");return"true"===c&&(b=!0),"#text"!==a.nodeName&&"false"===c},a);return!b&&c},getSelectionHtml:function(a){var b,c,d,e="",f=a.getSelection();if(f.rangeCount){for(d=a.createElement("div"),b=0,c=f.rangeCount;c>b;b+=1)d.appendChild(f.getRangeAt(b).cloneContents());e=d.innerHTML}return e},getCaretOffsets:function(a,b){var c,d;return b||(b=window.getSelection().getRangeAt(0)),c=b.cloneRange(),d=b.cloneRange(),c.selectNodeContents(a),c.setEnd(b.endContainer,b.endOffset),d.selectNodeContents(a),d.setStart(b.endContainer,b.endOffset),{left:c.toString().length,right:d.toString().length}},rangeSelectsSingleNode:function(a){var b=a.startContainer;return b===a.endContainer&&b.hasChildNodes()&&a.endOffset===a.startOffset+1},getSelectedParentElement:function(a){return a?this.rangeSelectsSingleNode(a)&&3!==a.startContainer.childNodes[a.startOffset].nodeType?a.startContainer.childNodes[a.startOffset]:3===a.startContainer.nodeType?a.startContainer.parentNode:a.startContainer:null},getSelectedElements:function(a){var b,c,d,e=a.getSelection();if(!e.rangeCount||e.isCollapsed||!e.getRangeAt(0).commonAncestorContainer)return[];if(b=e.getRangeAt(0),3===b.commonAncestorContainer.nodeType){for(c=[],d=b.commonAncestorContainer;d.parentNode&&1===d.parentNode.childNodes.length;)c.push(d.parentNode),d=d.parentNode;return c}return[].filter.call(b.commonAncestorContainer.getElementsByTagName("*"),function(a){return"function"==typeof e.containsNode?e.containsNode(a,!0):!0})},selectNode:function(a,b){var c=b.createRange();c.selectNodeContents(a),this.selectRange(b,c)},select:function(a,b,c,d,e){var f=a.createRange();return f.setStart(b,c),d?f.setEnd(d,e):f.collapse(!0),this.selectRange(a,f),f},clearSelection:function(a,b){b?a.getSelection().collapseToStart():a.getSelection().collapseToEnd()},moveCursor:function(a,b,c){this.select(a,b,c)},getSelectionRange:function(a){var b=a.getSelection();return 0===b.rangeCount?null:b.getRangeAt(0)},selectRange:function(a,b){var c=a.getSelection();c.removeAllRanges(),c.addRange(b)},getSelectionStart:function(a){var b=a.getSelection().anchorNode,c=b&&3===b.nodeType?b.parentNode:b;return c}};a.selection=c}(),function(){function b(b,c){return b?b.some(function(b){if("function"!=typeof b.getInteractionElements)return!1;var d=b.getInteractionElements();return d?(Array.isArray(d)||(d=[d]),d.some(function(b){return a.util.isDescendant(b,c,!0)})):!1}):!1}var c=function(a){this.base=a,this.options=this.base.options,this.events=[],this.disabledEvents={},this.customEvents={},this.listeners={}};c.prototype={InputEventOnContenteditableSupported:!a.util.isIE&&!a.util.isEdge,attachDOMEvent:function(b,c,d,e){var f=this.base.options.contentWindow,g=this.base.options.ownerDocument;b=a.util.isElement(b)||[f,g].indexOf(b)>-1?[b]:b,Array.prototype.forEach.call(b,function(a){a.addEventListener(c,d,e),this.events.push([a,c,d,e])}.bind(this))},detachDOMEvent:function(b,c,d,e){var f,g,h=this.base.options.contentWindow,i=this.base.options.ownerDocument;b&&(b=a.util.isElement(b)||[h,i].indexOf(b)>-1?[b]:b,Array.prototype.forEach.call(b,function(a){f=this.indexOfListener(a,c,d,e),-1!==f&&(g=this.events.splice(f,1)[0],g[0].removeEventListener(g[1],g[2],g[3]))}.bind(this)))},indexOfListener:function(a,b,c,d){var e,f,g;for(e=0,f=this.events.length;f>e;e+=1)if(g=this.events[e],g[0]===a&&g[1]===b&&g[2]===c&&g[3]===d)return e;return-1},detachAllDOMEvents:function(){for(var a=this.events.pop();a;)a[0].removeEventListener(a[1],a[2],a[3]),a=this.events.pop()},detachAllEventsFromElement:function(a){for(var b=this.events.filter(function(b){return b&&b[0].getAttribute&&b[0].getAttribute("medium-editor-index")===a.getAttribute("medium-editor-index")}),c=0,d=b.length;d>c;c++){var e=b[c];this.detachDOMEvent(e[0],e[1],e[2],e[3])}},attachAllEventsToElement:function(a){this.listeners.editableInput&&(this.contentCache[a.getAttribute("medium-editor-index")]=a.innerHTML),this.eventsCache&&this.eventsCache.forEach(function(b){this.attachDOMEvent(a,b.name,b.handler.bind(this))},this)},enableCustomEvent:function(a){void 0!==this.disabledEvents[a]&&delete this.disabledEvents[a]},disableCustomEvent:function(a){this.disabledEvents[a]=!0},attachCustomEvent:function(a,b){this.setupListener(a),this.customEvents[a]||(this.customEvents[a]=[]),this.customEvents[a].push(b)},detachCustomEvent:function(a,b){var c=this.indexOfCustomListener(a,b);-1!==c&&this.customEvents[a].splice(c,1)},indexOfCustomListener:function(a,b){return this.customEvents[a]&&this.customEvents[a].length?this.customEvents[a].indexOf(b):-1},detachAllCustomEvents:function(){this.customEvents={}},triggerCustomEvent:function(a,b,c){this.customEvents[a]&&!this.disabledEvents[a]&&this.customEvents[a].forEach(function(a){a(b,c)})},destroy:function(){this.detachAllDOMEvents(),this.detachAllCustomEvents(),this.detachExecCommand(),this.base.elements&&this.base.elements.forEach(function(a){a.removeAttribute("data-medium-focused")})},attachToExecCommand:function(){this.execCommandListener||(this.execCommandListener=function(a){this.handleDocumentExecCommand(a)}.bind(this),this.wrapExecCommand(),this.options.ownerDocument.execCommand.listeners.push(this.execCommandListener))},detachExecCommand:function(){var a=this.options.ownerDocument;if(this.execCommandListener&&a.execCommand.listeners){var b=a.execCommand.listeners.indexOf(this.execCommandListener);-1!==b&&a.execCommand.listeners.splice(b,1),a.execCommand.listeners.length||this.unwrapExecCommand()}},wrapExecCommand:function(){var a=this.options.ownerDocument;if(!a.execCommand.listeners){var b=function(b,c){a.execCommand.listeners&&a.execCommand.listeners.forEach(function(a){a({command:b[0],value:b[2],args:b,result:c})})},c=function(){var c=a.execCommand.orig.apply(this,arguments);if(!a.execCommand.listeners)return c;var d=Array.prototype.slice.call(arguments);return b(d,c),c};c.orig=a.execCommand,c.listeners=[],c.callListeners=b,a.execCommand=c}},unwrapExecCommand:function(){var a=this.options.ownerDocument;a.execCommand.orig&&(a.execCommand=a.execCommand.orig)},setupListener:function(a){if(!this.listeners[a]){switch(a){case"externalInteraction":this.attachDOMEvent(this.options.ownerDocument.body,"mousedown",this.handleBodyMousedown.bind(this),!0),this.attachDOMEvent(this.options.ownerDocument.body,"click",this.handleBodyClick.bind(this),!0),this.attachDOMEvent(this.options.ownerDocument.body,"focus",this.handleBodyFocus.bind(this),!0);break;case"blur":this.setupListener("externalInteraction");break;case"focus":this.setupListener("externalInteraction");break;case"editableInput":this.contentCache={},this.base.elements.forEach(function(a){this.contentCache[a.getAttribute("medium-editor-index")]=a.innerHTML},this),this.InputEventOnContenteditableSupported&&this.attachToEachElement("input",this.handleInput),this.InputEventOnContenteditableSupported||(this.setupListener("editableKeypress"),this.keypressUpdateInput=!0,this.attachDOMEvent(document,"selectionchange",this.handleDocumentSelectionChange.bind(this)),this.attachToExecCommand());break;case"editableClick":this.attachToEachElement("click",this.handleClick);break;case"editableBlur":this.attachToEachElement("blur",this.handleBlur);break;case"editableKeypress":this.attachToEachElement("keypress",this.handleKeypress);break;case"editableKeyup":this.attachToEachElement("keyup",this.handleKeyup);break;case"editableKeydown":this.attachToEachElement("keydown",this.handleKeydown);break;case"editableKeydownSpace":this.setupListener("editableKeydown");break;case"editableKeydownEnter":this.setupListener("editableKeydown");break;case"editableKeydownTab":this.setupListener("editableKeydown");break;case"editableKeydownDelete":this.setupListener("editableKeydown");break;case"editableMouseover":this.attachToEachElement("mouseover",this.handleMouseover);break;case"editableDrag":this.attachToEachElement("dragover",this.handleDragging),this.attachToEachElement("dragleave",this.handleDragging);break;case"editableDrop":this.attachToEachElement("drop",this.handleDrop);break;case"editablePaste":this.attachToEachElement("paste",this.handlePaste)}this.listeners[a]=!0}},attachToEachElement:function(a,b){this.eventsCache||(this.eventsCache=[]),this.base.elements.forEach(function(c){this.attachDOMEvent(c,a,b.bind(this))},this),this.eventsCache.push({name:a,handler:b})},cleanupElement:function(a){var b=a.getAttribute("medium-editor-index");b&&(this.detachAllEventsFromElement(a),this.contentCache&&delete this.contentCache[b])},focusElement:function(a){a.focus(),this.updateFocus(a,{target:a,type:"focus"})},updateFocus:function(c,d){var e,f=this.base.getFocusedElement();f&&"click"===d.type&&this.lastMousedownTarget&&(a.util.isDescendant(f,this.lastMousedownTarget,!0)||b(this.base.extensions,this.lastMousedownTarget))&&(e=f),e||this.base.elements.some(function(b){return!e&&a.util.isDescendant(b,c,!0)&&(e=b),!!e},this);var g=!a.util.isDescendant(f,c,!0)&&!b(this.base.extensions,c);e!==f&&(f&&g&&(f.removeAttribute("data-medium-focused"),this.triggerCustomEvent("blur",d,f)),e&&(e.setAttribute("data-medium-focused",!0),this.triggerCustomEvent("focus",d,e))),g&&this.triggerCustomEvent("externalInteraction",d)},updateInput:function(a,b){if(this.contentCache){var c=a.getAttribute("medium-editor-index"),d=a.innerHTML;d!==this.contentCache[c]&&this.triggerCustomEvent("editableInput",b,a),this.contentCache[c]=d}},handleDocumentSelectionChange:function(b){if(b.currentTarget&&b.currentTarget.activeElement){var c,d=b.currentTarget.activeElement;this.base.elements.some(function(b){return a.util.isDescendant(b,d,!0)?(c=b,!0):!1},this),c&&this.updateInput(c,{target:d,currentTarget:c})}},handleDocumentExecCommand:function(){var a=this.base.getFocusedElement();a&&this.updateInput(a,{target:a,currentTarget:a})},handleBodyClick:function(a){this.updateFocus(a.target,a)},handleBodyFocus:function(a){this.updateFocus(a.target,a); -},handleBodyMousedown:function(a){this.lastMousedownTarget=a.target},handleInput:function(a){this.updateInput(a.currentTarget,a)},handleClick:function(a){this.triggerCustomEvent("editableClick",a,a.currentTarget)},handleBlur:function(a){this.triggerCustomEvent("editableBlur",a,a.currentTarget)},handleKeypress:function(a){if(this.triggerCustomEvent("editableKeypress",a,a.currentTarget),this.keypressUpdateInput){var b={target:a.target,currentTarget:a.currentTarget};setTimeout(function(){this.updateInput(b.currentTarget,b)}.bind(this),0)}},handleKeyup:function(a){this.triggerCustomEvent("editableKeyup",a,a.currentTarget)},handleMouseover:function(a){this.triggerCustomEvent("editableMouseover",a,a.currentTarget)},handleDragging:function(a){this.triggerCustomEvent("editableDrag",a,a.currentTarget)},handleDrop:function(a){this.triggerCustomEvent("editableDrop",a,a.currentTarget)},handlePaste:function(a){this.triggerCustomEvent("editablePaste",a,a.currentTarget)},handleKeydown:function(b){return this.triggerCustomEvent("editableKeydown",b,b.currentTarget),a.util.isKey(b,a.util.keyCode.SPACE)?this.triggerCustomEvent("editableKeydownSpace",b,b.currentTarget):a.util.isKey(b,a.util.keyCode.ENTER)||b.ctrlKey&&a.util.isKey(b,a.util.keyCode.M)?this.triggerCustomEvent("editableKeydownEnter",b,b.currentTarget):a.util.isKey(b,a.util.keyCode.TAB)?this.triggerCustomEvent("editableKeydownTab",b,b.currentTarget):a.util.isKey(b,[a.util.keyCode.DELETE,a.util.keyCode.BACKSPACE])?this.triggerCustomEvent("editableKeydownDelete",b,b.currentTarget):void 0}},a.Events=c}(),function(){var b=a.Extension.extend({action:void 0,aria:void 0,tagNames:void 0,style:void 0,useQueryState:void 0,contentDefault:void 0,contentFA:void 0,classList:void 0,attrs:void 0,constructor:function(c){b.isBuiltInButton(c)?a.Extension.call(this,this.defaults[c]):a.Extension.call(this,c)},init:function(){a.Extension.prototype.init.apply(this,arguments),this.button=this.createButton(),this.on(this.button,"click",this.handleClick.bind(this))},getButton:function(){return this.button},getAction:function(){return"function"==typeof this.action?this.action(this.base.options):this.action},getAria:function(){return"function"==typeof this.aria?this.aria(this.base.options):this.aria},getTagNames:function(){return"function"==typeof this.tagNames?this.tagNames(this.base.options):this.tagNames},createButton:function(){var a=this.document.createElement("button"),b=this.contentDefault,c=this.getAria(),d=this.getEditorOption("buttonLabels");return a.classList.add("medium-editor-action"),a.classList.add("medium-editor-action-"+this.name),this.classList&&this.classList.forEach(function(b){a.classList.add(b)}),a.setAttribute("data-action",this.getAction()),c&&(a.setAttribute("title",c),a.setAttribute("aria-label",c)),this.attrs&&Object.keys(this.attrs).forEach(function(b){a.setAttribute(b,this.attrs[b])},this),"fontawesome"===d&&this.contentFA&&(b=this.contentFA),a.innerHTML=b,a},handleClick:function(a){a.preventDefault(),a.stopPropagation();var b=this.getAction();b&&this.execAction(b)},isActive:function(){return this.button.classList.contains(this.getEditorOption("activeButtonClass"))},setInactive:function(){this.button.classList.remove(this.getEditorOption("activeButtonClass")),delete this.knownState},setActive:function(){this.button.classList.add(this.getEditorOption("activeButtonClass")),delete this.knownState},queryCommandState:function(){var a=null;return this.useQueryState&&(a=this.base.queryCommandState(this.getAction())),a},isAlreadyApplied:function(a){var b,c,d=!1,e=this.getTagNames();return this.knownState===!1||this.knownState===!0?this.knownState:(e&&e.length>0&&(d=-1!==e.indexOf(a.nodeName.toLowerCase())),!d&&this.style&&(b=this.style.value.split("|"),c=this.window.getComputedStyle(a,null).getPropertyValue(this.style.prop),b.forEach(function(a){this.knownState||(d=-1!==c.indexOf(a),(d||"text-decoration"!==this.style.prop)&&(this.knownState=d))},this)),d)}});b.isBuiltInButton=function(b){return"string"==typeof b&&a.extensions.button.prototype.defaults.hasOwnProperty(b)},a.extensions.button=b}(),function(){a.extensions.button.prototype.defaults={bold:{name:"bold",action:"bold",aria:"bold",tagNames:["b","strong"],style:{prop:"font-weight",value:"700|bold"},useQueryState:!0,contentDefault:"B",contentFA:''},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"I",contentFA:''},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"U",contentFA:''},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"A",contentFA:''},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"x1",contentFA:''},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"x1",contentFA:''},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"image",contentFA:''},html:{name:"html",action:"html",aria:"evaluate html",tagNames:["iframe","object"],contentDefault:"html",contentFA:''},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"1.",contentFA:''},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"",contentFA:''},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"",contentFA:''},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"",contentFA:''},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"C",contentFA:''},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"J",contentFA:''},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"L",contentFA:''},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"R",contentFA:''},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"X",contentFA:''},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"",contentFA:''},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"0101",contentFA:''},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"H1",contentFA:'1'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"H2",contentFA:'2'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"H3",contentFA:'3'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"H4",contentFA:'4'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"H5",contentFA:'5'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"H6",contentFA:'6'}}}(),function(){var b=a.extensions.button.extend({init:function(){a.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"✓",formCloseLabel:"×",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return this.hasForm?this.getForm().classList.contains(this.activeClass):!1},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var a=this.base.getExtensionByName("toolbar");a&&a.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var a=this.base.getExtensionByName("toolbar");a&&a.hideToolbarDefaultActions()},setToolbarPosition:function(){var a=this.base.getExtensionByName("toolbar");a&&a.setToolbarPosition()}});a.extensions.form=b}(),function(){var b=a.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"#",contentFA:'',init:function(){a.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(b){b.preventDefault(),b.stopPropagation();var c=a.selection.getSelectionRange(this.document);return"a"===c.startContainer.nodeName.toLowerCase()||"a"===c.endContainer.nodeName.toLowerCase()||a.util.getClosestTag(a.selection.getSelectedParentElement(c),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(b){a.util.isKey(b,a.util.keyCode.K)&&a.util.isMetaCtrlKey(b)&&!b.shiftKey&&this.handleClick(b)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var a=[''];return a.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formSaveLabel,""),a.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formCloseLabel,""),this.targetCheckbox&&a.push('

','','","
"),this.customClassOption&&a.push('
','',"","
"),a.join("")},isDisplayed:function(){return a.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){a.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(b){var c=this.getInput(),d=this.getAnchorTargetCheckbox(),e=this.getAnchorButtonCheckbox();if(b=b||{value:""},"string"==typeof b&&(b={value:b}),this.base.saveSelection(),this.hideToolbarDefaultActions(),a.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),c.value=b.value,c.focus(),d&&(d.checked="_blank"===b.target),e){var f=b.buttonClass?b.buttonClass.split(" "):[];e.checked=-1!==f.indexOf(this.customClassOption)}},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},getFormOpts:function(){var a=this.getAnchorTargetCheckbox(),b=this.getAnchorButtonCheckbox(),c={value:this.getInput().value.trim()};return this.linkValidation&&(c.value=this.checkLinkFormat(c.value)),c.target="_self",a&&a.checked&&(c.target="_blank"),b&&b.checked&&(c.buttonClass=this.customClassOption),c},doFormSave:function(){var a=this.getFormOpts();this.completeFormSave(a)},completeFormSave:function(a){this.base.restoreSelection(),this.execAction(this.action,a),this.base.checkSelection()},ensureEncodedUri:function(a){return a===decodeURI(a)?encodeURI(a):a},ensureEncodedUriComponent:function(a){return a===decodeURIComponent(a)?encodeURIComponent(a):a},ensureEncodedParam:function(a){var b=a.split("="),c=b[0],d=b[1];return c+(void 0===d?"":"="+this.ensureEncodedUriComponent(d))},ensureEncodedQuery:function(a){return a.split("&").map(this.ensureEncodedParam.bind(this)).join("&")},checkLinkFormat:function(a){var b=/^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i,c=b.test(a),d="",e=/^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/,f=a.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),g=f[1],h=f[2],i=f[3];if(e.test(a))return"tel:"+a;if(!c){var j=g.split("/")[0];(j.match(/.+(\.|:).+/)||"localhost"===j)&&(d="http://")}return d+this.ensureEncodedUri(g)+(void 0===h?"":"?"+this.ensureEncodedQuery(h))+(void 0===i?"":"#"+i)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(a){var b=a.querySelector(".medium-editor-toolbar-close"),c=a.querySelector(".medium-editor-toolbar-save"),d=a.querySelector(".medium-editor-toolbar-input");this.on(a,"click",this.handleFormClick.bind(this)),this.on(d,"keyup",this.handleTextboxKeyup.bind(this)),this.on(b,"click",this.handleCloseClick.bind(this)),this.on(c,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var a=this.document,b=a.createElement("div");return b.className="medium-editor-toolbar-form",b.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),b.innerHTML=this.getTemplate(),this.attachFormEvents(b),b},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(b){return b.keyCode===a.util.keyCode.ENTER?(b.preventDefault(),void this.doFormSave()):void(b.keyCode===a.util.keyCode.ESCAPE&&(b.preventDefault(),this.doFormCancel()))},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.anchor=b}(),function(){var b=a.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var a=this.document.createElement("div");return a.id="medium-editor-anchor-preview-"+this.getEditorId(),a.className="medium-editor-anchor-preview",a.innerHTML=this.getTemplate(),this.on(a,"click",this.handleClick.bind(this)),a},getTemplate:function(){return'
'},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview&&this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(a){return this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||a.getAttribute("data-disable-preview")?!0:(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=a.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=a.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=a,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(a){a=a||this.activeAnchor;var b,c,d,e,f,g=this.window.innerWidth,h=this.anchorPreview.offsetHeight,i=a.getBoundingClientRect(),j=this.diffLeft,k=this.diffTop,l=this.getEditorOption("elementsContainer"),m=["absolute","fixed"].indexOf(window.getComputedStyle(l).getPropertyValue("position"))>-1,n={};b=this.anchorPreview.offsetWidth/2;var o=this.base.getExtensionByName("toolbar");o&&(j=o.diffLeft,k=o.diffTop),c=j-b,m?(e=l.getBoundingClientRect(),["top","left"].forEach(function(a){n[a]=i[a]-e[a]}),n.width=i.width,n.height=i.height,i=n,g=e.width,f=l.scrollTop):f=this.window.pageYOffset,d=i.left+i.width/2,f+=h+i.top+i.height-k-this.anchorPreview.offsetHeight,this.anchorPreview.style.top=Math.round(f)+"px",this.anchorPreview.style.right="initial",b>d?(this.anchorPreview.style.left=c+b+"px",this.anchorPreview.style.right="initial"):b>g-d?(this.anchorPreview.style.left="auto",this.anchorPreview.style.right=0):(this.anchorPreview.style.left=c+d+"px",this.anchorPreview.style.right="initial")},attachToEditables:function(){this.subscribe("editableMouseover",this.handleEditableMouseover.bind(this)),this.subscribe("positionedToolbar",this.handlePositionedToolbar.bind(this))},handlePositionedToolbar:function(){this.showWhenToolbarIsVisible||this.hidePreview()},handleClick:function(a){var b=this.base.getExtensionByName("anchor"),c=this.activeAnchor;b&&c&&(a.preventDefault(),this.base.selectElement(this.activeAnchor),this.base.delay(function(){if(c){var a={value:c.attributes.href.value,target:c.getAttribute("target"),buttonClass:c.getAttribute("class")};b.showForm(a),c=null}}.bind(this))),this.hidePreview()},handleAnchorMouseout:function(){this.anchorToPreview=null,this.off(this.activeAnchor,"mouseout",this.instanceHandleAnchorMouseout),this.instanceHandleAnchorMouseout=null},handleEditableMouseover:function(b){var c=a.util.getClosestTag(b.target,"a");if(!1!==c){if(!this.showOnEmptyLinks&&(!/href=["']\S+["']/.test(c.outerHTML)||/href=["']#\S+["']/.test(c.outerHTML)))return!0;var d=this.base.getExtensionByName("toolbar");if(!this.showWhenToolbarIsVisible&&d&&d.isDisplayed&&d.isDisplayed())return!0;this.activeAnchor&&this.activeAnchor!==c&&this.detachPreviewHandlers(),this.anchorToPreview=c,this.instanceHandleAnchorMouseout=this.handleAnchorMouseout.bind(this),this.on(this.anchorToPreview,"mouseout",this.instanceHandleAnchorMouseout),this.base.delay(function(){this.anchorToPreview&&this.showPreview(this.anchorToPreview)}.bind(this))}},handlePreviewMouseover:function(){this.lastOver=(new Date).getTime(),this.hovering=!0},handlePreviewMouseout:function(a){a.relatedTarget&&/anchor-preview/.test(a.relatedTarget.className)||(this.hovering=!1)},updatePreview:function(){if(this.hovering)return!0;var a=(new Date).getTime()-this.lastOver;a>this.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}});a.extensions.anchorPreview=b}(),function(){function b(b){return!a.util.getClosestTag(b,"a")}var c,d,e,f,g;c=[" "," ","\n","\r"," "," "," "," "," ","\u2028","\u2029"],d="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw",e="(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.]("+d+")\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.("+d+"))",f=new RegExp("^("+d+")$","i"),g=new RegExp(e,"gi");var h=a.Extension.extend({init:function(){a.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var a=0,b=0;b0&&null!==g;)e=c.currentNode,f=e.nodeValue,f.length>b?(g=e.splitText(f.length-b),b=0):(g=c.previousNode(),b-=f.length);return g},performLinkingWithinElement:function(b){for(var c=this.findLinkableText(b),d=!1,e=0;e1;)e.appendChild(d.childNodes[1])}});a.extensions.autoLink=h}(),function(){function b(b){var d=a.util.getContainerEditorElement(b),e=Array.prototype.slice.call(d.parentElement.querySelectorAll("."+c));e.forEach(function(a){a.classList.remove(c)})}var c="medium-editor-dragover",d=a.Extension.extend({name:"fileDragging",allowedTypes:["image"],init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableDrag",this.handleDrag.bind(this)),this.subscribe("editableDrop",this.handleDrop.bind(this))},handleDrag:function(a){a.preventDefault(),a.dataTransfer.dropEffect="copy";var d=a.target.classList?a.target:a.target.parentElement;b(d),"dragover"===a.type&&d.classList.add(c)},handleDrop:function(a){a.preventDefault(),a.stopPropagation(),this.base.selectElement(a.target);var c=this.base.exportSelection();c.start=c.end,this.base.importSelection(c),a.dataTransfer.files&&Array.prototype.slice.call(a.dataTransfer.files).forEach(function(a){this.isAllowedFile(a)&&a.type.match("image")&&this.insertImageFile(a)},this),b(a.target)},isAllowedFile:function(a){return this.allowedTypes.some(function(b){return!!a.type.match(b)})},insertImageFile:function(b){if("function"==typeof FileReader){var c=new FileReader;c.readAsDataURL(b),c.addEventListener("load",function(b){var c=this.document.createElement("img");c.src=b.target.result,a.util.insertHTMLCommand(this.document,c.outerHTML)}.bind(this))}}});a.extensions.fileDragging=d}(),function(){var b=a.Extension.extend({name:"keyboard-commands",commands:[{command:"bold",key:"B",meta:!0,shift:!1,alt:!1},{command:"italic",key:"I",meta:!0,shift:!1,alt:!1},{command:"underline",key:"U",meta:!0,shift:!1,alt:!1}],init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.keys={},this.commands.forEach(function(a){var b=a.key.charCodeAt(0);this.keys[b]||(this.keys[b]=[]),this.keys[b].push(a)},this)},handleKeydown:function(b){var c=a.util.getKeyCode(b);if(this.keys[c]){var d=a.util.isMetaCtrlKey(b),e=!!b.shiftKey,f=!!b.altKey;this.keys[c].forEach(function(a){a.meta!==d||a.shift!==e||a.alt!==f&&void 0!==a.alt||(b.preventDefault(),b.stopPropagation(),"function"==typeof a.command?a.command.apply(this):!1!==a.command&&this.execAction(a.command))},this)}}});a.extensions.keyboardCommands=b}(),function(){var b=a.extensions.form.extend({name:"fontname",action:"fontName",aria:"change font name",contentDefault:"±",contentFA:'',fonts:["","Arial","Verdana","Times New Roman"],init:function(){a.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(a){if(a.preventDefault(),a.stopPropagation(),!this.isDisplayed()){var b=this.document.queryCommandValue("fontName")+"";this.showForm(b)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(a){var b=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),b.value=a||"",b.focus()},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var a,b=this.document,c=b.createElement("div"),d=b.createElement("select"),e=b.createElement("a"),f=b.createElement("a");c.className="medium-editor-toolbar-form",c.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(c,"click",this.handleFormClick.bind(this));for(var g=0;g
':"✓",c.appendChild(f),this.on(f,"click",this.handleSaveClick.bind(this),!0),e.setAttribute("href","#"),e.className="medium-editor-toobar-close",e.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",c.appendChild(e),this.on(e,"click",this.handleCloseClick.bind(this)),c},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){a.selection.getSelectedElements(this.document).forEach(function(a){"font"===a.nodeName.toLowerCase()&&a.hasAttribute("face")&&a.removeAttribute("face")})},handleFontChange:function(){var a=this.getSelect().value;""===a?this.clearFontName():this.execAction("fontName",{value:a})},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.fontName=b}(),function(){var b=a.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"±",contentFA:'',init:function(){a.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(a){if(a.preventDefault(),a.stopPropagation(),!this.isDisplayed()){var b=this.document.queryCommandValue("fontSize")+"";this.showForm(b)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(a){var b=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),b.value=a||"",b.focus()},destroy:function(){return this.form?(this.form.parentNode&&this.form.parentNode.removeChild(this.form),void delete this.form):!1},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var a=this.document,b=a.createElement("div"),c=a.createElement("input"),d=a.createElement("a"),e=a.createElement("a");return b.className="medium-editor-toolbar-form",b.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(b,"click",this.handleFormClick.bind(this)),c.setAttribute("type","range"),c.setAttribute("min","1"), -c.setAttribute("max","7"),c.className="medium-editor-toolbar-input",b.appendChild(c),this.on(c,"change",this.handleSliderChange.bind(this)),e.setAttribute("href","#"),e.className="medium-editor-toobar-save",e.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"✓",b.appendChild(e),this.on(e,"click",this.handleSaveClick.bind(this),!0),d.setAttribute("href","#"),d.className="medium-editor-toobar-close",d.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",b.appendChild(d),this.on(d,"click",this.handleCloseClick.bind(this)),b},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){a.selection.getSelectedElements(this.document).forEach(function(a){"font"===a.nodeName.toLowerCase()&&a.hasAttribute("size")&&a.removeAttribute("size")})},handleSliderChange:function(){var a=this.getInput().value;"4"===a?this.clearFontSize():this.execAction("fontSize",{value:a})},handleFormClick:function(a){a.stopPropagation()},handleSaveClick:function(a){a.preventDefault(),this.doFormSave()},handleCloseClick:function(a){a.preventDefault(),this.doFormCancel()}});a.extensions.fontSize=b}(),function(){function b(){return[[new RegExp(/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/|/g),""],[new RegExp(/
$/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(]*>)?$/gi),""],[new RegExp(/\s+<\/span>/g)," "],[new RegExp(/
/g),"
"],[new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi),''],[new RegExp(/]*font-style:italic[^>]*>/gi),''],[new RegExp(/]*font-weight:(bold|700)[^>]*>/gi),''],[new RegExp(/<(\/?)(i|b|a)>/gi),"<$1$2>"],[new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi),''],[new RegExp(/<\/p>\n+/gi),"

"],[new RegExp(/\n+

/gi),""],[new RegExp(/(((?!/gi),"$1"]]}function c(a,b,c){var d=a.clipboardData||b.clipboardData||c.dataTransfer,e={};if(!d)return e;if(d.getData){var f=d.getData("Text");f&&f.length>0&&(e["text/plain"]=f)}if(d.types)for(var g=0;g1)for(f=0;f"+a.util.htmlEntities(e[f])+"

");else g=a.util.htmlEntities(e[0]);a.util.insertHTMLCommand(this.document,g)}},handlePasteBinPaste:function(a){if(a.defaultPrevented)return void this.removePasteBin();var b=c(a,this.window,this.document),d=b["text/html"],e=b["text/plain"],g=f;return!this.cleanPastedHTML||d?(a.preventDefault(),this.removePasteBin(),this.doPaste(d,e,g),void this.trigger("editablePaste",{currentTarget:g,target:g},g)):void setTimeout(function(){this.cleanPastedHTML&&(d=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(d,e,g),this.trigger("editablePaste",{currentTarget:g,target:g},g)}.bind(this),0)},handleKeydown:function(b,c){a.util.isKey(b,a.util.keyCode.V)&&a.util.isMetaCtrlKey(b)&&(b.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(c))},createPasteBin:function(b){var c,h=a.selection.getSelectionRange(this.document),i=this.window.pageYOffset;f=b,h&&(c=h.getClientRects(),i+=c.length?c[0].top:void 0!==h.startContainer.getBoundingClientRect?h.startContainer.getBoundingClientRect().top:h.getBoundingClientRect().top),e=h;var j=this.document.createElement("div");j.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),j.setAttribute("style","border: 1px red solid; position: absolute; top: "+i+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),j.setAttribute("contentEditable",!0),j.innerHTML=d,this.document.body.appendChild(j),this.on(j,"focus",g),this.on(j,"focusin",g),this.on(j,"focusout",g),j.focus(),a.selection.selectNode(j,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(j,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==e&&(a.selection.selectRange(this.document,e),e=null),null!==f&&(f=null);var b=this.getPasteBin();b&&b&&(this.off(b,"focus",g),this.off(b,"focusin",g),this.off(b,"focusout",g),this.off(b,"paste",this.boundHandlePaste),b.parentElement.removeChild(b))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var a=this.getPasteBin();if(!a)return!1;if(a.firstChild&&"mcepastebin"===a.firstChild.id)return!1;var b=a.innerHTML;return b&&b!==d?b:!1},cleanPaste:function(a){var c,d,e,f,g=/"+a.split("

").join("

")+"

",d=e.querySelectorAll("a,p,div,br"),c=0;c"+d.innerHTML+"
":e.innerHTML=d.innerHTML,d.parentNode.replaceChild(e,d);for(f=b.querySelectorAll("span"),c=0;c0&&(d[0].classList.add(this.firstButtonClass),d[d.length-1].classList.add(this.lastButtonClass)),h},destroy:function(){this.toolbar&&(this.toolbar.parentNode&&this.toolbar.parentNode.removeChild(this.toolbar),delete this.toolbar)},getInteractionElements:function(){return this.getToolbarElement()},getToolbarElement:function(){return this.toolbar||(this.toolbar=this.createToolbar()),this.toolbar},getToolbarActionsElement:function(){return this.getToolbarElement().querySelector(".medium-editor-toolbar-actions")},initThrottledMethods:function(){this.throttledPositionToolbar=a.util.throttle(function(){this.base.isActive&&this.positionToolbarIfShown()}.bind(this))},attachEventHandlers:function(){this.subscribe("blur",this.handleBlur.bind(this)),this.subscribe("focus",this.handleFocus.bind(this)),this.subscribe("editableClick",this.handleEditableClick.bind(this)),this.subscribe("editableKeyup",this.handleEditableKeyup.bind(this)),this.on(this.document.documentElement,"mouseup",this.handleDocumentMouseup.bind(this)),this["static"]&&this.sticky&&this.on(this.window,"scroll",this.handleWindowScroll.bind(this),!0),this.on(this.window,"resize",this.handleWindowResize.bind(this))},handleWindowScroll:function(){this.positionToolbarIfShown()},handleWindowResize:function(){this.throttledPositionToolbar()},handleDocumentMouseup:function(b){return b&&b.target&&a.util.isDescendant(this.getToolbarElement(),b.target)?!1:void this.checkState()},handleEditableClick:function(){setTimeout(function(){this.checkState()}.bind(this),0)},handleEditableKeyup:function(){this.checkState()},handleBlur:function(){clearTimeout(this.hideTimeout),clearTimeout(this.delayShowTimeout),this.hideTimeout=setTimeout(function(){this.hideToolbar()}.bind(this),1)},handleFocus:function(){this.checkState()},isDisplayed:function(){return this.getToolbarElement().classList.contains("medium-editor-toolbar-active")},showToolbar:function(){clearTimeout(this.hideTimeout),this.isDisplayed()||(this.getToolbarElement().classList.add("medium-editor-toolbar-active"),this.trigger("showToolbar",{},this.base.getFocusedElement()))},hideToolbar:function(){this.isDisplayed()&&(this.getToolbarElement().classList.remove("medium-editor-toolbar-active"),this.trigger("hideToolbar",{},this.base.getFocusedElement()))},isToolbarDefaultActionsDisplayed:function(){return"block"===this.getToolbarActionsElement().style.display},hideToolbarDefaultActions:function(){this.isToolbarDefaultActionsDisplayed()&&(this.getToolbarActionsElement().style.display="none")},showToolbarDefaultActions:function(){this.hideExtensionForms(),this.isToolbarDefaultActionsDisplayed()||(this.getToolbarActionsElement().style.display="block"),this.delayShowTimeout=this.base.delay(function(){this.showToolbar()}.bind(this))},hideExtensionForms:function(){this.forEachExtension(function(a){a.hasForm&&a.isDisplayed()&&a.hideForm()})},multipleBlockElementsSelected:function(){var b=/<[^\/>][^>]*><\/[^>]+>/gim,c=new RegExp("<("+a.util.blockContainerElementNames.join("|")+")[^>]*>","g"),d=a.selection.getSelectionHtml(this.document).replace(b,""),e=d.match(c);return!!e&&e.length>1},modifySelection:function(){var b=this.window.getSelection(),c=b.getRangeAt(0);if(this.standardizeSelectionStart&&c.startContainer.nodeValue&&c.startOffset===c.startContainer.nodeValue.length){var d=a.util.findAdjacentTextNodeWithContent(a.selection.getSelectionElement(this.window),c.startContainer,this.document);if(d){for(var e=0;0===d.nodeValue.substr(e,1).trim().length;)e+=1;c=a.selection.select(this.document,d,e,c.endContainer,c.endOffset)}}},checkState:function(){if(!this.base.preventSelectionUpdates){if(!this.base.getFocusedElement()||a.selection.selectionInContentEditableFalse(this.window))return this.hideToolbar();var b=a.selection.getSelectionElement(this.window);return!b||-1===this.getEditorElements().indexOf(b)||b.getAttribute("data-disable-toolbar")?this.hideToolbar():this.updateOnEmptySelection&&this["static"]?this.showAndUpdateToolbar():!a.selection.selectionContainsContent(this.document)||this.allowMultiParagraphSelection===!1&&this.multipleBlockElementsSelected()?this.hideToolbar():void this.showAndUpdateToolbar()}},showAndUpdateToolbar:function(){this.modifySelection(),this.setToolbarButtonStates(),this.trigger("positionToolbar",{},this.base.getFocusedElement()),this.showToolbarDefaultActions(),this.setToolbarPosition()},setToolbarButtonStates:function(){this.forEachExtension(function(a){"function"==typeof a.isActive&&"function"==typeof a.setInactive&&a.setInactive()}),this.checkActiveButtons()},checkActiveButtons:function(){var b,c=[],d=null,e=a.selection.getSelectionRange(this.document),f=function(a){"function"==typeof a.checkState?a.checkState(b):"function"==typeof a.isActive&&"function"==typeof a.isAlreadyApplied&&"function"==typeof a.setActive&&!a.isActive()&&a.isAlreadyApplied(b)&&a.setActive()};if(e&&(this.forEachExtension(function(a){return"function"==typeof a.queryCommandState&&(d=a.queryCommandState(),null!==d)?void(d&&"function"==typeof a.setActive&&a.setActive()):void c.push(a)}),b=a.selection.getSelectedParentElement(e),this.getEditorElements().some(function(c){return a.util.isDescendant(c,b,!0)})))for(;b&&(c.forEach(f),!a.util.isMediumEditorElement(b));)b=b.parentNode},positionToolbarIfShown:function(){this.isDisplayed()&&this.setToolbarPosition()},setToolbarPosition:function(){var a=this.base.getFocusedElement(),b=this.window.getSelection();return a?void(!this["static"]&&b.isCollapsed||(this.showToolbar(),this.relativeContainer||(this["static"]?this.positionStaticToolbar(a):this.positionToolbar(b)),this.trigger("positionedToolbar",{},this.base.getFocusedElement()))):this},positionStaticToolbar:function(a){this.getToolbarElement().style.left="0";var b,c=this.document.documentElement&&this.document.documentElement.scrollTop||this.document.body.scrollTop,d=this.window.innerWidth,e=this.getToolbarElement(),f=a.getBoundingClientRect(),g=f.top+c,h=f.left+f.width/2,i=e.offsetHeight,j=e.offsetWidth,k=j/2;switch(this.sticky?c>g+a.offsetHeight-i-this.stickyTopOffset?(e.style.top=g+a.offsetHeight-i+"px",e.classList.remove("medium-editor-sticky-toolbar")):c>g-i-this.stickyTopOffset?(e.classList.add("medium-editor-sticky-toolbar"),e.style.top=this.stickyTopOffset+"px"):(e.classList.remove("medium-editor-sticky-toolbar"),e.style.top=g-i+"px"):e.style.top=g-i+"px",this.align){case"left":b=f.left;break;case"right":b=f.right-j;break;case"center":b=h-k}0>b?b=0:b+j>d&&(b=d-Math.ceil(j)-1),e.style.left=b+"px"},positionToolbar:function(a){this.getToolbarElement().style.left="0",this.getToolbarElement().style.right="initial";var b=a.getRangeAt(0),c=b.getBoundingClientRect();(!c||0===c.height&&0===c.width&&b.startContainer===b.endContainer)&&(c=1===b.startContainer.nodeType&&b.startContainer.querySelector("img")?b.startContainer.querySelector("img").getBoundingClientRect():b.startContainer.getBoundingClientRect());var d,e,f=this.window.innerWidth,g=this.getToolbarElement(),h=g.offsetHeight,i=g.offsetWidth,j=i/2,k=50,l=this.diffLeft-j,m=this.getEditorOption("elementsContainer"),n=["absolute","fixed"].indexOf(window.getComputedStyle(m).getPropertyValue("position"))>-1,o={},p={};n?(e=m.getBoundingClientRect(),["top","left"].forEach(function(a){p[a]=c[a]-e[a]}),p.width=c.width,p.height=c.height,c=p,f=e.width,o.top=m.scrollTop):o.top=this.window.pageYOffset,d=c.left+c.width/2,o.top+=c.top-h,c.topd?(o.left=l+j,o.right="initial"):j>f-d?(o.left="auto",o.right=0):(o.left=l+d,o.right="initial"),["top","left","right"].forEach(function(a){g.style[a]=o[a]+(isNaN(o[a])?"":"px")})}});a.extensions.toolbar=b}(),function(){var b=a.Extension.extend({init:function(){a.Extension.prototype.init.apply(this,arguments),this.subscribe("editableDrag",this.handleDrag.bind(this)),this.subscribe("editableDrop",this.handleDrop.bind(this))},handleDrag:function(a){var b="medium-editor-dragover";a.preventDefault(),a.dataTransfer.dropEffect="copy","dragover"===a.type?a.target.classList.add(b):"dragleave"===a.type&&a.target.classList.remove(b)},handleDrop:function(b){var c,d="medium-editor-dragover";b.preventDefault(),b.stopPropagation(),b.dataTransfer.files&&(c=Array.prototype.slice.call(b.dataTransfer.files,0),c.some(function(b){if(b.type.match("image")){var c,d;c=new FileReader,c.readAsDataURL(b),d="medium-img-"+ +new Date,a.util.insertHTMLCommand(this.document,''),c.onload=function(){var a=this.document.getElementById(d);a&&(a.removeAttribute("id"),a.removeAttribute("class"),a.src=c.result)}.bind(this)}}.bind(this))),b.target.classList.remove(d)}});a.extensions.imageDragging=b}(),function(){function b(b){var c=a.selection.getSelectionStart(this.options.ownerDocument),d=c.textContent,e=a.selection.getCaretOffsets(c);(void 0===d[e.left-1]||""===d[e.left-1].trim()||void 0!==d[e.left]&&""===d[e.left].trim())&&b.preventDefault()}function c(b,c){if(this.options.disableReturn||c.getAttribute("data-disable-return"))b.preventDefault();else if(this.options.disableDoubleReturn||c.getAttribute("data-disable-double-return")){var d=a.selection.getSelectionStart(this.options.ownerDocument);(d&&""===d.textContent.trim()&&"li"!==d.nodeName.toLowerCase()||d.previousElementSibling&&"br"!==d.previousElementSibling.nodeName.toLowerCase()&&""===d.previousElementSibling.textContent.trim())&&b.preventDefault()}}function d(b){var c=a.selection.getSelectionStart(this.options.ownerDocument),d=c&&c.nodeName.toLowerCase();"pre"===d&&(b.preventDefault(),a.util.insertHTMLCommand(this.options.ownerDocument," ")),a.util.isListItem(c)&&(b.preventDefault(),b.shiftKey?this.options.ownerDocument.execCommand("outdent",!1,null):this.options.ownerDocument.execCommand("indent",!1,null))}function e(b){var c,d=a.selection.getSelectionStart(this.options.ownerDocument),e=d.nodeName.toLowerCase(),f=/^(\s+|)?$/i,g=/h\d/i;a.util.isKey(b,[a.util.keyCode.BACKSPACE,a.util.keyCode.ENTER])&&d.previousElementSibling&&g.test(e)&&0===a.selection.getCaretOffsets(d).left?a.util.isKey(b,a.util.keyCode.BACKSPACE)&&f.test(d.previousElementSibling.innerHTML)?(d.previousElementSibling.parentNode.removeChild(d.previousElementSibling),b.preventDefault()):!this.options.disableDoubleReturn&&a.util.isKey(b,a.util.keyCode.ENTER)&&(c=this.options.ownerDocument.createElement("p"),c.innerHTML="
",d.previousElementSibling.parentNode.insertBefore(c,d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.DELETE)&&d.nextElementSibling&&d.previousElementSibling&&!g.test(e)&&f.test(d.innerHTML)&&g.test(d.nextElementSibling.nodeName.toLowerCase())?(a.selection.moveCursor(this.options.ownerDocument,d.nextElementSibling),d.previousElementSibling.parentNode.removeChild(d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.BACKSPACE)&&"li"===e&&f.test(d.innerHTML)&&!d.previousElementSibling&&!d.parentElement.previousElementSibling&&d.nextElementSibling&&"li"===d.nextElementSibling.nodeName.toLowerCase()?(c=this.options.ownerDocument.createElement("p"),c.innerHTML="
",d.parentElement.parentElement.insertBefore(c,d.parentElement),a.selection.moveCursor(this.options.ownerDocument,c),d.parentElement.removeChild(d),b.preventDefault()):a.util.isKey(b,a.util.keyCode.BACKSPACE)&&a.util.getClosestTag(d,"blockquote")!==!1&&0===a.selection.getCaretOffsets(d).left?(b.preventDefault(),a.util.execFormatBlock(this.options.ownerDocument,"p")):a.util.isKey(b,a.util.keyCode.ENTER)&&a.util.getClosestTag(d,"blockquote")!==!1&&0===a.selection.getCaretOffsets(d).right?(c=this.options.ownerDocument.createElement("p"),c.innerHTML="
",d.parentElement.insertBefore(c,d.nextSibling),a.selection.moveCursor(this.options.ownerDocument,c),b.preventDefault()):a.util.isKey(b,a.util.keyCode.BACKSPACE)&&a.util.isMediumEditorElement(d.parentElement)&&!d.previousElementSibling&&d.nextElementSibling&&f.test(d.innerHTML)&&(b.preventDefault(),a.selection.moveCursor(this.options.ownerDocument,d.nextSibling),d.parentElement.removeChild(d))}function f(b){var c,d=a.selection.getSelectionStart(this.options.ownerDocument);d&&(a.util.isMediumEditorElement(d)&&0===d.children.length&&!a.util.isBlockContainer(d)&&this.options.ownerDocument.execCommand("formatBlock",!1,"p"),!a.util.isKey(b,a.util.keyCode.ENTER)||a.util.isListItem(d)||a.util.isBlockContainer(d)||(c=d.nodeName.toLowerCase(),"a"===c?this.options.ownerDocument.execCommand("unlink",!1,null):b.shiftKey||b.ctrlKey||this.options.ownerDocument.execCommand("formatBlock",!1,"p")))}function g(a,b){var c=b.parentNode.querySelector('textarea[medium-editor-textarea-id="'+b.getAttribute("medium-editor-textarea-id")+'"]');c&&(c.value=b.innerHTML.trim())}function h(a){a._mediumEditors||(a._mediumEditors=[null]),this.id||(this.id=a._mediumEditors.length),a._mediumEditors[this.id]=this}function i(a){a._mediumEditors&&a._mediumEditors[this.id]&&(a._mediumEditors[this.id]=null)}function j(b,c,d){var e=[];if(b||(b=[]),"string"==typeof b&&(b=c.querySelectorAll(b)),a.util.isElement(b)&&(b=[b]),d)for(var f=0;ff;f++)b.hasAttribute(e[f].nodeName)||b.setAttribute(e[f].nodeName,e[f].value);return a.form&&this.on(a.form,"reset",function(a){a.defaultPrevented||this.resetContent(this.options.ownerDocument.getElementById(d))}.bind(this)),a.classList.add("medium-editor-hidden"),a.parentNode.insertBefore(b,a),b}function v(b,d){if(!b.getAttribute("data-medium-editor-element")){"textarea"===b.nodeName.toLowerCase()&&(b=u.call(this,b),this.instanceHandleEditableInput||(this.instanceHandleEditableInput=g.bind(this),this.subscribe("editableInput",this.instanceHandleEditableInput))),this.options.disableEditing||b.getAttribute("data-disable-editing")||(b.setAttribute("contentEditable",!0),b.setAttribute("spellcheck",this.options.spellcheck)),this.instanceHandleEditableKeydownEnter||(b.getAttribute("data-disable-return")||b.getAttribute("data-disable-double-return"))&&(this.instanceHandleEditableKeydownEnter=c.bind(this),this.subscribe("editableKeydownEnter",this.instanceHandleEditableKeydownEnter)),this.options.disableReturn||b.getAttribute("data-disable-return")||this.on(b,"keyup",f.bind(this));var e=a.util.guid();b.setAttribute("data-medium-editor-element",!0),b.classList.add("medium-editor-element"),b.setAttribute("role","textbox"),b.setAttribute("aria-multiline",!0),b.setAttribute("data-medium-editor-editor-index",d),b.setAttribute("medium-editor-index",e),B[e]=b.innerHTML,this.events.attachAllEventsToElement(b)}return b}function w(){this.subscribe("editableKeydownTab",d.bind(this)),this.subscribe("editableKeydownDelete",e.bind(this)),this.subscribe("editableKeydownEnter",e.bind(this)),this.options.disableExtraSpaces&&this.subscribe("editableKeydownSpace",b.bind(this)),this.instanceHandleEditableKeydownEnter||(this.options.disableReturn||this.options.disableDoubleReturn)&&(this.instanceHandleEditableKeydownEnter=c.bind(this),this.subscribe("editableKeydownEnter",this.instanceHandleEditableKeydownEnter))}function x(){if(this.extensions=[],Object.keys(this.options.extensions).forEach(function(a){"toolbar"!==a&&this.options.extensions[a]&&this.extensions.push(m(this.options.extensions[a],a,this))},this),t.call(this)){var b=this.options.fileDragging;b||(b={},r.call(this)||(b.allowedTypes=[])),this.addBuiltInExtension("fileDragging",b)}var c={paste:!0,"anchor-preview":o.call(this),autoLink:q.call(this),keyboardCommands:s.call(this),placeholder:p.call(this)};Object.keys(c).forEach(function(a){c[a]&&this.addBuiltInExtension(a)},this);var d=this.options.extensions.toolbar;if(!d&&n.call(this)){var e=a.util.extend({},this.options.toolbar,{allowMultiParagraphSelection:this.options.allowMultiParagraphSelection});d=new a.extensions.toolbar(e)}d&&this.extensions.push(m(d,"toolbar",this))}function y(b,c){var d=[["allowMultiParagraphSelection","toolbar.allowMultiParagraphSelection"]];return c&&d.forEach(function(b){c.hasOwnProperty(b[0])&&void 0!==c[b[0]]&&a.util.deprecated(b[0],b[1],"v6.0.0")}),a.util.defaults({},c,b)}function z(b,c){var d,e,f=/^append-(.+)$/gi,g=/justify([A-Za-z]*)$/g;if(d=f.exec(b))return a.util.execFormatBlock(this.options.ownerDocument,d[1]);if("fontSize"===b)return c.size&&a.util.deprecated(".size option for fontSize command",".value","6.0.0"),e=c.value||c.size,this.options.ownerDocument.execCommand("fontSize",!1,e);if("fontName"===b)return c.name&&a.util.deprecated(".name option for fontName command",".value","6.0.0"),e=c.value||c.name,this.options.ownerDocument.execCommand("fontName",!1,e);if("createLink"===b)return this.createLink(c);if("image"===b){var h=this.options.contentWindow.getSelection().toString().trim();return this.options.ownerDocument.execCommand("insertImage",!1,h)}if("html"===b){var i=this.options.contentWindow.getSelection().toString().trim();return a.util.insertHTMLCommand(this.options.ownerDocument,i)}if(g.exec(b)){var j=this.options.ownerDocument.execCommand(b,!1,null),k=a.selection.getSelectedParentElement(a.selection.getSelectionRange(this.options.ownerDocument));return k&&A.call(this,a.util.getTopBlockContainer(k)),j}return e=c&&c.value,this.options.ownerDocument.execCommand(b,!1,e)}function A(b){if(b){var c,d=Array.prototype.slice.call(b.childNodes).filter(function(a){var b="div"===a.nodeName.toLowerCase();return b&&!c&&(c=a.style.textAlign),b});d.length&&(this.saveSelection(),d.forEach(function(b){if(b.style.textAlign===c){var d=b.lastChild;if(d){a.util.unwrap(b,this.options.ownerDocument);var e=this.options.ownerDocument.createElement("BR");d.parentNode.insertBefore(e,d.nextSibling)}}},this),b.style.textAlign=c,this.restoreSelection())}}var B={};a.prototype={init:function(a,b){return this.options=y.call(this,this.defaults,b),this.origElements=a,this.options.elementsContainer||(this.options.elementsContainer=this.options.ownerDocument.body),this.setup()},setup:function(){this.isActive||(h.call(this,this.options.contentWindow),this.events=new a.Events(this),this.elements=[],this.addElements(this.origElements),0!==this.elements.length&&(this.isActive=!0,x.call(this),w.call(this))); -},destroy:function(){this.isActive&&(this.isActive=!1,this.extensions.forEach(function(a){"function"==typeof a.destroy&&a.destroy()},this),this.events.destroy(),this.elements.forEach(function(a){this.options.spellcheck&&(a.innerHTML=a.innerHTML),a.removeAttribute("contentEditable"),a.removeAttribute("spellcheck"),a.removeAttribute("data-medium-editor-element"),a.classList.remove("medium-editor-element"),a.removeAttribute("role"),a.removeAttribute("aria-multiline"),a.removeAttribute("medium-editor-index"),a.removeAttribute("data-medium-editor-editor-index"),a.getAttribute("medium-editor-textarea-id")&&k(a)},this),this.elements=[],this.instanceHandleEditableKeydownEnter=null,this.instanceHandleEditableInput=null,i.call(this,this.options.contentWindow))},on:function(a,b,c,d){return this.events.attachDOMEvent(a,b,c,d),this},off:function(a,b,c,d){return this.events.detachDOMEvent(a,b,c,d),this},subscribe:function(a,b){return this.events.attachCustomEvent(a,b),this},unsubscribe:function(a,b){return this.events.detachCustomEvent(a,b),this},trigger:function(a,b,c){return this.events.triggerCustomEvent(a,b,c),this},delay:function(a){var b=this;return setTimeout(function(){b.isActive&&a()},this.options.delay)},serialize:function(){var a,b,c={},d=this.elements.length;for(a=0;d>a;a+=1)b=""!==this.elements[a].id?this.elements[a].id:"element-"+a,c[b]={value:this.elements[a].innerHTML.trim()};return c},getExtensionByName:function(a){var b;return this.extensions&&this.extensions.length&&this.extensions.some(function(c){return c.name===a?(b=c,!0):!1}),b},addBuiltInExtension:function(b,c){var d,e=this.getExtensionByName(b);if(e)return e;switch(b){case"anchor":d=a.util.extend({},this.options.anchor,c),e=new a.extensions.anchor(d);break;case"anchor-preview":e=new a.extensions.anchorPreview(this.options.anchorPreview);break;case"autoLink":e=new a.extensions.autoLink;break;case"fileDragging":e=new a.extensions.fileDragging(c);break;case"fontname":e=new a.extensions.fontName(this.options.fontName);break;case"fontsize":e=new a.extensions.fontSize(c);break;case"keyboardCommands":e=new a.extensions.keyboardCommands(this.options.keyboardCommands);break;case"paste":e=new a.extensions.paste(this.options.paste);break;case"placeholder":e=new a.extensions.placeholder(this.options.placeholder);break;default:a.extensions.button.isBuiltInButton(b)&&(c?(d=a.util.defaults({},c,a.extensions.button.prototype.defaults[b]),e=new a.extensions.button(d)):e=new a.extensions.button(b))}return e&&this.extensions.push(m(e,b,this)),e},stopSelectionUpdates:function(){this.preventSelectionUpdates=!0},startSelectionUpdates:function(){this.preventSelectionUpdates=!1},checkSelection:function(){var a=this.getExtensionByName("toolbar");return a&&a.checkState(),this},queryCommandState:function(a){var b,c=/^full-(.+)$/gi,d=null;b=c.exec(a),b&&(a=b[1]);try{d=this.options.ownerDocument.queryCommandState(a)}catch(e){d=null}return d},execAction:function(b,c){var d,e,f=/^full-(.+)$/gi;return d=f.exec(b),d?(this.saveSelection(),this.selectAllContents(),e=z.call(this,d[1],c),this.restoreSelection()):e=z.call(this,b,c),"insertunorderedlist"!==b&&"insertorderedlist"!==b||a.util.cleanListDOM(this.options.ownerDocument,this.getSelectedParentElement()),this.checkSelection(),e},getSelectedParentElement:function(b){return void 0===b&&(b=this.options.contentWindow.getSelection().getRangeAt(0)),a.selection.getSelectedParentElement(b)},selectAllContents:function(){var b=a.selection.getSelectionElement(this.options.contentWindow);if(b){for(;1===b.children.length;)b=b.children[0];this.selectElement(b)}},selectElement:function(b){a.selection.selectNode(b,this.options.ownerDocument);var c=a.selection.getSelectionElement(this.options.contentWindow);c&&this.events.focusElement(c)},getFocusedElement:function(){var a;return this.elements.some(function(b){return!a&&b.getAttribute("data-medium-focused")&&(a=b),!!a},this),a},exportSelection:function(){var b=a.selection.getSelectionElement(this.options.contentWindow),c=this.elements.indexOf(b),d=null;return c>=0&&(d=a.selection.exportSelection(b,this.options.ownerDocument)),null!==d&&0!==c&&(d.editableElementIndex=c),d},saveSelection:function(){this.selectionState=this.exportSelection()},importSelection:function(b,c){if(b){var d=this.elements[b.editableElementIndex||0];a.selection.importSelection(b,d,this.options.ownerDocument,c)}},restoreSelection:function(){this.importSelection(this.selectionState)},createLink:function(b){var c,d=a.selection.getSelectionElement(this.options.contentWindow),e={};if(-1!==this.elements.indexOf(d)){try{if(this.events.disableCustomEvent("editableInput"),b.url&&a.util.deprecated(".url option for createLink",".value","6.0.0"),c=b.url||b.value,c&&c.trim().length>0){var f=this.options.contentWindow.getSelection();if(f){var g,h,i,j,k=f.getRangeAt(0),l=k.commonAncestorContainer;if(3===k.endContainer.nodeType&&3!==k.startContainer.nodeType&&0===k.startOffset&&k.startContainer.firstChild===k.endContainer&&(l=k.endContainer),h=a.util.getClosestBlockContainer(k.startContainer),i=a.util.getClosestBlockContainer(k.endContainer),3!==l.nodeType&&0!==l.textContent.length&&h===i){var m=h||d,n=this.options.ownerDocument.createDocumentFragment();this.execAction("unlink"),g=this.exportSelection(),n.appendChild(m.cloneNode(!0)),d===m?a.selection.select(this.options.ownerDocument,m.firstChild,0,m.lastChild,3===m.lastChild.nodeType?m.lastChild.nodeValue.length:m.lastChild.childNodes.length):a.selection.select(this.options.ownerDocument,m,0,m,m.childNodes.length);var o=this.exportSelection();j=a.util.findOrCreateMatchingTextNodes(this.options.ownerDocument,n,{start:g.start-o.start,end:g.end-o.start,editableElementIndex:g.editableElementIndex}),0===j.length&&(n=this.options.ownerDocument.createDocumentFragment(),n.appendChild(l.cloneNode(!0)),j=[n.firstChild.firstChild,n.firstChild.lastChild]),a.util.createLink(this.options.ownerDocument,j,c.trim());var p=(n.firstChild.innerHTML.match(/^\s+/)||[""])[0].length;a.util.insertHTMLCommand(this.options.ownerDocument,n.firstChild.innerHTML.replace(/^\s+/,"")),g.start-=p,g.end-=p,this.importSelection(g)}else this.options.ownerDocument.execCommand("createLink",!1,c);this.options.targetBlank||"_blank"===b.target?a.util.setTargetBlank(a.selection.getSelectionStart(this.options.ownerDocument),c):a.util.removeTargetBlank(a.selection.getSelectionStart(this.options.ownerDocument),c),b.buttonClass&&a.util.addClassToAnchors(a.selection.getSelectionStart(this.options.ownerDocument),b.buttonClass)}}if(this.options.targetBlank||"_blank"===b.target||b.buttonClass){e=this.options.ownerDocument.createEvent("HTMLEvents"),e.initEvent("input",!0,!0,this.options.contentWindow);for(var q=0,r=this.elements.length;r>q;q+=1)this.elements[q].dispatchEvent(e)}}finally{this.events.enableCustomEvent("editableInput")}this.events.triggerCustomEvent("editableInput",e,d)}},cleanPaste:function(a){this.getExtensionByName("paste").cleanPaste(a)},pasteHTML:function(a,b){this.getExtensionByName("paste").pasteHTML(a,b)},setContent:function(a,b){if(b=b||0,this.elements[b]){var c=this.elements[b];c.innerHTML=a,this.checkContentChanged(c)}},getContent:function(a){return a=a||0,this.elements[a]?this.elements[a].innerHTML.trim():null},checkContentChanged:function(b){b=b||a.selection.getSelectionElement(this.options.contentWindow),this.events.updateInput(b,{target:b,currentTarget:b})},resetContent:function(a){if(a){var b=this.elements.indexOf(a);return void(-1!==b&&this.setContent(B[a.getAttribute("medium-editor-index")],b))}this.elements.forEach(function(a,b){this.setContent(B[a.getAttribute("medium-editor-index")],b)},this)},addElements:function(a){var b=j(a,this.options.ownerDocument,!0);return 0===b.length?!1:void b.forEach(function(a){a=v.call(this,a,this.id),this.elements.push(a),this.trigger("addElement",{target:a,currentTarget:a},a)},this)},removeElements:function(a){var b=j(a,this.options.ownerDocument),c=b.map(function(a){return a.getAttribute("medium-editor-textarea-id")&&a.parentNode?a.parentNode.querySelector('div[medium-editor-textarea-id="'+a.getAttribute("medium-editor-textarea-id")+'"]'):a});this.elements=this.elements.filter(function(a){return-1!==c.indexOf(a)?(this.events.cleanupElement(a),a.getAttribute("medium-editor-textarea-id")&&k(a),this.trigger("removeElement",{target:a,currentTarget:a},a),!1):!0},this)}},a.getEditorFromElement=function(a){var b=a.getAttribute("data-medium-editor-editor-index"),c=a&&a.ownerDocument&&(a.ownerDocument.defaultView||a.ownerDocument.parentWindow);return c&&c._mediumEditors&&c._mediumEditors[b]?c._mediumEditors[b]:null}}(),function(){a.prototype.defaults={activeButtonClass:"medium-editor-button-active",buttonLabels:!1,delay:0,disableReturn:!1,disableDoubleReturn:!1,disableExtraSpaces:!1,disableEditing:!1,autoLink:!1,elementsContainer:!1,contentWindow:window,ownerDocument:document,targetBlank:!1,extensions:{},spellcheck:!0}}(),a.parseVersionString=function(a){var b=a.split("-"),c=b[0].split("."),d=b.length>1?b[1]:"";return{major:parseInt(c[0],10),minor:parseInt(c[1],10),revision:parseInt(c[2],10),preRelease:d,toString:function(){return[c[0],c[1],c[2]].join(".")+(d?"-"+d:"")}}},a.version=a.parseVersionString.call(this,{version:"5.23.3"}.version),a}()); \ No newline at end of file +"classList"in document.createElement("_")||function(e){"use strict";if("Element"in e){var t="classList",n="prototype",i=e.Element[n],o=Object,s=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[n].indexOf||function(e){for(var t=0,n=this.length;t)?$/i,s=/h\d/i;b.util.isKey(e,[b.util.keyCode.BACKSPACE,b.util.keyCode.ENTER])&&n.previousElementSibling&&s.test(i)&&0===b.selection.getCaretOffsets(n).left?b.util.isKey(e,b.util.keyCode.BACKSPACE)&&o.test(n.previousElementSibling.innerHTML)?(n.previousElementSibling.parentNode.removeChild(n.previousElementSibling),e.preventDefault()):!this.options.disableDoubleReturn&&b.util.isKey(e,b.util.keyCode.ENTER)&&((t=this.options.ownerDocument.createElement("p")).innerHTML="
",n.previousElementSibling.parentNode.insertBefore(t,n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.DELETE)&&n.nextElementSibling&&n.previousElementSibling&&!s.test(i)&&o.test(n.innerHTML)&&s.test(n.nextElementSibling.nodeName.toLowerCase())?(b.selection.moveCursor(this.options.ownerDocument,n.nextElementSibling),n.previousElementSibling.parentNode.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&"li"===i&&o.test(n.innerHTML)&&!n.previousElementSibling&&!n.parentElement.previousElementSibling&&n.nextElementSibling&&"li"===n.nextElementSibling.nodeName.toLowerCase()?((t=this.options.ownerDocument.createElement("p")).innerHTML="
",n.parentElement.parentElement.insertBefore(t,n.parentElement),b.selection.moveCursor(this.options.ownerDocument,t),n.parentElement.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).left?(e.preventDefault(),b.util.execFormatBlock(this.options.ownerDocument,"p")):b.util.isKey(e,b.util.keyCode.ENTER)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).right?((t=this.options.ownerDocument.createElement("p")).innerHTML="
",n.parentElement.insertBefore(t,n.nextSibling),b.selection.moveCursor(this.options.ownerDocument,t),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&b.util.isMediumEditorElement(n.parentElement)&&!n.previousElementSibling&&n.nextElementSibling&&o.test(n.innerHTML)&&(e.preventDefault(),b.selection.moveCursor(this.options.ownerDocument,n.nextSibling),n.parentElement.removeChild(n))}function N(e,t,n){var i=[];if("string"==typeof(e=e||[])&&(e=t.querySelectorAll(e)),b.util.isElement(e)&&(e=[e]),n)for(var o=0;on.end+1)throw new Error("PerformLinking overshot the target!");r&&o.push(l||a),s+=a.nodeValue.length,null!==l&&(s+=l.nodeValue.length,i.nextNode()),l=null}else"img"===a.tagName.toLowerCase()&&(!r&&n.start<=s&&(r=!0),r&&o.push(a));return o},splitStartNodeIfNeeded:function(e,t,n){return t!==n?e.splitText(t-n):null},splitEndNodeIfNeeded:function(e,t,n,i){var o,s;o=i+e.nodeValue.length+(t?t.nodeValue.length:0)-1,s=n-i-(t?e.nodeValue.length:0),n<=o&&i!==o&&0!=s&&(t||e).splitText(s)},splitByBlockElements:function(e){if(3!==e.nodeType&&1!==e.nodeType)return[];var t=[],n=b.util.blockContainerElementNames.join(",");if(3===e.nodeType||0===e.querySelectorAll(n).length)return[e];for(var i=0;i/g,">").replace(/"/g,""")},insertHTMLCommand:function(e,t){var n,i,o,s,r,a,l,c=!1,d=["insertHTML",!1,t];if(!b.util.isEdge&&e.queryCommandSupported("insertHTML"))try{return e.execCommand.apply(e,d)}catch(e){}if((n=e.getSelection()).rangeCount){if(l=(i=n.getRangeAt(0)).commonAncestorContainer,f.isMediumEditorElement(l)&&!l.firstChild)i.selectNode(l.appendChild(e.createTextNode("")));else if(3===l.nodeType&&0===i.startOffset&&i.endOffset===l.nodeValue.length||3!==l.nodeType&&l.innerHTML===i.toString()){for(;!f.isMediumEditorElement(l)&&l.parentNode&&1===l.parentNode.childNodes.length&&!f.isMediumEditorElement(l.parentNode);)l=l.parentNode;i.selectNode(l)}for(i.deleteContents(),(o=e.createElement("div")).innerHTML=t,s=e.createDocumentFragment();o.firstChild;)r=o.firstChild,a=s.appendChild(r);i.insertNode(s),a&&((i=i.cloneRange()).setStartAfter(a),i.collapse(!0),b.selection.selectRange(e,i)),c=!0}return e.execCommand.callListeners&&e.execCommand.callListeners(d,c),c},execFormatBlock:function(e,t){var n=f.getTopBlockContainer(b.selection.getSelectionStart(e));if("blockquote"===t){if(n&&Array.prototype.slice.call(n.childNodes).some(function(e){return f.isBlockContainer(e)}))return e.execCommand("outdent",!1,null);if(f.isIE)return e.execCommand("indent",!1,t)}if(n&&t===n.nodeName.toLowerCase()&&(t="p"),f.isIE&&(t="<"+t+">"),n&&"blockquote"===n.nodeName.toLowerCase()){if(f.isIE&&"

"===t)return e.execCommand("outdent",!1,t);if((f.isFF||f.isEdge)&&"p"===t)return Array.prototype.slice.call(n.childNodes).some(function(e){return!f.isBlockContainer(e)})&&e.execCommand("formatBlock",!1,t),e.execCommand("outdent",!1,t)}return e.execCommand("formatBlock",!1,t)},setTargetBlank:function(e,t){var n,i=t||!1;if("a"===e.nodeName.toLowerCase())e.target="_blank",e.rel="noopener noreferrer";else for(e=e.getElementsByTagName("a"),n=0;n=l&&e.start<=s&&(m||e.start=l&&e.end<=s&&(e.trailingImageCount?d=!0:(o.setEnd(r,e.end-l),h=!0)),l=s;h||(r=a.pop())}!c&&f&&(o.setStart(f,f.length),o.setEnd(f,f.length)),void 0!==e.emptyBlocksIndex&&(o=this.importSelectionMoveCursorPastBlocks(n,t,e.emptyBlocksIndex,o)),i&&(o=this.importSelectionMoveCursorPastAnchor(e,o)),this.selectRange(n,o)}},importSelectionMoveCursorPastAnchor:function(e,t){if(e.start===e.end&&3===t.startContainer.nodeType&&t.startOffset===t.startContainer.nodeValue.length&&b.util.traverseUp(t.startContainer,function(e){return"a"===e.nodeName.toLowerCase()})){for(var n=t.startContainer,i=t.startContainer.parentNode;null!==i&&"a"!==i.nodeName.toLowerCase();)i=i.childNodes[i.childNodes.length-1]!==n?null:(n=i).parentNode;if(null!==i&&"a"===i.nodeName.toLowerCase()){for(var o=null,s=0;null===o&&s=l&&t.start<=s&&(c=!0),c&&t.end>=l&&t.end<=s&&(d=!0),l=s;u||(r=a.pop())}return h},selectionContainsContent:function(e){var t=e.getSelection();if(!t||t.isCollapsed||!t.rangeCount)return!1;if(""!==t.toString().trim())return!0;var n=this.getSelectedParentElement(t.getRangeAt(0));return!(!n||!("img"===n.nodeName.toLowerCase()||1===n.nodeType&&n.querySelector("img")))},selectionInContentEditableFalse:function(e){var n,t=this.findMatchingSelectionParent(function(e){var t=e&&e.getAttribute("contenteditable");return"true"===t&&(n=!0),"#text"!==e.nodeName&&"false"===t},e);return!n&&t},getSelectionHtml:function(e){var t,n,i,o="",s=e.getSelection();if(s.rangeCount){for(i=e.createElement("div"),t=0,n=s.rangeCount;tB",contentFA:''},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"I",contentFA:''},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"U",contentFA:''},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"A",contentFA:''},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"x1",contentFA:''},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"x1",contentFA:''},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"image",contentFA:''},html:{name:"html",action:"html",aria:"evaluate html",tagNames:["iframe","object"],contentDefault:"html",contentFA:''},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"1.",contentFA:''},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"",contentFA:''},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"",contentFA:''},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"",contentFA:''},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"C",contentFA:''},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"J",contentFA:''},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"L",contentFA:''},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"R",contentFA:''},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"X",contentFA:''},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"",contentFA:''},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"0101",contentFA:''},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"H1",contentFA:'1'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"H2",contentFA:'2'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"H3",contentFA:'3'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"H4",contentFA:'4'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"H5",contentFA:'5'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"H6",contentFA:'6'}},i=b.extensions.button.extend({init:function(){b.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"✓",formCloseLabel:"×",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return!!this.hasForm&&this.getForm().classList.contains(this.activeClass)},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.hideToolbarDefaultActions()},setToolbarPosition:function(){var e=this.base.getExtensionByName("toolbar");e&&e.setToolbarPosition()}}),b.extensions.form=i,o=b.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"#",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(e){e.preventDefault(),e.stopPropagation();var t=b.selection.getSelectionRange(this.document);return"a"===t.startContainer.nodeName.toLowerCase()||"a"===t.endContainer.nodeName.toLowerCase()||b.util.getClosestTag(b.selection.getSelectedParentElement(t),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(e){b.util.isKey(e,b.util.keyCode.K)&&b.util.isMetaCtrlKey(e)&&!e.shiftKey&&this.handleClick(e)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var e=[''];return e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formSaveLabel,""),e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formCloseLabel,""),this.targetCheckbox&&e.push('

','','","
"),this.customClassOption&&e.push('
','','","
"),e.join("")},isDisplayed:function(){return b.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){b.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(e){var t=this.getInput(),n=this.getAnchorTargetCheckbox(),i=this.getAnchorButtonCheckbox();if("string"==typeof(e=e||{value:""})&&(e={value:e}),this.base.saveSelection(),this.hideToolbarDefaultActions(),b.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),t.value=e.value,t.focus(),n&&(n.checked="_blank"===e.target),i){var o=e.buttonClass?e.buttonClass.split(" "):[];i.checked=-1!==o.indexOf(this.customClassOption)}},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},getFormOpts:function(){var e=this.getAnchorTargetCheckbox(),t=this.getAnchorButtonCheckbox(),n={value:this.getInput().value.trim()};return this.linkValidation&&(n.value=this.checkLinkFormat(n.value)),n.target="_self",e&&e.checked&&(n.target="_blank"),t&&t.checked&&(n.buttonClass=this.customClassOption),n},doFormSave:function(){var e=this.getFormOpts();this.completeFormSave(e)},completeFormSave:function(e){this.base.restoreSelection(),this.execAction(this.action,e),this.base.checkSelection()},ensureEncodedUri:function(e){return e===decodeURI(e)?encodeURI(e):e},ensureEncodedUriComponent:function(e){return e===decodeURIComponent(e)?encodeURIComponent(e):e},ensureEncodedParam:function(e){var t=e.split("="),n=t[0],i=t[1];return n+(void 0===i?"":"="+this.ensureEncodedUriComponent(i))},ensureEncodedQuery:function(e){return e.split("&").map(this.ensureEncodedParam.bind(this)).join("&")},checkLinkFormat:function(e){var t=/^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i.test(e),n="",i=e.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),o=i[1],s=i[2],r=i[3];if(/^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/.test(e))return"tel:"+e;if(!t){var a=o.split("/")[0];!a.match(/.+(\.|:).+/)&&"localhost"!==a||(n="http://")}return n+this.ensureEncodedUri(o)+(void 0===s?"":"?"+this.ensureEncodedQuery(s))+(void 0===r?"":"#"+r)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(e){var t=e.querySelector(".medium-editor-toolbar-close"),n=e.querySelector(".medium-editor-toolbar-save"),i=e.querySelector(".medium-editor-toolbar-input");this.on(e,"click",this.handleFormClick.bind(this)),this.on(i,"keyup",this.handleTextboxKeyup.bind(this)),this.on(t,"click",this.handleCloseClick.bind(this)),this.on(n,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var e=this.document.createElement("div");return e.className="medium-editor-toolbar-form",e.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),e.innerHTML=this.getTemplate(),this.attachFormEvents(e),e},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(e){if(e.keyCode===b.util.keyCode.ENTER)return e.preventDefault(),void this.doFormSave();e.keyCode===b.util.keyCode.ESCAPE&&(e.preventDefault(),this.doFormCancel())},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.anchor=o,s=b.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var e=this.document.createElement("div");return e.id="medium-editor-anchor-preview-"+this.getEditorId(),e.className="medium-editor-anchor-preview",e.innerHTML=this.getTemplate(),this.on(e,"click",this.handleClick.bind(this)),e},getTemplate:function(){return'
'},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview&&this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(e){return!(!this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")&&!e.getAttribute("data-disable-preview"))||(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=e.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=e.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=e,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(e){e=e||this.activeAnchor;var t,n,i,o,s,r=this.window.innerWidth,a=this.anchorPreview.offsetHeight,l=e.getBoundingClientRect(),c=this.diffLeft,d=this.diffTop,u=this.getEditorOption("elementsContainer"),h=-1<["absolute","fixed"].indexOf(window.getComputedStyle(u).getPropertyValue("position")),m={};t=this.anchorPreview.offsetWidth/2;var f=this.base.getExtensionByName("toolbar");f&&(c=f.diffLeft,d=f.diffTop),n=c-t,s=h?(o=u.getBoundingClientRect(),["top","left"].forEach(function(e){m[e]=l[e]-o[e]}),m.width=l.width,m.height=l.height,l=m,r=o.width,u.scrollTop):this.window.pageYOffset,i=l.left+l.width/2,s+=a+l.top+l.height-d-this.anchorPreview.offsetHeight,this.anchorPreview.style.top=Math.round(s)+"px",this.anchorPreview.style.right="initial",ithis.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}}),b.extensions.anchorPreview=s,function(){var s,e,r,a;s=[" ","\t","\n","\r"," "," "," "," "," ","\u2028","\u2029"],e="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw",r=new RegExp("^("+e+")$","i"),a=new RegExp("(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))","gi");var t=b.Extension.extend({init:function(){b.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var e=0,t=0;tt?(o=n.splitText(i.length-t),t=0):(o=s.previousNode(),t-=i.length);return o},performLinkingWithinElement:function(e){for(var t=this.findLinkableText(e),n=0;n
',fonts:["","Arial","Verdana","Times New Roman"],init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontName")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(e){var t=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var e,t=this.document,n=t.createElement("div"),i=t.createElement("select"),o=t.createElement("a"),s=t.createElement("a");n.className="medium-editor-toolbar-form",n.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(n,"click",this.handleFormClick.bind(this));for(var r=0;r
':"✓",n.appendChild(s),this.on(s,"click",this.handleSaveClick.bind(this),!0),o.setAttribute("href","#"),o.className="medium-editor-toobar-close",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",n.appendChild(o),this.on(o,"click",this.handleCloseClick.bind(this)),n},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("face")&&e.removeAttribute("face")})},handleFontChange:function(){var e=this.getSelect().value;""===e?this.clearFontName():this.execAction("fontName",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontName=c,u=b.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"±",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontSize")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(e){var t=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var e=this.document,t=e.createElement("div"),n=e.createElement("input"),i=e.createElement("a"),o=e.createElement("a");return t.className="medium-editor-toolbar-form",t.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(t,"click",this.handleFormClick.bind(this)),n.setAttribute("type","range"),n.setAttribute("min","1"),n.setAttribute("max","7"),n.className="medium-editor-toolbar-input",t.appendChild(n),this.on(n,"change",this.handleSliderChange.bind(this)),o.setAttribute("href","#"),o.className="medium-editor-toobar-save",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"✓",t.appendChild(o),this.on(o,"click",this.handleSaveClick.bind(this),!0),i.setAttribute("href","#"),i.className="medium-editor-toobar-close",i.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",t.appendChild(i),this.on(i,"click",this.handleCloseClick.bind(this)),t},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("size")&&e.removeAttribute("size")})},handleSliderChange:function(){var e=this.getInput().value;"4"===e?this.clearFontSize():this.execAction("fontSize",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontSize=u,h="%ME_PASTEBIN%",f=m=null,p=b.Extension.extend({forcePlainText:!0,cleanPastedHTML:!1,preCleanReplacements:[],cleanReplacements:[],cleanAttrs:["class","style","dir"],cleanTags:["meta"],unwrapTags:[],init:function(){b.Extension.prototype.init.apply(this,arguments),(this.forcePlainText||this.cleanPastedHTML)&&(this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.getEditorElements().forEach(function(e){this.on(e,"paste",this.handlePaste.bind(this))},this),this.subscribe("addElement",this.handleAddElement.bind(this)))},handleAddElement:function(e,t){this.on(t,"paste",this.handlePaste.bind(this))},destroy:function(){(this.forcePlainText||this.cleanPastedHTML)&&this.removePasteBin()},handlePaste:function(e,t){if(!e.defaultPrevented){var n=k(e,this.window,this.document),i=n["text/html"],o=n["text/plain"];this.window.clipboardData&&void 0===e.clipboardData&&!i&&(i=o),(i||o)&&(e.preventDefault(),this.doPaste(i,o,t))}},doPaste:function(e,t,n){var i,o,s="";if(this.cleanPastedHTML&&e)return this.cleanPaste(e);if(t){if(this.getEditorOption("disableReturn")||n&&n.getAttribute("data-disable-return"))s=b.util.htmlEntities(t);else if(1<(i=t.split(/[\r\n]+/g)).length)for(o=0;o"+b.util.htmlEntities(i[o])+"

");else s=b.util.htmlEntities(i[0]);b.util.insertHTMLCommand(this.document,s)}},handlePasteBinPaste:function(e){if(e.defaultPrevented)this.removePasteBin();else{var t=k(e,this.window,this.document),n=t["text/html"],i=t["text/plain"],o=f;if(!this.cleanPastedHTML||n)return e.preventDefault(),this.removePasteBin(),this.doPaste(n,i,o),void this.trigger("editablePaste",{currentTarget:o,target:o},o);setTimeout(function(){this.cleanPastedHTML&&(n=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(n,i,o),this.trigger("editablePaste",{currentTarget:o,target:o},o)}.bind(this),0)}},handleKeydown:function(e,t){b.util.isKey(e,b.util.keyCode.V)&&b.util.isMetaCtrlKey(e)&&(e.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(t))},createPasteBin:function(e){var t,n=b.selection.getSelectionRange(this.document),i=this.window.pageYOffset;f=e,n&&((t=n.getClientRects()).length?i+=t[0].top:void 0!==n.startContainer.getBoundingClientRect?i+=n.startContainer.getBoundingClientRect().top:i+=n.getBoundingClientRect().top),m=n;var o=this.document.createElement("div");o.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),o.setAttribute("style","border: 1px red solid; position: absolute; top: "+i+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),o.setAttribute("contentEditable",!0),o.innerHTML=h,this.document.body.appendChild(o),this.on(o,"focus",T),this.on(o,"focusin",T),this.on(o,"focusout",T),o.focus(),b.selection.selectNode(o,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(o,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==m&&(b.selection.selectRange(this.document,m),m=null),null!==f&&(f=null);var e=this.getPasteBin();e&&e&&(this.off(e,"focus",T),this.off(e,"focusin",T),this.off(e,"focusout",T),this.off(e,"paste",this.boundHandlePaste),e.parentElement.removeChild(e))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var e=this.getPasteBin();if(!e)return!1;if(e.firstChild&&"mcepastebin"===e.firstChild.id)return!1;var t=e.innerHTML;return!(!t||t===h)&&t},cleanPaste:function(e){var t,n,i,o,s=/]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/|/g),""],[new RegExp(/
$/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(]*>)?$/gi),""],[new RegExp(/\s+<\/span>/g)," "],[new RegExp(/
/g),"
"],[new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi),''],[new RegExp(/]*font-style:italic[^>]*>/gi),''],[new RegExp(/]*font-weight:(bold|700)[^>]*>/gi),''],[new RegExp(/<(\/?)(i|b|a)>/gi),"<$1$2>"],[new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi),''],[new RegExp(/<\/p>\n+/gi),"

"],[new RegExp(/\n+

/gi),""],[new RegExp(/(((?!/gi),"$1"]],this.cleanReplacements||[]);for(t=0;t"+e.split("

").join("

")+"

",n=i.querySelectorAll("a,p,div,br"),t=0;t"+i.innerHTML+"
":o.innerHTML=i.innerHTML,i.parentNode.replaceChild(o,i);for(s=e.querySelectorAll("span"),n=0;n]*>","g"),t=b.selection.getSelectionHtml(this.document).replace(/<[^\/>][^>]*><\/[^>]+>/gim,"").match(e);return!!t&&1r+e.offsetHeight-l-this.stickyTopOffset?(o.style.top=r+e.offsetHeight-l+"px",o.classList.remove("medium-editor-sticky-toolbar")):n>r-l-this.stickyTopOffset?(o.classList.add("medium-editor-sticky-toolbar"),o.style.top=this.stickyTopOffset+"px"):(o.classList.remove("medium-editor-sticky-toolbar"),o.style.top=r-l+"px"):o.style.top=r-l+"px",this.align){case"left":t=s.left;break;case"right":t=s.right-c;break;case"center":t=a-d}t<0?t=0:i'),t.onload=function(){var e=this.document.getElementById(n);e&&(e.removeAttribute("id"),e.removeAttribute("class"),e.src=t.result)}.bind(this))}.bind(this)),e.target.classList.remove("medium-editor-dragover")}}),b.extensions.imageDragging=E,C={},b.prototype={init:function(e,t){return this.options=function(e,t){return t&&[["allowMultiParagraphSelection","toolbar.allowMultiParagraphSelection"]].forEach(function(e){t.hasOwnProperty(e[0])&&void 0!==t[e[0]]&&b.util.deprecated(e[0],e[1],"v6.0.0")}),b.util.defaults({},t,e)}.call(this,this.defaults,t),this.origElements=e,this.options.elementsContainer||(this.options.elementsContainer=this.options.ownerDocument.body),this.setup()},setup:function(){this.isActive||(function(e){e._mediumEditors||(e._mediumEditors=[null]),this.id||(this.id=e._mediumEditors.length),e._mediumEditors[this.id]=this}.call(this,this.options.contentWindow),this.events=new b.Events(this),this.elements=[],this.addElements(this.origElements),0!==this.elements.length&&(this.isActive=!0,M.call(this),F.call(this)))},destroy:function(){this.isActive&&(this.isActive=!1,this.extensions.forEach(function(e){"function"==typeof e.destroy&&e.destroy()},this),this.events.destroy(),this.elements.forEach(function(e){this.options.spellcheck&&(e.innerHTML=e.innerHTML),e.removeAttribute("contentEditable"),e.removeAttribute("spellcheck"),e.removeAttribute("data-medium-editor-element"),e.classList.remove("medium-editor-element"),e.removeAttribute("role"),e.removeAttribute("aria-multiline"),e.removeAttribute("medium-editor-index"),e.removeAttribute("data-medium-editor-editor-index"),e.getAttribute("medium-editor-textarea-id")&&L(e)},this),this.elements=[],this.instanceHandleEditableKeydownEnter=null,this.instanceHandleEditableInput=null,function(e){e._mediumEditors&&e._mediumEditors[this.id]&&(e._mediumEditors[this.id]=null)}.call(this,this.options.contentWindow))},on:function(e,t,n,i){return this.events.attachDOMEvent(e,t,n,i),this},off:function(e,t,n,i){return this.events.detachDOMEvent(e,t,n,i),this},subscribe:function(e,t){return this.events.attachCustomEvent(e,t),this},unsubscribe:function(e,t){return this.events.detachCustomEvent(e,t),this},trigger:function(e,t,n){return this.events.triggerCustomEvent(e,t,n),this},delay:function(e){var t=this;return setTimeout(function(){t.isActive&&e()},this.options.delay)},serialize:function(){var e,t={},n=this.elements.length;for(e=0;e tags for some reason... - var regex = new RegExp('^lorem (||)ipsum(||) dolor$'); + var regex = new RegExp('^lorem (||)ipsum(||) dolor$'); expect(editor.elements[0].innerHTML).toMatch(regex); }); }); diff --git a/spec/full-content.spec.js b/spec/full-content.spec.js index 92d49e0b2..5a2c7c21a 100644 --- a/spec/full-content.spec.js +++ b/spec/full-content.spec.js @@ -44,7 +44,8 @@ describe('Full Content Action TestCase', function () { // Ensure the selection is still maintained editor.execAction('strikethrough'); - expect(this.el.innerHTML).toBe('

lorem ipsum dolor

'); + var regex = new RegExp('^

lorem ipsum dolor

$'); + expect(this.el.innerHTML).toMatch(regex); }); it('should justify all contents including multiple block elements', function () { diff --git a/spec/toolbar.spec.js b/spec/toolbar.spec.js index 07436fa06..02c78bda3 100644 --- a/spec/toolbar.spec.js +++ b/spec/toolbar.spec.js @@ -735,11 +735,11 @@ describe('MediumEditor.extensions.toolbar TestCase', function () { window.document.body.appendChild(relativeContainer); var editor = this.newMediumEditor('.editor', { - toolbar: { - relativeContainer: document.getElementById('someRelativeDiv') - } - }), - toolbarHTML = editor.getExtensionByName('toolbar').getToolbarElement().outerHTML; + toolbar: { + relativeContainer: document.getElementById('someRelativeDiv') + } + }), + toolbarHTML = editor.getExtensionByName('toolbar').getToolbarElement().outerHTML; expect(document.getElementById('someRelativeDiv').innerHTML).toBe(toolbarHTML); }); diff --git a/src/js/core.js b/src/js/core.js index 0c59a786c..e70d69618 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -418,6 +418,8 @@ return div; } + var initialContent = {}; + function initElement(element, editorId) { if (!element.getAttribute('data-medium-editor-element')) { if (element.nodeName.toLowerCase() === 'textarea') { @@ -684,8 +686,6 @@ } } - var initialContent = {}; - MediumEditor.prototype = { // NOT DOCUMENTED - exposed for backwards compatability init: function (elements, options) { diff --git a/src/js/extensions/paste.js b/src/js/extensions/paste.js index 0081a7907..c50f91088 100644 --- a/src/js/extensions/paste.js +++ b/src/js/extensions/paste.js @@ -11,12 +11,11 @@ }; /*jslint regexp: true*/ - /* - jslint does not allow character negation, because the negation - will not match any unicode characters. In the regexes in this - block, negation is used specifically to match the end of an html - tag, and in fact unicode characters *should* be allowed. - */ + + // jslint does not allow character negation, because the negation + // will not match any unicode characters. In the regexes in this + // block, negation is used specifically to match the end of an html + // tag, and in fact unicode characters *should* be allowed. function createReplacements() { return [ // Remove anything but the contents within the BODY element @@ -32,7 +31,7 @@ [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ''], [new RegExp(/<\/b>(]*>)?$/gi), ''], - // un-html spaces and newlines inserted by OS X + // un-html spaces and newlines inserted by OS X [new RegExp(/\s+<\/span>/g), ' '], [new RegExp(/
/g), '
'], @@ -45,10 +44,10 @@ //[replace google docs bolds with a span to be replaced once the html is inserted [new RegExp(/]*font-weight:(bold|700)[^>]*>/gi), ''], - // replace manually entered b/i/a tags with real ones + // replace manually entered b/i/a tags with real ones [new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], - // replace manually a tags with real ones, converting smart-quotes from google docs + // replace manually a tags with real ones, converting smart-quotes from google docs [new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi), '
'], // Newlines between paragraphs in html have no syntactic value, @@ -63,6 +62,7 @@ [new RegExp(/(((?!/gi), '$1'] ]; } + /*jslint regexp: false*/ /** diff --git a/src/sass/components/_anchor-preview.scss b/src/sass/components/_anchor-preview.scss index 823789224..46178641e 100644 --- a/src/sass/components/_anchor-preview.scss +++ b/src/sass/components/_anchor-preview.scss @@ -7,9 +7,9 @@ position: absolute; text-align: center; top: 0; + visibility: hidden; word-break: break-all; word-wrap: break-word; - visibility: hidden; z-index: $z-toolbar; a { diff --git a/src/sass/components/_placeholder.scss b/src/sass/components/_placeholder.scss index 3e1c26737..008526230 100644 --- a/src/sass/components/_placeholder.scss +++ b/src/sass/components/_placeholder.scss @@ -4,12 +4,12 @@ &:after { content: attr(data-placeholder) !important; font-style: italic; - position: absolute; left: 0; + margin: inherit; + padding: inherit; + position: absolute; top: 0; white-space: pre; - padding: inherit; - margin: inherit; } } @@ -19,9 +19,9 @@ &:after { content: attr(data-placeholder) !important; font-style: italic; + margin: inherit; + padding: inherit; position: relative; white-space: pre; - padding: inherit; - margin: inherit; } } diff --git a/src/sass/components/_toolbar-form.scss b/src/sass/components/_toolbar-form.scss index 9aceb0ea6..332dc1cc0 100644 --- a/src/sass/components/_toolbar-form.scss +++ b/src/sass/components/_toolbar-form.scss @@ -16,11 +16,11 @@ label { border: none; box-sizing: border-box; + display: inline-block; font-size: 14px; margin: 0; padding: 6px; width: 316px; - display: inline-block; &:focus { appearance: none; diff --git a/src/sass/components/_toolbar.scss b/src/sass/components/_toolbar.scss index 4996ca8f5..a037ff0dc 100644 --- a/src/sass/components/_toolbar.scss +++ b/src/sass/components/_toolbar.scss @@ -78,7 +78,7 @@ } .medium-editor-relative-toolbar { - position: relative; + position: relative; } .medium-editor-toolbar-active.medium-editor-stalker-toolbar { diff --git a/src/sass/medium-editor.scss b/src/sass/medium-editor.scss index b33bd573c..28bb20955 100644 --- a/src/sass/medium-editor.scss +++ b/src/sass/medium-editor.scss @@ -10,8 +10,8 @@ // contenteditable rules .medium-editor-element { - word-wrap: break-word; min-height: 30px; + word-wrap: break-word; img { max-width: 100%; diff --git a/src/sass/themes/beagle.scss b/src/sass/themes/beagle.scss index a7eb56d88..d5abb3698 100644 --- a/src/sass/themes/beagle.scss +++ b/src/sass/themes/beagle.scss @@ -32,6 +32,7 @@ $medium-editor-placeholder-color: #f8f5f3; min-width: $medium-editor-button-size; padding: 5px 12px; transition: background-color .2s ease-in, color .2s ease-in; + &:hover { background-color: $medium-editor-hover-color; color: $medium-editor-button-active-text-color; @@ -73,20 +74,23 @@ $medium-editor-placeholder-color: #f8f5f3; width: 220px; &::-webkit-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-moz-placeholder { /* Firefox 18- */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } - &::-moz-placeholder { /* Firefox 19+ */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + + &::-moz-placeholder { /* Firefox 19+ */ + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-ms-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } } @@ -96,7 +100,7 @@ $medium-editor-placeholder-color: #f8f5f3; } .medium-editor-toolbar-close { - margin-right: 16px; + margin-right: 16px; } } @@ -107,14 +111,14 @@ $medium-editor-placeholder-color: #f8f5f3; } .medium-editor-anchor-preview { - a { - color: $medium-editor-link-color; - text-decoration: none; - } + a { + color: $medium-editor-link-color; + text-decoration: none; + } } .medium-editor-toolbar-actions { - li, button { - border-radius: $medium-editor-border-radius; - } + li, button { + border-radius: $medium-editor-border-radius; + } } diff --git a/src/sass/themes/bootstrap.scss b/src/sass/themes/bootstrap.scss index d1fcc1d13..2bf4cb79b 100644 --- a/src/sass/themes/bootstrap.scss +++ b/src/sass/themes/bootstrap.scss @@ -33,6 +33,7 @@ $medium-editor-placeholder-color: #fff; height: $medium-editor-button-size; min-width: $medium-editor-button-size; transition: background-color .2s ease-in, color .2s ease-in; + &:hover { background-color: $medium-editor-hover-color; color: $medium-editor-button-active-text-color; @@ -68,20 +69,23 @@ $medium-editor-placeholder-color: #fff; height: $medium-editor-button-size; &::-webkit-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-moz-placeholder { /* Firefox 18- */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } - &::-moz-placeholder { /* Firefox 19+ */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + + &::-moz-placeholder { /* Firefox 19+ */ + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-ms-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } } diff --git a/src/sass/themes/default.scss b/src/sass/themes/default.scss index 5b2d746e2..0a8855837 100644 --- a/src/sass/themes/default.scss +++ b/src/sass/themes/default.scss @@ -15,21 +15,21 @@ $medium-editor-border-radius: 5px; } .medium-editor-toolbar { - background-color: $medium-editor-bgcolor; background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba($medium-editor-bgcolor, 0.75)); + background-color: $medium-editor-bgcolor; border: 1px solid #000; border-radius: $medium-editor-border-radius; box-shadow: 0 0 3px #000; li { button { - background-color: $medium-editor-bgcolor; background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba($medium-editor-bgcolor, 0.89)); + background-color: $medium-editor-bgcolor; border: 0; - border-right: 1px solid #000; border-left: 1px solid #333; border-left: 1px solid rgba(#fff, .1); - box-shadow: 0 2px 2px rgba(0,0,0,0.3); + border-right: 1px solid #000; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3); color: #fff; height: $medium-editor-button-size; min-width: $medium-editor-button-size; @@ -52,8 +52,8 @@ $medium-editor-border-radius: 5px; } .medium-editor-button-active { - background-color: #000; background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba(#000, 0.89)); + background-color: #000; color: #fff; } } diff --git a/src/sass/themes/flat.scss b/src/sass/themes/flat.scss index 8e75a3c76..15f7643de 100644 --- a/src/sass/themes/flat.scss +++ b/src/sass/themes/flat.scss @@ -8,13 +8,13 @@ $medium-editor-placeholder-color: #fff; // theme rules .medium-toolbar-arrow-under:after { - top: $medium-editor-button-size; border-color: $medium-editor-bgcolor transparent transparent transparent; + top: $medium-editor-button-size; } .medium-toolbar-arrow-over:before { - top: -8px; border-color: transparent transparent $medium-editor-bgcolor transparent; + top: -8px; } .medium-editor-toolbar { @@ -24,13 +24,14 @@ $medium-editor-placeholder-color: #fff; padding: 0; button { - min-width: $medium-editor-button-size; - height: $medium-editor-button-size; + background-color: transparent; border: none; border-right: 1px solid lighten($medium-editor-bgcolor, 20); - background-color: transparent; color: $medium-editor-link-color; + height: $medium-editor-button-size; + min-width: $medium-editor-button-size; transition: background-color .2s ease-in, color .2s ease-in; + &:hover { background-color: darken($medium-editor-bgcolor, 20); color: $medium-editor-button-active-text-color; @@ -50,28 +51,28 @@ $medium-editor-placeholder-color: #fff; .medium-editor-toolbar-form { .medium-editor-toolbar-input { - height: $medium-editor-button-size; background: $medium-editor-bgcolor; color: $medium-editor-link-color; + height: $medium-editor-button-size; &::-webkit-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } &:-moz-placeholder { /* Firefox 18- */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } - &::-moz-placeholder { /* Firefox 19+ */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + &::-moz-placeholder { /* Firefox 19+ */ + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } &:-ms-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } } diff --git a/src/sass/themes/mani.scss b/src/sass/themes/mani.scss index 16924545a..a72faa447 100644 --- a/src/sass/themes/mani.scss +++ b/src/sass/themes/mani.scss @@ -17,22 +17,23 @@ $medium-editor-border-radius: 2px; } .medium-editor-toolbar { - border: 1px solid $medium-editor-border-color; + background: linear-gradient(to top, $medium-editor-bgcolor, rgba(#fff, 1)); background-color: $medium-editor-bgcolor; background-color: rgba($medium-editor-bgcolor, .95); - background: linear-gradient(to top, $medium-editor-bgcolor, rgba(#fff, 1)); + border: 1px solid $medium-editor-border-color; border-radius: $medium-editor-border-radius; box-shadow: 0 2px 6px rgba(#000, .45); li { button { - min-width: $medium-editor-button-size; - height: $medium-editor-button-size; + background-color: transparent; border: none; border-right: 1px solid $medium-editor-border-color; - background-color: transparent; color: $medium-editor-link-color; + height: $medium-editor-button-size; + min-width: $medium-editor-button-size; transition: background-color .2s ease-in, color .2s ease-in; + &:hover { background-color: $medium-editor-bgcolor-alt; background-color: rgba($medium-editor-bgcolor-alt, .45); @@ -41,34 +42,34 @@ $medium-editor-border-radius: 2px; } .medium-editor-button-first { - border-top-left-radius: $medium-editor-border-radius; border-bottom-left-radius: $medium-editor-border-radius; + border-top-left-radius: $medium-editor-border-radius; } .medium-editor-button-last { - border-top-right-radius: $medium-editor-border-radius; border-bottom-right-radius: $medium-editor-border-radius; + border-top-right-radius: $medium-editor-border-radius; } .medium-editor-button-active { + background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba(#000, .1)); background-color: $medium-editor-bgcolor-alt; background-color: rgba($medium-editor-bgcolor-alt, .45); color: $medium-editor-button-active-text-color; - background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba(#000, .1)); } } } .medium-editor-toolbar-form { background: $medium-editor-bgcolor; - color: #999; border-radius: $medium-editor-border-radius; + color: #999; .medium-editor-toolbar-input { - height: $medium-editor-button-size; background: $medium-editor-bgcolor; - color: $medium-editor-link-color; box-sizing: border-box; + color: $medium-editor-link-color; + height: $medium-editor-button-size; } a { @@ -78,8 +79,8 @@ $medium-editor-border-radius: 2px; .medium-editor-toolbar-anchor-preview { background: $medium-editor-bgcolor; - color: $medium-editor-link-color; border-radius: $medium-editor-border-radius; + color: $medium-editor-link-color; } .medium-editor-placeholder:after { diff --git a/src/sass/themes/roman.scss b/src/sass/themes/roman.scss index 2bd63917a..36169e3f4 100644 --- a/src/sass/themes/roman.scss +++ b/src/sass/themes/roman.scss @@ -23,16 +23,17 @@ $medium-editor-border-radius: 5px; li { button { - min-width: $medium-editor-button-size; - height: $medium-editor-button-size; + background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba(#000, .2)); + background-color: transparent; border: none; border-right: 1px solid $medium-editor-border-color; - background-color: transparent; - color: $medium-editor-link-color; box-shadow: inset 0 0 3px #f8f8e6; - background: linear-gradient(to bottom, $medium-editor-bgcolor, rgba(#000, .2)); + color: $medium-editor-link-color; + height: $medium-editor-button-size; + min-width: $medium-editor-button-size; text-shadow: 1px 4px 6px #def, 0 0 0 #000, 1px 4px 6px #def; transition: background-color .2s ease-in; + &:hover { background-color: #fff; color: $medium-editor-button-hover-text-color; @@ -41,34 +42,34 @@ $medium-editor-border-radius: 5px; } .medium-editor-button-first { - border-top-left-radius: $medium-editor-border-radius; border-bottom-left-radius: $medium-editor-border-radius; + border-top-left-radius: $medium-editor-border-radius; } .medium-editor-button-last { - border-top-right-radius: $medium-editor-border-radius; border-bottom-right-radius: $medium-editor-border-radius; + border-top-right-radius: $medium-editor-border-radius; } .medium-editor-button-active { + background: linear-gradient(to top, $medium-editor-bgcolor, rgba(#000, .1)); background-color: #ccc; color: $medium-editor-button-active-text-color; color: rgba(#000, .8); - background: linear-gradient(to top, $medium-editor-bgcolor, rgba(#000, .1)); } } } .medium-editor-toolbar-form { background: $medium-editor-bgcolor; - color: #999; border-radius: $medium-editor-border-radius; + color: #999; .medium-editor-toolbar-input { - margin: 0; - height: $medium-editor-button-size; background: $medium-editor-bgcolor; color: $medium-editor-border-color; + height: $medium-editor-button-size; + margin: 0; } a { @@ -78,8 +79,8 @@ $medium-editor-border-radius: 5px; .medium-editor-toolbar-anchor-preview { background: $medium-editor-bgcolor; - color: $medium-editor-link-color; border-radius: $medium-editor-border-radius; + color: $medium-editor-link-color; } .medium-editor-placeholder:after { diff --git a/src/sass/themes/tim.scss b/src/sass/themes/tim.scss index d11af752c..14c0a2b45 100644 --- a/src/sass/themes/tim.scss +++ b/src/sass/themes/tim.scss @@ -33,6 +33,7 @@ $medium-editor-placeholder-color: #ffedd5; height: $medium-editor-button-size; min-width: $medium-editor-button-size; transition: background-color .2s ease-in, color .2s ease-in; + &:hover { background-color: $medium-editor-hover-color; color: $medium-editor-button-active-text-color; @@ -68,20 +69,23 @@ $medium-editor-placeholder-color: #ffedd5; height: $medium-editor-button-size; &::-webkit-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-moz-placeholder { /* Firefox 18- */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } - &::-moz-placeholder { /* Firefox 19+ */ - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + + &::-moz-placeholder { /* Firefox 19+ */ + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } + &:-ms-input-placeholder { - color: $medium-editor-placeholder-color; - color: rgba($medium-editor-placeholder-color, .8); + color: $medium-editor-placeholder-color; + color: rgba($medium-editor-placeholder-color, .8); } } From 23608031bc594f8900fbe71a8e7c61f4e108af1c Mon Sep 17 00:00:00 2001 From: Ronie Martinez Date: Mon, 11 Nov 2019 11:30:37 +0800 Subject: [PATCH 2/4] Change linters - Change jscs to eslinit - Change csslint to stylelint - Fix spellings - Swap css and jss tasks --- .eslintignore | 1 + .eslintrc | 114 +++++++ .jscsrc | 134 --------- .jshintignore | 5 + .jshintrc | 3 +- .stylelintrc | 4 + CONTRIBUTING.md | 8 +- Gruntfile.js | 37 +-- OPTIONS.md | 2 - README.md | 10 +- dist/js/medium-editor.js | 380 ++++++++++++------------ dist/js/medium-editor.min.js | 2 +- package.json | 5 +- spec/anchor-preview.spec.js | 12 +- spec/anchor.spec.js | 82 ++--- spec/auto-link.spec.js | 104 +++---- spec/buttons.spec.js | 10 +- spec/dyn-elements.spec.js | 49 +-- spec/events.spec.js | 5 +- spec/extension.spec.js | 40 +-- spec/fontname.spec.js | 8 +- spec/fontsize.spec.js | 8 +- spec/init.spec.js | 56 ++-- spec/keyboard-commands.spec.js | 22 +- spec/paste.spec.js | 74 +++-- spec/placeholder.spec.js | 22 +- spec/selection.spec.js | 33 +- spec/util.spec.js | 51 ++-- src/js/core.js | 156 +++++----- src/js/events.js | 24 +- src/js/extension.js | 11 +- src/js/extensions/README.md | 4 +- src/js/extensions/WALKTHROUGH-BUTTON.md | 6 +- src/js/extensions/anchor-preview.js | 2 +- src/js/extensions/anchor.js | 10 +- src/js/extensions/auto-link.js | 6 +- src/js/extensions/file-dragging.js | 2 +- src/js/extensions/fontname.js | 16 +- src/js/extensions/fontsize.js | 14 +- src/js/extensions/keyboard-commands.js | 7 +- src/js/extensions/paste.js | 8 +- src/js/extensions/toolbar.js | 30 +- src/js/globals.js | 2 +- src/js/polyfills.js | 2 +- src/js/selection.js | 36 ++- src/js/util.js | 54 ++-- 46 files changed, 828 insertions(+), 843 deletions(-) create mode 100644 .eslintignore create mode 100644 .eslintrc delete mode 100644 .jscsrc create mode 100644 .jshintignore create mode 100644 .stylelintrc diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..edc68d462 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +src/js/polyfills.js \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..1fe2e17f2 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,114 @@ +{ + "rules": { + "no-empty": [ + 2, + { + "allowEmptyCatch": true + } + ], + "brace-style": [ + 2, + "1tbs", + { + "allowSingleLine": true + } + ], + "no-mixed-spaces-and-tabs": 2, + "no-multiple-empty-lines": 2, + "no-multi-str": 2, + "no-multi-spaces": 2, + "space-unary-ops": [ + 2, + { + "words": false, + "nonwords": false + } + ], + "key-spacing": [ + 2, + { + "beforeColon": false, + "afterColon": true + } + ], + "no-spaced-func": 2, + "space-before-function-paren": [ + 2, + "always" + ], + "array-bracket-spacing": [ + 2, + "never", + { + "objectsInArrays": false + } + ], + "space-in-parens": [ + 2, + "never" + ], + "comma-dangle": [ + 2, + "never" + ], + "no-trailing-spaces": 2, + "camelcase": [ + 2, + { + "properties": "never" + } + ], + "comma-style": [ + 2, + "last" + ], + "curly": [ + 2, + "all" + ], + "one-var": [ + 2, + "consecutive" + ], + "operator-linebreak": [ + 2, + "after" + ], + "semi": [ + 2, + "always" + ], + "space-infix-ops": 2, + "keyword-spacing": [ + 2, + { + "overrides": { + "else": { + "before": true + }, + "while": { + "before": true + }, + "catch": { + "before": true + } + } + } + ], + "space-before-blocks": [ + 2, + "always" + ], + "indent": [ + 2, + 4, + { + "SwitchCase": 1 + } + ], + "quotes": [ + 2, + "single" + ] + } +} diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index d2a8313e3..000000000 --- a/.jscsrc +++ /dev/null @@ -1,134 +0,0 @@ -{ - "disallowEmptyBlocks": true, - "disallowKeywordsOnNewLine": [ - "else" - ], - "disallowMixedSpacesAndTabs": true, - "disallowMultipleLineBreaks": true, - "disallowMultipleLineStrings": true, - "disallowMultipleSpaces": true, - "disallowNewlineBeforeBlockStatements": true, - "disallowSpaceAfterPrefixUnaryOperators": [ - "++", - "--", - "+", - "-", - "~", - "!" - ], - "disallowSpaceAfterObjectKeys": true, - "disallowSpaceBeforePostfixUnaryOperators": [ - "++", - "--" - ], - "disallowSpacesInCallExpression": true, - "disallowSpacesInFunctionDeclaration": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInsideArrayBrackets": true, - "disallowSpacesInsideBrackets": true, - "disallowSpacesInsideParentheses": true, - "disallowTrailingComma": true, - "disallowTrailingWhitespace": true, - "requireBlocksOnNewline": true, - "requireCamelCaseOrUpperCaseIdentifiers": true, - "requireCapitalizedConstructors": true, - "requireCommaBeforeLineBreak": true, - "requireCurlyBraces": [ - "if", - "else", - "for", - "while", - "do", - "try", - "catch" - ], - "requireLineBreakAfterVariableAssignment": true, - "requireMultipleVarDecl": true, - "requireOperatorBeforeLineBreak": [ - "?", - "=", - "+", - "-", - "/", - "*", - "==", - "===", - "!=", - "!==", - ">", - ">=", - "<", - "<=" - ], - "requireSemicolons": true, - "requireSpaceAfterBinaryOperators": [ - "=", - ",", - "+", - "-", - "/", - "*", - "==", - "===", - "!=", - "!==" - ], - "requireSpaceAfterKeywords": [ - "do", - "for", - "if", - "else", - "switch", - "case", - "try", - "catch", - "void", - "while", - "with", - "return", - "typeof", - "function" - ], - "requireSpaceBeforeBinaryOperators": [ - "=", - "+", - "-", - "/", - "*", - "==", - "===", - "!=", - "!==" - ], - "requireSpaceBeforeBlockStatements": true, - "requireSpaceBeforeKeywords": [ - "else", - "while", - "catch" - ], - "requireSpaceBetweenArguments": true, - "requireSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true, - "beforeOpeningCurlyBrace": true - }, - "requireSpacesInConditionalExpression": { - "afterTest": true, - "beforeConsequent": true, - "afterConsequent": true, - "beforeAlternate": true - }, - "requireSpacesInForStatement": true, - "requireSpacesInFunctionDeclaration": { - "beforeOpeningCurlyBrace": true - }, - "requireSpacesInFunction": { - "beforeOpeningCurlyBrace": true - }, - "requireSpacesInsideObjectBrackets": { - "allExcept": [ "}", ")" ] - }, - "validateIndentation": 4, - "validateParameterSeparator": ", ", - "validateQuoteMarks": "'" -} diff --git a/.jshintignore b/.jshintignore new file mode 100644 index 000000000..21504fcd6 --- /dev/null +++ b/.jshintignore @@ -0,0 +1,5 @@ +demo/ +dist/ +spec/vendor/*.js +src/js/polyfills.js +index.js \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index 09b469916..0de71e5b5 100644 --- a/.jshintrc +++ b/.jshintrc @@ -14,5 +14,6 @@ "sub": true, "undef": true, "unused": true, - "validthis": true + "validthis": true, + "esversion": 6 } diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 000000000..f97c1549c --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,4 @@ +{ + "rules": { + } +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d3edcf551..cbb685de6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,9 +20,9 @@ To help create consistent looking code throughout the project, we use a few tool We use [JSHint](http://jshint.com/) on each build to find easy-to-catch errors and potential problems in our js. You can find our JSHint settings in the `.jshintrc` file in the root of the project. -#### jscs +#### ESLint -We use [jscs](http://jscs.info/) on each build to enforce some code style rules we have for our project. You can find our jscs settings in the `.jscsrc` file in the root of the project. +We use [ESLint](https://eslint.org/) on each build to enforce some code style rules we have for our project. You can find our ESLint settings in the `.eslintrc` file in the root of the project. #### EditorConfig @@ -48,8 +48,8 @@ grunt These are the other available grunt tasks: * __js__: runs jslint and jasmine tests and creates minified and concatenated versions of the script; -* __css__: runs autoprefixer and csslint -* __test__: runs jasmine tests, jslint and csslint +* __css__: runs autoprefixer and stylelint +* __test__: runs jasmine tests, jslint and stylelint * __watch__: watch for modifications on script/scss files * __spec__: runs a task against a specified file diff --git a/Gruntfile.js b/Gruntfile.js index 2525de6e8..8045c7de8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -93,7 +93,6 @@ module.exports = function (grunt) { // TODO: build check with debug and devel false gruntConfig.jshint = { options: { - ignores: ['src/js/polyfills.js'], jshintrc: true, reporter: require('jshint-stylish') }, @@ -106,16 +105,14 @@ module.exports = function (grunt) { } }; - // TODO: "maximumLineLength": 120 - gruntConfig.jscs = { + gruntConfig.eslint = { src: [ 'src/js/**/*.js', 'spec/*.spec.js', - 'Gruntfile.js', - '!src/js/polyfills.js' + 'Gruntfile.js' ], options: { - config: '.jscsrc' + config: '.eslintrc' } }; @@ -172,20 +169,10 @@ module.exports = function (grunt) { } }; - gruntConfig.csslint = { - strict: { - options: { - 'box-sizing': false, - 'compatible-vendor-prefixes': false, - 'fallback-colors': false, - 'gradients': false, - 'important': false, - 'import': 2, - 'outline-none': false, - 'adjoining-classes': false - }, - src: 'dist/css/**/*.css' - } + gruntConfig.stylelint = { + all: [ + 'dist/css/**/*.css' + ] }; gruntConfig.sass = { @@ -333,15 +320,15 @@ module.exports = function (grunt) { }); if (parseInt(process.env.TRAVIS_PULL_REQUEST, 10) > 0) { - grunt.registerTask('travis', ['jshint', 'jscs', 'jasmine:suite', 'csslint', 'coveralls']); + grunt.registerTask('travis', ['jshint', 'eslint', 'jasmine:suite', 'stylelint', 'coveralls']); } else { - grunt.registerTask('travis', ['connect', 'jshint', 'jscs', 'jasmine:suite', 'csslint', 'saucelabs-jasmine', 'coveralls']); + grunt.registerTask('travis', ['connect', 'jshint', 'eslint', 'jasmine:suite', 'stylelint', 'saucelabs-jasmine', 'coveralls']); } - grunt.registerTask('test', ['jshint', 'jscs', 'concat', 'jasmine:suite', 'csslint']); + grunt.registerTask('test', ['jshint', 'eslint', 'concat', 'jasmine:suite', 'stylelint']); grunt.registerTask('sauce', ['connect', 'saucelabs-jasmine']); - grunt.registerTask('js', ['jshint', 'jscs', 'concat', 'jasmine:suite', 'uglify']); - grunt.registerTask('css', ['sass', 'autoprefixer', 'cssmin', 'csslint']); + grunt.registerTask('js', ['jshint', 'eslint', 'concat', 'jasmine:suite', 'uglify']); + grunt.registerTask('css', ['sass', 'autoprefixer', 'cssmin', 'stylelint']); grunt.registerTask('default', ['css', 'js']); grunt.registerTask('spec', 'Runs a task on a specified file', function (taskName, fileName) { diff --git a/OPTIONS.md b/OPTIONS.md index 9739e1b52..1e752672b 100644 --- a/OPTIONS.md +++ b/OPTIONS.md @@ -333,7 +333,6 @@ var editor = new MediumEditor('.editable', { hideDelay: 500, previewValueSelector: 'a' } -} }); ``` @@ -435,7 +434,6 @@ var editor = new MediumEditor('.editable', { targetCheckbox: false, targetCheckboxText: 'Open in new window' } -} }); ``` diff --git a/README.md b/README.md index 0db7945e1..1ac5fb6d3 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,6 @@ var editor = new MediumEditor('.editable', { hideDelay: 500, previewValueSelector: 'a' } -} }); ``` @@ -320,7 +319,6 @@ var editor = new MediumEditor('.editable', { targetCheckbox: false, targetCheckboxText: 'Open in new window' } -} }); ``` @@ -677,8 +675,8 @@ grunt These are the other available grunt tasks: * __js__: runs jslint and jasmine tests and creates minified and concatenated versions of the script; -* __css__: runs autoprefixer and csslint -* __test__: runs jasmine tests, jslint and csslint +* __css__: runs autoprefixer and stylelint +* __test__: runs jasmine tests, jslint and stylelint * __watch__: watch for modifications on script/scss files * __spec__: runs a task against a specified file @@ -704,9 +702,9 @@ To help create consistent looking code throughout the project, we use a few tool We use [JSHint](http://jshint.com/) on each build to find easy-to-catch errors and potential problems in our js. You can find our JSHint settings in the `.jshintrc` file in the root of the project. -#### jscs +#### ESLint -We use [jscs](http://jscs.info/) on each build to enforce some code style rules we have for our project. You can find our jscs settings in the `.jscsrc` file in the root of the project. +We use [ESLint](https://eslint.org/) on each build to enforce some code style rules we have for our project. You can find our ESLint settings in the `.eslintrc` file in the root of the project. #### EditorConfig diff --git a/dist/js/medium-editor.js b/dist/js/medium-editor.js index c9b046d65..1fbad845c 100644 --- a/dist/js/medium-editor.js +++ b/dist/js/medium-editor.js @@ -182,7 +182,7 @@ if (!("classList" in document.createElement("_"))) { */ /*global self, unescape */ -/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, +/*jslint bitwise: true, regexp: true, confusion: true, vars: true, white: true, plusplus: true */ /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ @@ -399,7 +399,7 @@ if (!("classList" in document.createElement("_"))) { 'use strict'; -function MediumEditor(elements, options) { +function MediumEditor (elements, options) { 'use strict'; return this.init(elements, options); } @@ -409,7 +409,7 @@ MediumEditor.extensions = {}; (function (window) { 'use strict'; - function copyInto(overwrite, dest) { + function copyInto (overwrite, dest) { var prop, sources = Array.prototype.slice.call(arguments, 2); dest = dest || {}; @@ -438,13 +438,14 @@ MediumEditor.extensions = {}; testText = document.createTextNode(' '); testParent.appendChild(testText); nodeContainsWorksWithTextNodes = testParent.contains(testText); - } catch (exc) {} + } catch (exc) { + } var Util = { // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562 // by rg89 - isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))), + isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]+[.0-9]*)').exec(navigator.userAgent) !== null))), isEdge: (/Edge\/\d+/).exec(navigator.userAgent) !== null, @@ -473,11 +474,7 @@ MediumEditor.extensions = {}; * See #591 */ isMetaCtrlKey: function (event) { - if ((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)) { - return true; - } - - return false; + return !!((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)); }, /** @@ -494,11 +491,7 @@ MediumEditor.extensions = {}; return keyCode === keys; } - if (-1 === keys.indexOf(keyCode)) { - return false; - } - - return true; + return -1 !== keys.indexOf(keyCode); }, getKeyCode: function (event) { @@ -524,12 +517,12 @@ MediumEditor.extensions = {}; emptyElementNames: ['br', 'col', 'colgroup', 'hr', 'img', 'input', 'source', 'wbr'], - extend: function extend(/* dest, source1, source2, ...*/) { + extend: function extend (/* dest, source1, source2, ...*/) { var args = [true].concat(Array.prototype.slice.call(arguments)); return copyInto.apply(this, args); }, - defaults: function defaults(/*dest, source1, source2, ...*/) { + defaults: function defaults (/*dest, source1, source2, ...*/) { var args = [false].concat(Array.prototype.slice.call(arguments)); return copyInto.apply(this, args); }, @@ -633,12 +626,12 @@ MediumEditor.extensions = {}; var textIndexOfEndOfFarthestNode, endSplitPoint; textIndexOfEndOfFarthestNode = currentTextIndex + currentNode.nodeValue.length + - (newNode ? newNode.nodeValue.length : 0) - 1; + (newNode ? newNode.nodeValue.length : 0) - 1; endSplitPoint = matchEndIndex - currentTextIndex - - (newNode ? currentNode.nodeValue.length : 0); + (newNode ? currentNode.nodeValue.length : 0); if (textIndexOfEndOfFarthestNode >= matchEndIndex && - currentTextIndex !== textIndexOfEndOfFarthestNode && - endSplitPoint !== 0) { + currentTextIndex !== textIndexOfEndOfFarthestNode && + endSplitPoint !== 0) { (newNode || currentNode).splitText(endSplitPoint); } }, @@ -648,7 +641,7 @@ MediumEditor.extensions = {}; * 1) All text content of the elements are in separate blocks. No piece of text content should span * across multiple blocks. This means no element return by this function should have * any blocks as children. - * 2) The union of the textcontent of all of the elements returned here covers all + * 2) The union of the text content of all of the elements returned here covers all * of the text within the element. * * @@ -706,7 +699,7 @@ MediumEditor.extensions = {}; // - A descendant of a sibling element // - A sibling text node of an ancestor // - A descendant of a sibling element of an ancestor - findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) { + findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent (rootNode, targetNode, ownerDocument) { var pastTarget = false, nextNode, nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false); @@ -744,7 +737,7 @@ MediumEditor.extensions = {}; return previousSibling; }, - isDescendant: function isDescendant(parent, child, checkEquality) { + isDescendant: function isDescendant (parent, child, checkEquality) { if (!parent || !child) { return false; } @@ -769,7 +762,7 @@ MediumEditor.extensions = {}; }, // https://github.com/jashkenas/underscore - isElement: function isElement(obj) { + isElement: function isElement (obj) { return !!(obj && obj.nodeType === 1); }, @@ -861,7 +854,8 @@ MediumEditor.extensions = {}; if (!MediumEditor.util.isEdge && doc.queryCommandSupported('insertHTML')) { try { return doc.execCommand.apply(doc, ecArgs); - } catch (ignore) {} + } catch (ignore) { + } } selection = doc.getSelection(); @@ -875,13 +869,13 @@ MediumEditor.extensions = {}; if (Util.isMediumEditorElement(toReplace) && !toReplace.firstChild) { range.selectNode(toReplace.appendChild(doc.createTextNode(''))); } else if ((toReplace.nodeType === 3 && range.startOffset === 0 && range.endOffset === toReplace.nodeValue.length) || - (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) { + (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) { // Ensure range covers maximum amount of nodes as possible // By moving up the DOM and selecting ancestors whose only child is the range while (!Util.isMediumEditorElement(toReplace) && - toReplace.parentNode && - toReplace.parentNode.childNodes.length === 1 && - !Util.isMediumEditorElement(toReplace.parentNode)) { + toReplace.parentNode && + toReplace.parentNode.childNodes.length === 1 && + !Util.isMediumEditorElement(toReplace.parentNode)) { toReplace = toReplace.parentNode; } range.selectNode(toReplace); @@ -954,7 +948,7 @@ MediumEditor.extensions = {}; tagName = '<' + tagName + '>'; } - // When FF, IE and Edge, we have to handle blockquote node seperately as 'formatblock' does not work. + // When FF, IE and Edge, we have to handle blockquote node separately as 'formatblock' does not work. // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') { // For IE, just use outdent @@ -1554,7 +1548,7 @@ MediumEditor.extensions = {}; }, guid: function () { - function _s4() { + function _s4 () { return Math .floor((1 + Math.random()) * 0x10000) .toString(16) @@ -1643,11 +1637,12 @@ MediumEditor.extensions = {}; * current instance of MediumEditor when this is called. * All helper methods will exist as well */ - init: function () {}, + init: function () { + }, /* base: [MediumEditor instance] * - * If not overriden, this will be set to the current instance + * If not overridden, this will be set to the current instance * of MediumEditor, before the init method is called */ base: undefined, @@ -1737,7 +1732,7 @@ MediumEditor.extensions = {}; /* setInactive: [function ()] * * If implemented, this function is called when MediumEditor knows - * that this extension is currently disabled. Curently, this + * that this extension is currently disabled. Currently, this * is called at the beginning of each state change for * the editor & toolbar. After calling this, MediumEditor * will attempt to update the extension, either via checkState() @@ -1768,7 +1763,7 @@ MediumEditor.extensions = {}; /* window: [Window] * - * If not overriden, this will be set to the window object + * If not overridden, this will be set to the window object * to be used by MediumEditor and its extensions. This is * passed via the 'contentWindow' option to MediumEditor * and is the global 'window' object by default @@ -1777,7 +1772,7 @@ MediumEditor.extensions = {}; /* document: [Document] * - * If not overriden, this will be set to the document object + * If not overridden, this will be set to the document object * to be used by MediumEditor and its extensions. This is * passed via the 'ownerDocument' optin to MediumEditor * and is the global 'document' object by default @@ -1844,7 +1839,7 @@ MediumEditor.extensions = {}; (function () { 'use strict'; - function filterOnlyParentElements(node) { + function filterOnlyParentElements (node) { if (MediumEditor.util.isBlockContainer(node)) { return NodeFilter.FILTER_ACCEPT; } else { @@ -1988,12 +1983,11 @@ MediumEditor.extensions = {}; if (allowRangeToStartAtEndOfNode || selectionState.start < nextCharIndex) { range.setStart(node, selectionState.start - charIndex); foundStart = true; - } - // We're at the end of a text node where the selection could start but we shouldn't - // make the selection start here because allowRangeToStartAtEndOfNode is false. - // However, we should keep a reference to this node in case there aren't any more - // text nodes after this, so that we have somewhere to import the selection to - else { + } else { + // We're at the end of a text node where the selection could start but we shouldn't + // make the selection start here because allowRangeToStartAtEndOfNode is false. + // However, we should keep a reference to this node in case there aren't any more + // text nodes after this, so that we have somewhere to import the selection to lastTextNode = node; } } @@ -2065,9 +2059,9 @@ MediumEditor.extensions = {}; return node.nodeName.toLowerCase() === 'a'; }; if (selectionState.start === selectionState.end && - range.startContainer.nodeType === 3 && - range.startOffset === range.startContainer.nodeValue.length && - MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { + range.startContainer.nodeType === 3 && + range.startOffset === range.startContainer.nodeValue.length && + MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { var prevNode = range.startContainer, currentNode = range.startContainer.parentNode; while (currentNode !== null && currentNode.nodeName.toLowerCase() !== 'a') { @@ -2145,7 +2139,7 @@ MediumEditor.extensions = {}; }, // Returns -1 unless the cursor is at the beginning of a paragraph/block - // If the paragraph/block is preceeded by empty paragraphs/block (with no text) + // If the paragraph/block is preceded by empty paragraphs/block (with no text) // it will return the number of empty paragraphs before the cursor. // Otherwise, it will return 0, which indicates the cursor is at the beginning // of a paragraph/block, and not at the end of the paragraph/block before it @@ -2170,9 +2164,8 @@ MediumEditor.extensions = {}; // If there is no previous sibling, this is the first text element in the editor if (!previousSibling) { return -1; - } - // If the previous sibling has text, then there are no empty blocks before this - else if (previousSibling.nodeValue) { + } else if (previousSibling.nodeValue) { + // If the previous sibling has text, then there are no empty blocks before this return -1; } } @@ -2222,7 +2215,7 @@ MediumEditor.extensions = {}; if (next === img) { break; } - // If we haven't hit the iamge, but found text that contains content + // If we haven't hit the image, but found text that contains content // then the range doesn't start with an image if (next.nodeValue) { return false; @@ -2307,7 +2300,7 @@ MediumEditor.extensions = {}; selectionContainsContent: function (doc) { var sel = doc.getSelection(); - // collapsed selection or selection withour range doesn't contain content + // collapsed selection or selection without range doesn't contain content if (!sel || sel.isCollapsed || !sel.rangeCount) { return false; } @@ -2348,7 +2341,7 @@ MediumEditor.extensions = {}; // http://stackoverflow.com/questions/4176923/html-of-selected-text // by Tim Down - getSelectionHtml: function getSelectionHtml(doc) { + getSelectionHtml: function getSelectionHtml (doc) { var i, html = '', sel = doc.getSelection(), @@ -2369,9 +2362,9 @@ MediumEditor.extensions = {}; * * @param {DOMElement} An element containing the cursor to find offsets relative to. * @param {Range} A Range representing cursor position. Will window.getSelection if none is passed. - * @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element + * @return {Object} 'left' and 'right' attributes contain offsets from beginning and end of Element */ - getCaretOffsets: function getCaretOffsets(element, range) { + getCaretOffsets: function getCaretOffsets (element, range) { var preCaretRange, postCaretRange; if (!range) { @@ -2522,7 +2515,7 @@ MediumEditor.extensions = {}; (function () { 'use strict'; - function isElementDescendantOfExtension(extensions, element) { + function isElementDescendantOfExtension (extensions, element) { if (!extensions) { return false; } @@ -2747,17 +2740,17 @@ MediumEditor.extensions = {}; // Helper method to call all listeners to execCommand var callListeners = function (args, result) { - if (doc.execCommand.listeners) { - doc.execCommand.listeners.forEach(function (listener) { - listener({ - command: args[0], - value: args[2], - args: args, - result: result + if (doc.execCommand.listeners) { + doc.execCommand.listeners.forEach(function (listener) { + listener({ + command: args[0], + value: args[2], + args: args, + result: result + }); }); - }); - } - }, + } + }, // Create a wrapper method for execCommand which will: // 1) Call document.execCommand with the correct arguments @@ -2987,7 +2980,7 @@ MediumEditor.extensions = {}; if (!this.contentCache) { return; } - // An event triggered which signifies that the user may have changed someting + // An event triggered which signifies that the user may have changed something // Look in our cache of input for the contenteditables to see if something changed var index = target.getAttribute('medium-editor-index'), html = target.innerHTML; @@ -3843,7 +3836,7 @@ MediumEditor.extensions = {}; if (this.targetCheckbox) { // fixme: ideally, this targetCheckboxText would be a formLabel too, - // figure out how to deprecate? also consider `fa-` icon default implcations. + // figure out how to deprecate? also consider `fa-` icon default implications. template.push( '
', '', @@ -3887,7 +3880,7 @@ MediumEditor.extensions = {}; buttonCheckbox = this.getAnchorButtonCheckbox(); opts = opts || { value: '' }; - // TODO: This is for backwards compatability + // TODO: This is for backwards compatibility // We don't need to support the 'string' argument in 6.0.0 if (typeof opts === 'string') { opts = { @@ -3992,11 +3985,11 @@ MediumEditor.extensions = {}; // Matches protocol relative "//" // Matches common external protocols "mailto:" "tel:" "maps:" // Matches relative hash link, begins with "#" - var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i, + var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^#/i, hasScheme = urlSchemeRegex.test(value), scheme = '', // telRegex is a regex for checking if the string is a telephone number - telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/, + telRegex = /^\+?\s?\(?(?:\d\s?-?\)?){3,20}$/, urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/), path = urlParts[1], query = urlParts[2], @@ -4009,7 +4002,7 @@ MediumEditor.extensions = {}; if (!hasScheme) { var host = path.split('/')[0]; // if the host part of the path looks like a hostname - if (host.match(/.+(\.|:).+/) || host === 'localhost') { + if (host.match(/.+([.:]).+/) || host === 'localhost') { scheme = 'http://'; } } @@ -4191,7 +4184,7 @@ MediumEditor.extensions = {}; showPreview: function (anchorEl) { if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active') || - anchorEl.getAttribute('data-disable-preview')) { + anchorEl.getAttribute('data-disable-preview')) { return true; } @@ -4427,7 +4420,7 @@ MediumEditor.extensions = {}; LINK_REGEXP; WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003', - '\u2028', '\u2029']; + '\u2028', '\u2029']; KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' + 'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' + 'bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|' + @@ -4448,7 +4441,7 @@ MediumEditor.extensions = {}; LINK_REGEXP = new RegExp(LINK_REGEXP_TEXT, 'gi'); - function nodeIsNotInsideAnchorTag(node) { + function nodeIsNotInsideAnchorTag (node) { return !MediumEditor.util.getClosestTag(node, 'a'); } @@ -4607,7 +4600,7 @@ MediumEditor.extensions = {}; for (var matchIndex = 0; matchIndex < matches.length; matchIndex++) { var matchingTextNodes = MediumEditor.util.findOrCreateMatchingTextNodes(this.document, element, - matches[matchIndex]); + matches[matchIndex]); if (this.shouldNotLink(matchingTextNodes)) { continue; } @@ -4676,7 +4669,7 @@ MediumEditor.extensions = {}; var CLASS_DRAG_OVER = 'medium-editor-dragover'; - function clearClassNames(element) { + function clearClassNames (element) { var editable = MediumEditor.util.getContainerEditorElement(element), existing = Array.prototype.slice.call(editable.parentElement.querySelectorAll('.' + CLASS_DRAG_OVER)); @@ -4831,16 +4824,15 @@ MediumEditor.extensions = {}; if (data.meta === isMeta && data.shift === isShift && (data.alt === isAlt || - undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0 + undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0 event.preventDefault(); event.stopPropagation(); // command can be a function to execute if (typeof data.command === 'function') { data.command.apply(this); - } - // command can be false so the shortcut is just disabled - else if (false !== data.command) { + } else if (false !== data.command) { + // command can be false so the shortcut is just disabled this.execAction(data.command); } } @@ -4956,7 +4948,7 @@ MediumEditor.extensions = {}; this.on(form, 'click', this.handleFormClick.bind(this)); // Add font names - for (var i = 0; i' : - '✓'; + '' : + '✓'; form.appendChild(save); // Handle save button clicks (capture) @@ -4982,10 +4974,10 @@ MediumEditor.extensions = {}; // Add close button close.setAttribute('href', '#'); - close.className = 'medium-editor-toobar-close'; + close.className = 'medium-editor-toolbar-close'; close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; + '' : + '×'; form.appendChild(close); // Handle close button clicks @@ -5147,12 +5139,12 @@ MediumEditor.extensions = {}; // Handle typing in the textbox this.on(input, 'change', this.handleSliderChange.bind(this)); - // Add save buton + // Add save button save.setAttribute('href', '#'); - save.className = 'medium-editor-toobar-save'; + save.className = 'medium-editor-toolbar-save'; save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '✓'; + '' : + '✓'; form.appendChild(save); // Handle save button clicks (capture) @@ -5160,10 +5152,10 @@ MediumEditor.extensions = {}; // Add close button close.setAttribute('href', '#'); - close.className = 'medium-editor-toobar-close'; + close.className = 'medium-editor-toolbar-close'; close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; + '' : + '×'; form.appendChild(close); // Handle close button clicks @@ -5231,7 +5223,7 @@ MediumEditor.extensions = {}; // will not match any unicode characters. In the regexes in this // block, negation is used specifically to match the end of an html // tag, and in fact unicode characters *should* be allowed. - function createReplacements() { + function createReplacements () { return [ // Remove anything but the contents within the BODY element [new RegExp(/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g), ''], @@ -5260,7 +5252,7 @@ MediumEditor.extensions = {}; [new RegExp(/]*font-weight:(bold|700)[^>]*>/gi), ''], // replace manually entered b/i/a tags with real ones - [new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], + [new RegExp(/<(\/?)([iba])>/gi), '<$1$2>'], // replace manually a tags with real ones, converting smart-quotes from google docs [new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi), ''], @@ -5274,7 +5266,7 @@ MediumEditor.extensions = {}; [new RegExp(/<\/?o:[a-z]*>/gi), ''], // Microsoft Word adds some special elements around list items - [new RegExp(/(((?!/gi), '$1'] + [new RegExp(/(((?!/gi), '$1'] ]; } @@ -5289,7 +5281,7 @@ MediumEditor.extensions = {}; * @param {doc} reference to document * @return {Object} Object with mime types and data for those mime types. */ - function getClipboardContent(event, win, doc) { + function getClipboardContent (event, win, doc) { var dataTransfer = event.clipboardData || win.clipboardData || doc.dataTransfer, data = {}; @@ -6043,7 +6035,7 @@ MediumEditor.extensions = {}; } // If the button already exists as an extension, it'll be returned - // othwerise it'll create the default built-in button + // otherwise it'll create the default built-in button extension = this.base.addBuiltInExtension(buttonName, buttonOpts); if (extension && typeof extension.getButton === 'function') { @@ -6140,8 +6132,8 @@ MediumEditor.extensions = {}; handleDocumentMouseup: function (event) { // Do not trigger checkState when mouseup fires over the toolbar if (event && - event.target && - MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) { + event.target && + MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) { return false; } this.checkState(); @@ -6233,7 +6225,7 @@ MediumEditor.extensions = {}; // Responding to changes in user selection - // Checks for existance of multiple block elements in the current selection + // Checks for existence of multiple block elements in the current selection multipleBlockElementsSelected: function () { var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex regexBlockElements = new RegExp('<(' + MediumEditor.util.blockContainerElementNames.join('|') + ')[^>]*>', 'g'), @@ -6264,8 +6256,8 @@ MediumEditor.extensions = {}; * adjacent text node that actually has content in it, and move the selectionRange start there. */ if (this.standardizeSelectionStart && - selectionRange.startContainer.nodeValue && - (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) { + selectionRange.startContainer.nodeValue && + (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) { var adjacentNode = MediumEditor.util.findAdjacentTextNodeWithContent(MediumEditor.selection.getSelectionElement(this.window), selectionRange.startContainer, this.document); if (adjacentNode) { var offset = 0; @@ -6286,7 +6278,7 @@ MediumEditor.extensions = {}; // If no editable has focus OR selection is inside contenteditable = false // hide toolbar if (!this.base.getFocusedElement() || - MediumEditor.selection.selectionInContentEditableFalse(this.window)) { + MediumEditor.selection.selectionInContentEditableFalse(this.window)) { return this.hideToolbar(); } @@ -6295,8 +6287,8 @@ MediumEditor.extensions = {}; // hide toolbar var selectionElement = MediumEditor.selection.getSelectionElement(this.window); if (!selectionElement || - this.getEditorElements().indexOf(selectionElement) === -1 || - selectionElement.getAttribute('data-disable-toolbar')) { + this.getEditorElements().indexOf(selectionElement) === -1 || + selectionElement.getAttribute('data-disable-toolbar')) { return this.hideToolbar(); } @@ -6346,8 +6338,8 @@ MediumEditor.extensions = {}; if (typeof extension.checkState === 'function') { extension.checkState(parentNode); } else if (typeof extension.isActive === 'function' && - typeof extension.isAlreadyApplied === 'function' && - typeof extension.setActive === 'function') { + typeof extension.isAlreadyApplied === 'function' && + typeof extension.setActive === 'function') { if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) { extension.setActive(); } @@ -6380,8 +6372,8 @@ MediumEditor.extensions = {}; // Make sure the selection parent isn't outside of the contenteditable if (!this.getEditorElements().some(function (element) { - return MediumEditor.util.isDescendant(element, parentNode, true); - })) { + return MediumEditor.util.isDescendant(element, parentNode, true); + })) { return; } @@ -6451,11 +6443,11 @@ MediumEditor.extensions = {}; if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) { toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px'; toolbarElement.classList.remove('medium-editor-sticky-toolbar'); - // Stick the toolbar to the top of the window + // Stick the toolbar to the top of the window } else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) { toolbarElement.classList.add('medium-editor-sticky-toolbar'); toolbarElement.style.top = this.stickyTopOffset + 'px'; - // Normal static toolbar position + // Normal static toolbar position } else { toolbarElement.classList.remove('medium-editor-sticky-toolbar'); toolbarElement.style.top = containerTop - toolbarHeight + 'px'; @@ -6636,7 +6628,7 @@ MediumEditor.extensions = {}; // Event handlers that shouldn't be exposed externally - function handleDisableExtraSpaces(event) { + function handleDisableExtraSpaces (event) { var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), textContent = node.textContent, caretPositions = MediumEditor.selection.getCaretOffsets(node); @@ -6646,7 +6638,7 @@ MediumEditor.extensions = {}; } } - function handleDisabledEnterKeydown(event, element) { + function handleDisabledEnterKeydown (event, element) { if (this.options.disableReturn || element.getAttribute('data-disable-return')) { event.preventDefault(); } else if (this.options.disableDoubleReturn || element.getAttribute('data-disable-double-return')) { @@ -6655,13 +6647,13 @@ MediumEditor.extensions = {}; // if current text selection is empty OR previous sibling text is empty OR it is not a list if ((node && node.textContent.trim() === '' && node.nodeName.toLowerCase() !== 'li') || (node.previousElementSibling && node.previousElementSibling.nodeName.toLowerCase() !== 'br' && - node.previousElementSibling.textContent.trim() === '')) { + node.previousElementSibling.textContent.trim() === '')) { event.preventDefault(); } } } - function handleTabKeydown(event) { + function handleTabKeydown (event) { // Override tab only for pre nodes var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tag = node && node.nodeName.toLowerCase(); @@ -6684,27 +6676,27 @@ MediumEditor.extensions = {}; } } - function handleBlockDeleteKeydowns(event) { + function handleBlockDeleteKeydowns (event) { var p, node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tagName = node.nodeName.toLowerCase(), isEmpty = /^(\s+|)?$/i, isHeader = /h\d/i; if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.BACKSPACE, MediumEditor.util.keyCode.ENTER]) && - // has a preceeding sibling - node.previousElementSibling && - // in a header - isHeader.test(tagName) && - // at the very end of the block - MediumEditor.selection.getCaretOffsets(node).left === 0) { + // has a preceding sibling + node.previousElementSibling && + // in a header + isHeader.test(tagName) && + // at the very end of the block + MediumEditor.selection.getCaretOffsets(node).left === 0) { if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && isEmpty.test(node.previousElementSibling.innerHTML)) { - // backspacing the begining of a header into an empty previous element will + // backspacing the beginning of a header into an empty previous element will // change the tagName of the current node to prevent one // instead delete previous node and cancel the event. node.previousElementSibling.parentNode.removeChild(node.previousElementSibling); event.preventDefault(); } else if (!this.options.disableDoubleReturn && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) { - // hitting return in the begining of a header will create empty header elements before the current one + // hitting return in the beginning of a header will create empty header elements before the current one // instead, make "


" element, which are what happens if you hit return in an empty paragraph p = this.options.ownerDocument.createElement('p'); p.innerHTML = '
'; @@ -6712,19 +6704,19 @@ MediumEditor.extensions = {}; event.preventDefault(); } } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.DELETE) && - // between two sibling elements - node.nextElementSibling && - node.previousElementSibling && - // not in a header - !isHeader.test(tagName) && - // in an empty tag - isEmpty.test(node.innerHTML) && - // when the next tag *is* a header - isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) { + // between two sibling elements + node.nextElementSibling && + node.previousElementSibling && + // not in a header + !isHeader.test(tagName) && + // in an empty tag + isEmpty.test(node.innerHTML) && + // when the next tag *is* a header + isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) { // hitting delete in an empty element preceding a header, ex: //

[CURSOR]

Header

// Will cause the h1 to become a paragraph. - // Instead, delete the paragraph node and move the cursor to the begining of the h1 + // Instead, delete the paragraph node and move the cursor to the beginning of the h1 // remove node and move cursor to start of header MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextElementSibling); @@ -6733,16 +6725,16 @@ MediumEditor.extensions = {}; event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - tagName === 'li' && - // hitting backspace inside an empty li - isEmpty.test(node.innerHTML) && - // is first element (no preceeding siblings) - !node.previousElementSibling && - // parent also does not have a sibling - !node.parentElement.previousElementSibling && - // is not the only li in a list - node.nextElementSibling && - node.nextElementSibling.nodeName.toLowerCase() === 'li') { + tagName === 'li' && + // hitting backspace inside an empty li + isEmpty.test(node.innerHTML) && + // is first element (no preceding siblings) + !node.previousElementSibling && + // parent also does not have a sibling + !node.parentElement.previousElementSibling && + // is not the only li in a list + node.nextElementSibling && + node.nextElementSibling.nodeName.toLowerCase() === 'li') { // backspacing in an empty first list element in the first list (with more elements) ex: //
  • [CURSOR]
  • List Item 2
// will remove the first
  • but add some extra element before (varies based on browser) @@ -6764,16 +6756,16 @@ MediumEditor.extensions = {}; event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).left === 0) { + (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && + MediumEditor.selection.getCaretOffsets(node).left === 0) { - // when cursor is at the begining of the element and the element is
    + // when cursor is at the beginning of the element and the element is
    // then pressing backspace key should change the
    to a

    tag event.preventDefault(); MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p'); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).right === 0) { + (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && + MediumEditor.selection.getCaretOffsets(node).right === 0) { // when cursor is at the end of

    , // then pressing enter key should create

    tag, not

    @@ -6786,10 +6778,10 @@ MediumEditor.extensions = {}; event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - MediumEditor.util.isMediumEditorElement(node.parentElement) && - !node.previousElementSibling && - node.nextElementSibling && - isEmpty.test(node.innerHTML)) { + MediumEditor.util.isMediumEditorElement(node.parentElement) && + !node.previousElementSibling && + node.nextElementSibling && + isEmpty.test(node.innerHTML)) { // when cursor is in the first element, it's empty and user presses backspace, // do delete action instead to get rid of the first element and move caret to 2nd @@ -6799,7 +6791,7 @@ MediumEditor.extensions = {}; } } - function handleKeyup(event) { + function handleKeyup (event) { var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tagName; @@ -6844,7 +6836,7 @@ MediumEditor.extensions = {}; } } - function handleEditableInput(event, editable) { + function handleEditableInput (event, editable) { var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]'); if (textarea) { textarea.value = editable.innerHTML.trim(); @@ -6853,7 +6845,7 @@ MediumEditor.extensions = {}; // Internal helper methods which shouldn't be exposed externally - function addToEditors(win) { + function addToEditors (win) { if (!win._mediumEditors) { // To avoid breaking users who are assuming that the unique id on // medium-editor elements will start at 1, inserting a 'null' in the @@ -6869,7 +6861,7 @@ MediumEditor.extensions = {}; win._mediumEditors[this.id] = this; } - function removeFromEditors(win) { + function removeFromEditors (win) { if (!win._mediumEditors || !win._mediumEditors[this.id]) { return; } @@ -6884,7 +6876,7 @@ MediumEditor.extensions = {}; win._mediumEditors[this.id] = null; } - function createElementsArray(selector, doc, filterEditorElements) { + function createElementsArray (selector, doc, filterEditorElements) { var elements = []; if (!selector) { @@ -6918,7 +6910,7 @@ MediumEditor.extensions = {}; return elements; } - function cleanupTextareaElement(element) { + function cleanupTextareaElement (element) { var textarea = element.parentNode.querySelector('textarea[medium-editor-textarea-id="' + element.getAttribute('medium-editor-textarea-id') + '"]'); if (textarea) { // Un-hide the textarea @@ -6930,7 +6922,7 @@ MediumEditor.extensions = {}; } } - function setExtensionDefaults(extension, defaults) { + function setExtensionDefaults (extension, defaults) { Object.keys(defaults).forEach(function (prop) { if (extension[prop] === undefined) { extension[prop] = defaults[prop]; @@ -6939,7 +6931,7 @@ MediumEditor.extensions = {}; return extension; } - function initExtension(extension, name, instance) { + function initExtension (extension, name, instance) { var extensionDefaults = { 'window': instance.options.contentWindow, 'document': instance.options.ownerDocument, @@ -6961,19 +6953,19 @@ MediumEditor.extensions = {}; return extension; } - function isToolbarEnabled() { + function isToolbarEnabled () { // If any of the elements don't have the toolbar disabled // We need a toolbar if (this.elements.every(function (element) { - return !!element.getAttribute('data-disable-toolbar'); - })) { + return !!element.getAttribute('data-disable-toolbar'); + })) { return false; } return this.options.toolbar !== false; } - function isAnchorPreviewEnabled() { + function isAnchorPreviewEnabled () { // If toolbar is disabled, don't add if (!isToolbarEnabled.call(this)) { return false; @@ -6982,34 +6974,34 @@ MediumEditor.extensions = {}; return this.options.anchorPreview !== false; } - function isPlaceholderEnabled() { + function isPlaceholderEnabled () { return this.options.placeholder !== false; } - function isAutoLinkEnabled() { + function isAutoLinkEnabled () { return this.options.autoLink !== false; } - function isImageDraggingEnabled() { + function isImageDraggingEnabled () { return this.options.imageDragging !== false; } - function isKeyboardCommandsEnabled() { + function isKeyboardCommandsEnabled () { return this.options.keyboardCommands !== false; } - function shouldUseFileDraggingExtension() { + function shouldUseFileDraggingExtension () { // Since the file-dragging extension replaces the image-dragging extension, - // we need to check if the user passed an overrided image-dragging extension. + // we need to check if the user passed an overridden image-dragging extension. // If they have, to avoid breaking users, we won't use file-dragging extension. return !this.options.extensions['imageDragging']; } - function createContentEditable(textarea) { + function createContentEditable (textarea) { var div = this.options.ownerDocument.createElement('div'), now = Date.now(), uniqueId = 'medium-editor-' + now, - atts = textarea.attributes; + attributes = textarea.attributes; // Some browsers can move pretty fast, since we're using a timestamp // to make a unique-id, ensure that the id is actually unique on the page @@ -7024,11 +7016,11 @@ MediumEditor.extensions = {}; textarea.setAttribute('medium-editor-textarea-id', uniqueId); - // re-create all attributes from the textearea to the new created div - for (var i = 0, n = atts.length; i < n; i++) { + // re-create all attributes from the textarea to the new created div + for (var i = 0, n = attributes.length; i < n; i++) { // do not re-create existing attributes - if (!div.hasAttribute(atts[i].nodeName)) { - div.setAttribute(atts[i].nodeName, atts[i].value); + if (!div.hasAttribute(attributes[i].nodeName)) { + div.setAttribute(attributes[i].nodeName, attributes[i].value); } } @@ -7053,7 +7045,7 @@ MediumEditor.extensions = {}; var initialContent = {}; - function initElement(element, editorId) { + function initElement (element, editorId) { if (!element.getAttribute('data-medium-editor-element')) { if (element.nodeName.toLowerCase() === 'textarea') { element = createContentEditable.call(this, element); @@ -7103,7 +7095,7 @@ MediumEditor.extensions = {}; return element; } - function attachHandlers() { + function attachHandlers () { // attach to tabs this.subscribe('editableKeydownTab', handleTabKeydown.bind(this)); @@ -7126,7 +7118,7 @@ MediumEditor.extensions = {}; } } - function initExtensions() { + function initExtensions () { this.extensions = []; @@ -7138,7 +7130,7 @@ MediumEditor.extensions = {}; } }, this); - // 4 Cases for imageDragging + fileDragging extensons: + // 4 Cases for imageDragging + fileDragging extensions: // // 1. ImageDragging ON + No Custom Image Dragging Extension: // * Use fileDragging extension (default options) @@ -7181,7 +7173,7 @@ MediumEditor.extensions = {}; // just create the default toolbar var toolbarExtension = this.options.extensions['toolbar']; if (!toolbarExtension && isToolbarEnabled.call(this)) { - // Backwards compatability + // Backwards compatibility var toolbarOptions = MediumEditor.util.extend({}, this.options.toolbar, { allowMultiParagraphSelection: this.options.allowMultiParagraphSelection // deprecated }); @@ -7195,7 +7187,7 @@ MediumEditor.extensions = {}; } } - function mergeOptions(defaults, options) { + function mergeOptions (defaults, options) { var deprecatedProperties = [ ['allowMultiParagraphSelection', 'toolbar.allowMultiParagraphSelection'] ]; @@ -7211,7 +7203,7 @@ MediumEditor.extensions = {}; return MediumEditor.util.defaults({}, options, defaults); } - function execActionInternal(action, opts) { + function execActionInternal (action, opts) { /*jslint regexp: true*/ var appendAction = /^append-(.+)$/gi, justifyAction = /justify([A-Za-z]*)$/g, /* Detecting if is justifyCenter|Right|Left */ @@ -7278,7 +7270,7 @@ MediumEditor.extensions = {}; * Chrome may have removed
    elements and instead wrapped lines in
    elements * with a text-align property. If so, we want to fix this */ - function cleanupJustifyDivFragments(blockContainer) { + function cleanupJustifyDivFragments (blockContainer) { if (!blockContainer) { return; } @@ -7320,7 +7312,7 @@ MediumEditor.extensions = {}; } MediumEditor.prototype = { - // NOT DOCUMENTED - exposed for backwards compatability + // NOT DOCUMENTED - exposed for backwards compatibility init: function (elements, options) { this.options = mergeOptions.call(this, this.defaults, options); this.origElements = elements; @@ -7733,7 +7725,7 @@ MediumEditor.extensions = {}; // since we are going to create a link from an extracted text, // be sure that if we are updating a link, we won't let an empty link behind (see #754) - // (Workaroung for Chrome) + // (Workaround for Chrome) this.execAction('unlink'); exportedSelection = this.exportSelection(); @@ -7744,7 +7736,7 @@ MediumEditor.extensions = {}; // as our reference inside this.elements gets detached from the page when insertHTML runs. // If we just use [parentElement, 0] and [parentElement, parentElement.childNodes.length] // as the range boundaries, this happens whenever parentElement === currentEditor. - // The tradeoff to this workaround is that a orphaned tag can sometimes be left behind at + // The trade-off to this workaround is that a orphaned tag can sometimes be left behind at // the end of the editor's content. // In Gecko: // as an empty if parentElement.lastChild is a tag. @@ -7756,7 +7748,7 @@ MediumEditor.extensions = {}; 0, parentElement.lastChild, parentElement.lastChild.nodeType === 3 ? - parentElement.lastChild.nodeValue.length : parentElement.lastChild.childNodes.length + parentElement.lastChild.nodeValue.length : parentElement.lastChild.childNodes.length ); } else { MediumEditor.selection.select( diff --git a/dist/js/medium-editor.min.js b/dist/js/medium-editor.min.js index 3f9cc1851..d0fb0f8d2 100644 --- a/dist/js/medium-editor.min.js +++ b/dist/js/medium-editor.min.js @@ -1 +1 @@ -"classList"in document.createElement("_")||function(e){"use strict";if("Element"in e){var t="classList",n="prototype",i=e.Element[n],o=Object,s=String[n].trim||function(){return this.replace(/^\s+|\s+$/g,"")},r=Array[n].indexOf||function(e){for(var t=0,n=this.length;t)?$/i,s=/h\d/i;b.util.isKey(e,[b.util.keyCode.BACKSPACE,b.util.keyCode.ENTER])&&n.previousElementSibling&&s.test(i)&&0===b.selection.getCaretOffsets(n).left?b.util.isKey(e,b.util.keyCode.BACKSPACE)&&o.test(n.previousElementSibling.innerHTML)?(n.previousElementSibling.parentNode.removeChild(n.previousElementSibling),e.preventDefault()):!this.options.disableDoubleReturn&&b.util.isKey(e,b.util.keyCode.ENTER)&&((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.previousElementSibling.parentNode.insertBefore(t,n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.DELETE)&&n.nextElementSibling&&n.previousElementSibling&&!s.test(i)&&o.test(n.innerHTML)&&s.test(n.nextElementSibling.nodeName.toLowerCase())?(b.selection.moveCursor(this.options.ownerDocument,n.nextElementSibling),n.previousElementSibling.parentNode.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&"li"===i&&o.test(n.innerHTML)&&!n.previousElementSibling&&!n.parentElement.previousElementSibling&&n.nextElementSibling&&"li"===n.nextElementSibling.nodeName.toLowerCase()?((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.parentElement.parentElement.insertBefore(t,n.parentElement),b.selection.moveCursor(this.options.ownerDocument,t),n.parentElement.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).left?(e.preventDefault(),b.util.execFormatBlock(this.options.ownerDocument,"p")):b.util.isKey(e,b.util.keyCode.ENTER)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).right?((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.parentElement.insertBefore(t,n.nextSibling),b.selection.moveCursor(this.options.ownerDocument,t),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&b.util.isMediumEditorElement(n.parentElement)&&!n.previousElementSibling&&n.nextElementSibling&&o.test(n.innerHTML)&&(e.preventDefault(),b.selection.moveCursor(this.options.ownerDocument,n.nextSibling),n.parentElement.removeChild(n))}function N(e,t,n){var i=[];if("string"==typeof(e=e||[])&&(e=t.querySelectorAll(e)),b.util.isElement(e)&&(e=[e]),n)for(var o=0;on.end+1)throw new Error("PerformLinking overshot the target!");r&&o.push(l||a),s+=a.nodeValue.length,null!==l&&(s+=l.nodeValue.length,i.nextNode()),l=null}else"img"===a.tagName.toLowerCase()&&(!r&&n.start<=s&&(r=!0),r&&o.push(a));return o},splitStartNodeIfNeeded:function(e,t,n){return t!==n?e.splitText(t-n):null},splitEndNodeIfNeeded:function(e,t,n,i){var o,s;o=i+e.nodeValue.length+(t?t.nodeValue.length:0)-1,s=n-i-(t?e.nodeValue.length:0),n<=o&&i!==o&&0!=s&&(t||e).splitText(s)},splitByBlockElements:function(e){if(3!==e.nodeType&&1!==e.nodeType)return[];var t=[],n=b.util.blockContainerElementNames.join(",");if(3===e.nodeType||0===e.querySelectorAll(n).length)return[e];for(var i=0;i/g,">").replace(/"/g,""")},insertHTMLCommand:function(e,t){var n,i,o,s,r,a,l,c=!1,d=["insertHTML",!1,t];if(!b.util.isEdge&&e.queryCommandSupported("insertHTML"))try{return e.execCommand.apply(e,d)}catch(e){}if((n=e.getSelection()).rangeCount){if(l=(i=n.getRangeAt(0)).commonAncestorContainer,f.isMediumEditorElement(l)&&!l.firstChild)i.selectNode(l.appendChild(e.createTextNode("")));else if(3===l.nodeType&&0===i.startOffset&&i.endOffset===l.nodeValue.length||3!==l.nodeType&&l.innerHTML===i.toString()){for(;!f.isMediumEditorElement(l)&&l.parentNode&&1===l.parentNode.childNodes.length&&!f.isMediumEditorElement(l.parentNode);)l=l.parentNode;i.selectNode(l)}for(i.deleteContents(),(o=e.createElement("div")).innerHTML=t,s=e.createDocumentFragment();o.firstChild;)r=o.firstChild,a=s.appendChild(r);i.insertNode(s),a&&((i=i.cloneRange()).setStartAfter(a),i.collapse(!0),b.selection.selectRange(e,i)),c=!0}return e.execCommand.callListeners&&e.execCommand.callListeners(d,c),c},execFormatBlock:function(e,t){var n=f.getTopBlockContainer(b.selection.getSelectionStart(e));if("blockquote"===t){if(n&&Array.prototype.slice.call(n.childNodes).some(function(e){return f.isBlockContainer(e)}))return e.execCommand("outdent",!1,null);if(f.isIE)return e.execCommand("indent",!1,t)}if(n&&t===n.nodeName.toLowerCase()&&(t="p"),f.isIE&&(t="<"+t+">"),n&&"blockquote"===n.nodeName.toLowerCase()){if(f.isIE&&"
    ','','","
    "),this.customClassOption&&e.push('
    ','','","
    "),e.join("")},isDisplayed:function(){return b.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){b.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(e){var t=this.getInput(),n=this.getAnchorTargetCheckbox(),i=this.getAnchorButtonCheckbox();if("string"==typeof(e=e||{value:""})&&(e={value:e}),this.base.saveSelection(),this.hideToolbarDefaultActions(),b.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),t.value=e.value,t.focus(),n&&(n.checked="_blank"===e.target),i){var o=e.buttonClass?e.buttonClass.split(" "):[];i.checked=-1!==o.indexOf(this.customClassOption)}},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},getFormOpts:function(){var e=this.getAnchorTargetCheckbox(),t=this.getAnchorButtonCheckbox(),n={value:this.getInput().value.trim()};return this.linkValidation&&(n.value=this.checkLinkFormat(n.value)),n.target="_self",e&&e.checked&&(n.target="_blank"),t&&t.checked&&(n.buttonClass=this.customClassOption),n},doFormSave:function(){var e=this.getFormOpts();this.completeFormSave(e)},completeFormSave:function(e){this.base.restoreSelection(),this.execAction(this.action,e),this.base.checkSelection()},ensureEncodedUri:function(e){return e===decodeURI(e)?encodeURI(e):e},ensureEncodedUriComponent:function(e){return e===decodeURIComponent(e)?encodeURIComponent(e):e},ensureEncodedParam:function(e){var t=e.split("="),n=t[0],i=t[1];return n+(void 0===i?"":"="+this.ensureEncodedUriComponent(i))},ensureEncodedQuery:function(e){return e.split("&").map(this.ensureEncodedParam.bind(this)).join("&")},checkLinkFormat:function(e){var t=/^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i.test(e),n="",i=e.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),o=i[1],s=i[2],r=i[3];if(/^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/.test(e))return"tel:"+e;if(!t){var a=o.split("/")[0];!a.match(/.+(\.|:).+/)&&"localhost"!==a||(n="http://")}return n+this.ensureEncodedUri(o)+(void 0===s?"":"?"+this.ensureEncodedQuery(s))+(void 0===r?"":"#"+r)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(e){var t=e.querySelector(".medium-editor-toolbar-close"),n=e.querySelector(".medium-editor-toolbar-save"),i=e.querySelector(".medium-editor-toolbar-input");this.on(e,"click",this.handleFormClick.bind(this)),this.on(i,"keyup",this.handleTextboxKeyup.bind(this)),this.on(t,"click",this.handleCloseClick.bind(this)),this.on(n,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var e=this.document.createElement("div");return e.className="medium-editor-toolbar-form",e.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),e.innerHTML=this.getTemplate(),this.attachFormEvents(e),e},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(e){if(e.keyCode===b.util.keyCode.ENTER)return e.preventDefault(),void this.doFormSave();e.keyCode===b.util.keyCode.ESCAPE&&(e.preventDefault(),this.doFormCancel())},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.anchor=o,s=b.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var e=this.document.createElement("div");return e.id="medium-editor-anchor-preview-"+this.getEditorId(),e.className="medium-editor-anchor-preview",e.innerHTML=this.getTemplate(),this.on(e,"click",this.handleClick.bind(this)),e},getTemplate:function(){return'
    '},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview&&this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(e){return!(!this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")&&!e.getAttribute("data-disable-preview"))||(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=e.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=e.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=e,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(e){e=e||this.activeAnchor;var t,n,i,o,s,r=this.window.innerWidth,a=this.anchorPreview.offsetHeight,l=e.getBoundingClientRect(),c=this.diffLeft,d=this.diffTop,u=this.getEditorOption("elementsContainer"),h=-1<["absolute","fixed"].indexOf(window.getComputedStyle(u).getPropertyValue("position")),m={};t=this.anchorPreview.offsetWidth/2;var f=this.base.getExtensionByName("toolbar");f&&(c=f.diffLeft,d=f.diffTop),n=c-t,s=h?(o=u.getBoundingClientRect(),["top","left"].forEach(function(e){m[e]=l[e]-o[e]}),m.width=l.width,m.height=l.height,l=m,r=o.width,u.scrollTop):this.window.pageYOffset,i=l.left+l.width/2,s+=a+l.top+l.height-d-this.anchorPreview.offsetHeight,this.anchorPreview.style.top=Math.round(s)+"px",this.anchorPreview.style.right="initial",ithis.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}}),b.extensions.anchorPreview=s,function(){var s,e,r,a;s=[" ","\t","\n","\r"," "," "," "," "," ","\u2028","\u2029"],e="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw",r=new RegExp("^("+e+")$","i"),a=new RegExp("(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))","gi");var t=b.Extension.extend({init:function(){b.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var e=0,t=0;tt?(o=n.splitText(i.length-t),t=0):(o=s.previousNode(),t-=i.length);return o},performLinkingWithinElement:function(e){for(var t=this.findLinkableText(e),n=0;n
    ',fonts:["","Arial","Verdana","Times New Roman"],init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontName")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(e){var t=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var e,t=this.document,n=t.createElement("div"),i=t.createElement("select"),o=t.createElement("a"),s=t.createElement("a");n.className="medium-editor-toolbar-form",n.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(n,"click",this.handleFormClick.bind(this));for(var r=0;r
    ':"✓",n.appendChild(s),this.on(s,"click",this.handleSaveClick.bind(this),!0),o.setAttribute("href","#"),o.className="medium-editor-toobar-close",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",n.appendChild(o),this.on(o,"click",this.handleCloseClick.bind(this)),n},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("face")&&e.removeAttribute("face")})},handleFontChange:function(){var e=this.getSelect().value;""===e?this.clearFontName():this.execAction("fontName",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontName=c,u=b.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"±",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontSize")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(e){var t=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var e=this.document,t=e.createElement("div"),n=e.createElement("input"),i=e.createElement("a"),o=e.createElement("a");return t.className="medium-editor-toolbar-form",t.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(t,"click",this.handleFormClick.bind(this)),n.setAttribute("type","range"),n.setAttribute("min","1"),n.setAttribute("max","7"),n.className="medium-editor-toolbar-input",t.appendChild(n),this.on(n,"change",this.handleSliderChange.bind(this)),o.setAttribute("href","#"),o.className="medium-editor-toobar-save",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"✓",t.appendChild(o),this.on(o,"click",this.handleSaveClick.bind(this),!0),i.setAttribute("href","#"),i.className="medium-editor-toobar-close",i.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",t.appendChild(i),this.on(i,"click",this.handleCloseClick.bind(this)),t},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("size")&&e.removeAttribute("size")})},handleSliderChange:function(){var e=this.getInput().value;"4"===e?this.clearFontSize():this.execAction("fontSize",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontSize=u,h="%ME_PASTEBIN%",f=m=null,p=b.Extension.extend({forcePlainText:!0,cleanPastedHTML:!1,preCleanReplacements:[],cleanReplacements:[],cleanAttrs:["class","style","dir"],cleanTags:["meta"],unwrapTags:[],init:function(){b.Extension.prototype.init.apply(this,arguments),(this.forcePlainText||this.cleanPastedHTML)&&(this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.getEditorElements().forEach(function(e){this.on(e,"paste",this.handlePaste.bind(this))},this),this.subscribe("addElement",this.handleAddElement.bind(this)))},handleAddElement:function(e,t){this.on(t,"paste",this.handlePaste.bind(this))},destroy:function(){(this.forcePlainText||this.cleanPastedHTML)&&this.removePasteBin()},handlePaste:function(e,t){if(!e.defaultPrevented){var n=k(e,this.window,this.document),i=n["text/html"],o=n["text/plain"];this.window.clipboardData&&void 0===e.clipboardData&&!i&&(i=o),(i||o)&&(e.preventDefault(),this.doPaste(i,o,t))}},doPaste:function(e,t,n){var i,o,s="";if(this.cleanPastedHTML&&e)return this.cleanPaste(e);if(t){if(this.getEditorOption("disableReturn")||n&&n.getAttribute("data-disable-return"))s=b.util.htmlEntities(t);else if(1<(i=t.split(/[\r\n]+/g)).length)for(o=0;o"+b.util.htmlEntities(i[o])+"

    ");else s=b.util.htmlEntities(i[0]);b.util.insertHTMLCommand(this.document,s)}},handlePasteBinPaste:function(e){if(e.defaultPrevented)this.removePasteBin();else{var t=k(e,this.window,this.document),n=t["text/html"],i=t["text/plain"],o=f;if(!this.cleanPastedHTML||n)return e.preventDefault(),this.removePasteBin(),this.doPaste(n,i,o),void this.trigger("editablePaste",{currentTarget:o,target:o},o);setTimeout(function(){this.cleanPastedHTML&&(n=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(n,i,o),this.trigger("editablePaste",{currentTarget:o,target:o},o)}.bind(this),0)}},handleKeydown:function(e,t){b.util.isKey(e,b.util.keyCode.V)&&b.util.isMetaCtrlKey(e)&&(e.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(t))},createPasteBin:function(e){var t,n=b.selection.getSelectionRange(this.document),i=this.window.pageYOffset;f=e,n&&((t=n.getClientRects()).length?i+=t[0].top:void 0!==n.startContainer.getBoundingClientRect?i+=n.startContainer.getBoundingClientRect().top:i+=n.getBoundingClientRect().top),m=n;var o=this.document.createElement("div");o.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),o.setAttribute("style","border: 1px red solid; position: absolute; top: "+i+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),o.setAttribute("contentEditable",!0),o.innerHTML=h,this.document.body.appendChild(o),this.on(o,"focus",T),this.on(o,"focusin",T),this.on(o,"focusout",T),o.focus(),b.selection.selectNode(o,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(o,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==m&&(b.selection.selectRange(this.document,m),m=null),null!==f&&(f=null);var e=this.getPasteBin();e&&e&&(this.off(e,"focus",T),this.off(e,"focusin",T),this.off(e,"focusout",T),this.off(e,"paste",this.boundHandlePaste),e.parentElement.removeChild(e))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var e=this.getPasteBin();if(!e)return!1;if(e.firstChild&&"mcepastebin"===e.firstChild.id)return!1;var t=e.innerHTML;return!(!t||t===h)&&t},cleanPaste:function(e){var t,n,i,o,s=/]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/|/g),""],[new RegExp(/
    $/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(]*>)?$/gi),""],[new RegExp(/\s+<\/span>/g)," "],[new RegExp(/
    /g),"
    "],[new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi),''],[new RegExp(/]*font-style:italic[^>]*>/gi),''],[new RegExp(/]*font-weight:(bold|700)[^>]*>/gi),''],[new RegExp(/<(\/?)(i|b|a)>/gi),"<$1$2>"],[new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi),''],[new RegExp(/<\/p>\n+/gi),"

    "],[new RegExp(/\n+

    /gi),""],[new RegExp(/(((?!/gi),"$1"]],this.cleanReplacements||[]);for(t=0;t"+e.split("

    ").join("

    ")+"

    ",n=i.querySelectorAll("a,p,div,br"),t=0;t"+i.innerHTML+"
    ":o.innerHTML=i.innerHTML,i.parentNode.replaceChild(o,i);for(s=e.querySelectorAll("span"),n=0;n]*>","g"),t=b.selection.getSelectionHtml(this.document).replace(/<[^\/>][^>]*><\/[^>]+>/gim,"").match(e);return!!t&&1r+e.offsetHeight-l-this.stickyTopOffset?(o.style.top=r+e.offsetHeight-l+"px",o.classList.remove("medium-editor-sticky-toolbar")):n>r-l-this.stickyTopOffset?(o.classList.add("medium-editor-sticky-toolbar"),o.style.top=this.stickyTopOffset+"px"):(o.classList.remove("medium-editor-sticky-toolbar"),o.style.top=r-l+"px"):o.style.top=r-l+"px",this.align){case"left":t=s.left;break;case"right":t=s.right-c;break;case"center":t=a-d}t<0?t=0:i'),t.onload=function(){var e=this.document.getElementById(n);e&&(e.removeAttribute("id"),e.removeAttribute("class"),e.src=t.result)}.bind(this))}.bind(this)),e.target.classList.remove("medium-editor-dragover")}}),b.extensions.imageDragging=E,C={},b.prototype={init:function(e,t){return this.options=function(e,t){return t&&[["allowMultiParagraphSelection","toolbar.allowMultiParagraphSelection"]].forEach(function(e){t.hasOwnProperty(e[0])&&void 0!==t[e[0]]&&b.util.deprecated(e[0],e[1],"v6.0.0")}),b.util.defaults({},t,e)}.call(this,this.defaults,t),this.origElements=e,this.options.elementsContainer||(this.options.elementsContainer=this.options.ownerDocument.body),this.setup()},setup:function(){this.isActive||(function(e){e._mediumEditors||(e._mediumEditors=[null]),this.id||(this.id=e._mediumEditors.length),e._mediumEditors[this.id]=this}.call(this,this.options.contentWindow),this.events=new b.Events(this),this.elements=[],this.addElements(this.origElements),0!==this.elements.length&&(this.isActive=!0,M.call(this),F.call(this)))},destroy:function(){this.isActive&&(this.isActive=!1,this.extensions.forEach(function(e){"function"==typeof e.destroy&&e.destroy()},this),this.events.destroy(),this.elements.forEach(function(e){this.options.spellcheck&&(e.innerHTML=e.innerHTML),e.removeAttribute("contentEditable"),e.removeAttribute("spellcheck"),e.removeAttribute("data-medium-editor-element"),e.classList.remove("medium-editor-element"),e.removeAttribute("role"),e.removeAttribute("aria-multiline"),e.removeAttribute("medium-editor-index"),e.removeAttribute("data-medium-editor-editor-index"),e.getAttribute("medium-editor-textarea-id")&&L(e)},this),this.elements=[],this.instanceHandleEditableKeydownEnter=null,this.instanceHandleEditableInput=null,function(e){e._mediumEditors&&e._mediumEditors[this.id]&&(e._mediumEditors[this.id]=null)}.call(this,this.options.contentWindow))},on:function(e,t,n,i){return this.events.attachDOMEvent(e,t,n,i),this},off:function(e,t,n,i){return this.events.detachDOMEvent(e,t,n,i),this},subscribe:function(e,t){return this.events.attachCustomEvent(e,t),this},unsubscribe:function(e,t){return this.events.detachCustomEvent(e,t),this},trigger:function(e,t,n){return this.events.triggerCustomEvent(e,t,n),this},delay:function(e){var t=this;return setTimeout(function(){t.isActive&&e()},this.options.delay)},serialize:function(){var e,t={},n=this.elements.length;for(e=0;e)?$/i,s=/h\d/i;b.util.isKey(e,[b.util.keyCode.BACKSPACE,b.util.keyCode.ENTER])&&n.previousElementSibling&&s.test(i)&&0===b.selection.getCaretOffsets(n).left?b.util.isKey(e,b.util.keyCode.BACKSPACE)&&o.test(n.previousElementSibling.innerHTML)?(n.previousElementSibling.parentNode.removeChild(n.previousElementSibling),e.preventDefault()):!this.options.disableDoubleReturn&&b.util.isKey(e,b.util.keyCode.ENTER)&&((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.previousElementSibling.parentNode.insertBefore(t,n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.DELETE)&&n.nextElementSibling&&n.previousElementSibling&&!s.test(i)&&o.test(n.innerHTML)&&s.test(n.nextElementSibling.nodeName.toLowerCase())?(b.selection.moveCursor(this.options.ownerDocument,n.nextElementSibling),n.previousElementSibling.parentNode.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&"li"===i&&o.test(n.innerHTML)&&!n.previousElementSibling&&!n.parentElement.previousElementSibling&&n.nextElementSibling&&"li"===n.nextElementSibling.nodeName.toLowerCase()?((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.parentElement.parentElement.insertBefore(t,n.parentElement),b.selection.moveCursor(this.options.ownerDocument,t),n.parentElement.removeChild(n),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).left?(e.preventDefault(),b.util.execFormatBlock(this.options.ownerDocument,"p")):b.util.isKey(e,b.util.keyCode.ENTER)&&!1!==b.util.getClosestTag(n,"blockquote")&&0===b.selection.getCaretOffsets(n).right?((t=this.options.ownerDocument.createElement("p")).innerHTML="
    ",n.parentElement.insertBefore(t,n.nextSibling),b.selection.moveCursor(this.options.ownerDocument,t),e.preventDefault()):b.util.isKey(e,b.util.keyCode.BACKSPACE)&&b.util.isMediumEditorElement(n.parentElement)&&!n.previousElementSibling&&n.nextElementSibling&&o.test(n.innerHTML)&&(e.preventDefault(),b.selection.moveCursor(this.options.ownerDocument,n.nextSibling),n.parentElement.removeChild(n))}function N(e,t,n){var i=[];if("string"==typeof(e=e||[])&&(e=t.querySelectorAll(e)),b.util.isElement(e)&&(e=[e]),n)for(var o=0;on.end+1)throw new Error("PerformLinking overshot the target!");r&&o.push(l||a),s+=a.nodeValue.length,null!==l&&(s+=l.nodeValue.length,i.nextNode()),l=null}else"img"===a.tagName.toLowerCase()&&(!r&&n.start<=s&&(r=!0),r&&o.push(a));return o},splitStartNodeIfNeeded:function(e,t,n){return t!==n?e.splitText(t-n):null},splitEndNodeIfNeeded:function(e,t,n,i){var o,s;o=i+e.nodeValue.length+(t?t.nodeValue.length:0)-1,s=n-i-(t?e.nodeValue.length:0),n<=o&&i!==o&&0!=s&&(t||e).splitText(s)},splitByBlockElements:function(e){if(3!==e.nodeType&&1!==e.nodeType)return[];var t=[],n=b.util.blockContainerElementNames.join(",");if(3===e.nodeType||0===e.querySelectorAll(n).length)return[e];for(var i=0;i/g,">").replace(/"/g,""")},insertHTMLCommand:function(e,t){var n,i,o,s,r,a,l,c=!1,d=["insertHTML",!1,t];if(!b.util.isEdge&&e.queryCommandSupported("insertHTML"))try{return e.execCommand.apply(e,d)}catch(e){}if((n=e.getSelection()).rangeCount){if(l=(i=n.getRangeAt(0)).commonAncestorContainer,f.isMediumEditorElement(l)&&!l.firstChild)i.selectNode(l.appendChild(e.createTextNode("")));else if(3===l.nodeType&&0===i.startOffset&&i.endOffset===l.nodeValue.length||3!==l.nodeType&&l.innerHTML===i.toString()){for(;!f.isMediumEditorElement(l)&&l.parentNode&&1===l.parentNode.childNodes.length&&!f.isMediumEditorElement(l.parentNode);)l=l.parentNode;i.selectNode(l)}for(i.deleteContents(),(o=e.createElement("div")).innerHTML=t,s=e.createDocumentFragment();o.firstChild;)r=o.firstChild,a=s.appendChild(r);i.insertNode(s),a&&((i=i.cloneRange()).setStartAfter(a),i.collapse(!0),b.selection.selectRange(e,i)),c=!0}return e.execCommand.callListeners&&e.execCommand.callListeners(d,c),c},execFormatBlock:function(e,t){var n=f.getTopBlockContainer(b.selection.getSelectionStart(e));if("blockquote"===t){if(n&&Array.prototype.slice.call(n.childNodes).some(function(e){return f.isBlockContainer(e)}))return e.execCommand("outdent",!1,null);if(f.isIE)return e.execCommand("indent",!1,t)}if(n&&t===n.nodeName.toLowerCase()&&(t="p"),f.isIE&&(t="<"+t+">"),n&&"blockquote"===n.nodeName.toLowerCase()){if(f.isIE&&"

    "===t)return e.execCommand("outdent",!1,t);if((f.isFF||f.isEdge)&&"p"===t)return Array.prototype.slice.call(n.childNodes).some(function(e){return!f.isBlockContainer(e)})&&e.execCommand("formatBlock",!1,t),e.execCommand("outdent",!1,t)}return e.execCommand("formatBlock",!1,t)},setTargetBlank:function(e,t){var n,i=t||!1;if("a"===e.nodeName.toLowerCase())e.target="_blank",e.rel="noopener noreferrer";else for(e=e.getElementsByTagName("a"),n=0;n=l&&e.start<=s&&(m||e.start=l&&e.end<=s&&(e.trailingImageCount?d=!0:(o.setEnd(r,e.end-l),h=!0)),l=s;h||(r=a.pop())}!c&&f&&(o.setStart(f,f.length),o.setEnd(f,f.length)),void 0!==e.emptyBlocksIndex&&(o=this.importSelectionMoveCursorPastBlocks(n,t,e.emptyBlocksIndex,o)),i&&(o=this.importSelectionMoveCursorPastAnchor(e,o)),this.selectRange(n,o)}},importSelectionMoveCursorPastAnchor:function(e,t){if(e.start===e.end&&3===t.startContainer.nodeType&&t.startOffset===t.startContainer.nodeValue.length&&b.util.traverseUp(t.startContainer,function(e){return"a"===e.nodeName.toLowerCase()})){for(var n=t.startContainer,i=t.startContainer.parentNode;null!==i&&"a"!==i.nodeName.toLowerCase();)i=i.childNodes[i.childNodes.length-1]!==n?null:(n=i).parentNode;if(null!==i&&"a"===i.nodeName.toLowerCase()){for(var o=null,s=0;null===o&&s=l&&t.start<=s&&(c=!0),c&&t.end>=l&&t.end<=s&&(d=!0),l=s;u||(r=a.pop())}return h},selectionContainsContent:function(e){var t=e.getSelection();if(!t||t.isCollapsed||!t.rangeCount)return!1;if(""!==t.toString().trim())return!0;var n=this.getSelectedParentElement(t.getRangeAt(0));return!(!n||!("img"===n.nodeName.toLowerCase()||1===n.nodeType&&n.querySelector("img")))},selectionInContentEditableFalse:function(e){var n,t=this.findMatchingSelectionParent(function(e){var t=e&&e.getAttribute("contenteditable");return"true"===t&&(n=!0),"#text"!==e.nodeName&&"false"===t},e);return!n&&t},getSelectionHtml:function(e){var t,n,i,o="",s=e.getSelection();if(s.rangeCount){for(i=e.createElement("div"),t=0,n=s.rangeCount;tB",contentFA:''},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"I",contentFA:''},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"U",contentFA:''},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"A",contentFA:''},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"x1",contentFA:''},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"x1",contentFA:''},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"image",contentFA:''},html:{name:"html",action:"html",aria:"evaluate html",tagNames:["iframe","object"],contentDefault:"html",contentFA:''},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"1.",contentFA:''},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"",contentFA:''},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"",contentFA:''},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"",contentFA:''},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"C",contentFA:''},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"J",contentFA:''},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"L",contentFA:''},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"R",contentFA:''},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"X",contentFA:''},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"",contentFA:''},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"0101",contentFA:''},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"H1",contentFA:'1'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"H2",contentFA:'2'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"H3",contentFA:'3'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"H4",contentFA:'4'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"H5",contentFA:'5'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"H6",contentFA:'6'}},i=b.extensions.button.extend({init:function(){b.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"✓",formCloseLabel:"×",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return!!this.hasForm&&this.getForm().classList.contains(this.activeClass)},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.hideToolbarDefaultActions()},setToolbarPosition:function(){var e=this.base.getExtensionByName("toolbar");e&&e.setToolbarPosition()}}),b.extensions.form=i,o=b.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"#",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(e){e.preventDefault(),e.stopPropagation();var t=b.selection.getSelectionRange(this.document);return"a"===t.startContainer.nodeName.toLowerCase()||"a"===t.endContainer.nodeName.toLowerCase()||b.util.getClosestTag(b.selection.getSelectedParentElement(t),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(e){b.util.isKey(e,b.util.keyCode.K)&&b.util.isMetaCtrlKey(e)&&!e.shiftKey&&this.handleClick(e)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var e=[''];return e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formSaveLabel,""),e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formCloseLabel,""),this.targetCheckbox&&e.push('

    ','','","
    "),this.customClassOption&&e.push('
    ','','","
    "),e.join("")},isDisplayed:function(){return b.extensions.form.prototype.isDisplayed.apply(this)},hideForm:function(){b.extensions.form.prototype.hideForm.apply(this),this.getInput().value=""},showForm:function(e){var t=this.getInput(),n=this.getAnchorTargetCheckbox(),i=this.getAnchorButtonCheckbox();if("string"==typeof(e=e||{value:""})&&(e={value:e}),this.base.saveSelection(),this.hideToolbarDefaultActions(),b.extensions.form.prototype.showForm.apply(this),this.setToolbarPosition(),t.value=e.value,t.focus(),n&&(n.checked="_blank"===e.target),i){var o=e.buttonClass?e.buttonClass.split(" "):[];i.checked=-1!==o.indexOf(this.customClassOption)}},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},getFormOpts:function(){var e=this.getAnchorTargetCheckbox(),t=this.getAnchorButtonCheckbox(),n={value:this.getInput().value.trim()};return this.linkValidation&&(n.value=this.checkLinkFormat(n.value)),n.target="_self",e&&e.checked&&(n.target="_blank"),t&&t.checked&&(n.buttonClass=this.customClassOption),n},doFormSave:function(){var e=this.getFormOpts();this.completeFormSave(e)},completeFormSave:function(e){this.base.restoreSelection(),this.execAction(this.action,e),this.base.checkSelection()},ensureEncodedUri:function(e){return e===decodeURI(e)?encodeURI(e):e},ensureEncodedUriComponent:function(e){return e===decodeURIComponent(e)?encodeURIComponent(e):e},ensureEncodedParam:function(e){var t=e.split("="),n=t[0],i=t[1];return n+(void 0===i?"":"="+this.ensureEncodedUriComponent(i))},ensureEncodedQuery:function(e){return e.split("&").map(this.ensureEncodedParam.bind(this)).join("&")},checkLinkFormat:function(e){var t=/^([a-z]+:)?\/\/|^(mailto|tel|maps):|^#/i.test(e),n="",i=e.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/),o=i[1],s=i[2],r=i[3];if(/^\+?\s?\(?(?:\d\s?-?\)?){3,20}$/.test(e))return"tel:"+e;if(!t){var a=o.split("/")[0];!a.match(/.+([.:]).+/)&&"localhost"!==a||(n="http://")}return n+this.ensureEncodedUri(o)+(void 0===s?"":"?"+this.ensureEncodedQuery(s))+(void 0===r?"":"#"+r)},doFormCancel:function(){this.base.restoreSelection(),this.base.checkSelection()},attachFormEvents:function(e){var t=e.querySelector(".medium-editor-toolbar-close"),n=e.querySelector(".medium-editor-toolbar-save"),i=e.querySelector(".medium-editor-toolbar-input");this.on(e,"click",this.handleFormClick.bind(this)),this.on(i,"keyup",this.handleTextboxKeyup.bind(this)),this.on(t,"click",this.handleCloseClick.bind(this)),this.on(n,"click",this.handleSaveClick.bind(this),!0)},createForm:function(){var e=this.document.createElement("div");return e.className="medium-editor-toolbar-form",e.id="medium-editor-toolbar-form-anchor-"+this.getEditorId(),e.innerHTML=this.getTemplate(),this.attachFormEvents(e),e},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},getAnchorTargetCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-target")},getAnchorButtonCheckbox:function(){return this.getForm().querySelector(".medium-editor-toolbar-anchor-button")},handleTextboxKeyup:function(e){if(e.keyCode===b.util.keyCode.ENTER)return e.preventDefault(),void this.doFormSave();e.keyCode===b.util.keyCode.ESCAPE&&(e.preventDefault(),this.doFormCancel())},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.anchor=o,s=b.Extension.extend({name:"anchor-preview",hideDelay:500,previewValueSelector:"a",showWhenToolbarIsVisible:!1,showOnEmptyLinks:!0,init:function(){this.anchorPreview=this.createPreview(),this.getEditorOption("elementsContainer").appendChild(this.anchorPreview),this.attachToEditables()},getInteractionElements:function(){return this.getPreviewElement()},getPreviewElement:function(){return this.anchorPreview},createPreview:function(){var e=this.document.createElement("div");return e.id="medium-editor-anchor-preview-"+this.getEditorId(),e.className="medium-editor-anchor-preview",e.innerHTML=this.getTemplate(),this.on(e,"click",this.handleClick.bind(this)),e},getTemplate:function(){return'
    '},destroy:function(){this.anchorPreview&&(this.anchorPreview.parentNode&&this.anchorPreview.parentNode.removeChild(this.anchorPreview),delete this.anchorPreview)},hidePreview:function(){this.anchorPreview&&this.anchorPreview.classList.remove("medium-editor-anchor-preview-active"),this.activeAnchor=null},showPreview:function(e){return!(!this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")&&!e.getAttribute("data-disable-preview"))||(this.previewValueSelector&&(this.anchorPreview.querySelector(this.previewValueSelector).textContent=e.attributes.href.value,this.anchorPreview.querySelector(this.previewValueSelector).href=e.attributes.href.value),this.anchorPreview.classList.add("medium-toolbar-arrow-over"),this.anchorPreview.classList.remove("medium-toolbar-arrow-under"),this.anchorPreview.classList.contains("medium-editor-anchor-preview-active")||this.anchorPreview.classList.add("medium-editor-anchor-preview-active"),this.activeAnchor=e,this.positionPreview(),this.attachPreviewHandlers(),this)},positionPreview:function(e){e=e||this.activeAnchor;var t,n,i,o,s,r=this.window.innerWidth,a=this.anchorPreview.offsetHeight,l=e.getBoundingClientRect(),c=this.diffLeft,d=this.diffTop,u=this.getEditorOption("elementsContainer"),h=-1<["absolute","fixed"].indexOf(window.getComputedStyle(u).getPropertyValue("position")),m={};t=this.anchorPreview.offsetWidth/2;var f=this.base.getExtensionByName("toolbar");f&&(c=f.diffLeft,d=f.diffTop),n=c-t,s=h?(o=u.getBoundingClientRect(),["top","left"].forEach(function(e){m[e]=l[e]-o[e]}),m.width=l.width,m.height=l.height,l=m,r=o.width,u.scrollTop):this.window.pageYOffset,i=l.left+l.width/2,s+=a+l.top+l.height-d-this.anchorPreview.offsetHeight,this.anchorPreview.style.top=Math.round(s)+"px",this.anchorPreview.style.right="initial",ithis.hideDelay&&this.detachPreviewHandlers()},detachPreviewHandlers:function(){clearInterval(this.intervalTimer),this.instanceHandlePreviewMouseover&&(this.off(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.activeAnchor&&(this.off(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.off(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout))),this.hidePreview(),this.hovering=this.instanceHandlePreviewMouseover=this.instanceHandlePreviewMouseout=null},attachPreviewHandlers:function(){this.lastOver=(new Date).getTime(),this.hovering=!0,this.instanceHandlePreviewMouseover=this.handlePreviewMouseover.bind(this),this.instanceHandlePreviewMouseout=this.handlePreviewMouseout.bind(this),this.intervalTimer=setInterval(this.updatePreview.bind(this),200),this.on(this.anchorPreview,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.anchorPreview,"mouseout",this.instanceHandlePreviewMouseout),this.on(this.activeAnchor,"mouseover",this.instanceHandlePreviewMouseover),this.on(this.activeAnchor,"mouseout",this.instanceHandlePreviewMouseout)}}),b.extensions.anchorPreview=s,function(){var s,e,r,a;s=[" ","\t","\n","\r"," "," "," "," "," ","\u2028","\u2029"],e="com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw",r=new RegExp("^("+e+")$","i"),a=new RegExp("(((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw)\\/)\\S+(?:[^\\s`!\\[\\]{};:'\".,?«»“”‘’])))|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw))","gi");var t=b.Extension.extend({init:function(){b.Extension.prototype.init.apply(this,arguments),this.disableEventHandling=!1,this.subscribe("editableKeypress",this.onKeypress.bind(this)),this.subscribe("editableBlur",this.onBlur.bind(this)),this.document.execCommand("AutoUrlDetect",!1,!1)},isLastInstance:function(){for(var e=0,t=0;tt?(o=n.splitText(i.length-t),t=0):(o=s.previousNode(),t-=i.length);return o},performLinkingWithinElement:function(e){for(var t=this.findLinkableText(e),n=0;n
    ',fonts:["","Arial","Verdana","Times New Roman"],init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontName")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getSelect().value=""},showForm:function(e){var t=this.getSelect();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontName(),this.base.checkSelection()},createForm:function(){var e,t=this.document,n=t.createElement("div"),i=t.createElement("select"),o=t.createElement("a"),s=t.createElement("a");n.className="medium-editor-toolbar-form",n.id="medium-editor-toolbar-form-fontname-"+this.getEditorId(),this.on(n,"click",this.handleFormClick.bind(this));for(var r=0;r
    ':"✓",n.appendChild(s),this.on(s,"click",this.handleSaveClick.bind(this),!0),o.setAttribute("href","#"),o.className="medium-editor-toolbar-close",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",n.appendChild(o),this.on(o,"click",this.handleCloseClick.bind(this)),n},getSelect:function(){return this.getForm().querySelector("select.medium-editor-toolbar-select")},clearFontName:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("face")&&e.removeAttribute("face")})},handleFontChange:function(){var e=this.getSelect().value;""===e?this.clearFontName():this.execAction("fontName",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontName=c,u=b.extensions.form.extend({name:"fontsize",action:"fontSize",aria:"increase/decrease font size",contentDefault:"±",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments)},handleClick:function(e){if(e.preventDefault(),e.stopPropagation(),!this.isDisplayed()){var t=this.document.queryCommandValue("fontSize")+"";this.showForm(t)}return!1},getForm:function(){return this.form||(this.form=this.createForm()),this.form},isDisplayed:function(){return"block"===this.getForm().style.display},hideForm:function(){this.getForm().style.display="none",this.getInput().value=""},showForm:function(e){var t=this.getInput();this.base.saveSelection(),this.hideToolbarDefaultActions(),this.getForm().style.display="block",this.setToolbarPosition(),t.value=e||"",t.focus()},destroy:function(){if(!this.form)return!1;this.form.parentNode&&this.form.parentNode.removeChild(this.form),delete this.form},doFormSave:function(){this.base.restoreSelection(),this.base.checkSelection()},doFormCancel:function(){this.base.restoreSelection(),this.clearFontSize(),this.base.checkSelection()},createForm:function(){var e=this.document,t=e.createElement("div"),n=e.createElement("input"),i=e.createElement("a"),o=e.createElement("a");return t.className="medium-editor-toolbar-form",t.id="medium-editor-toolbar-form-fontsize-"+this.getEditorId(),this.on(t,"click",this.handleFormClick.bind(this)),n.setAttribute("type","range"),n.setAttribute("min","1"),n.setAttribute("max","7"),n.className="medium-editor-toolbar-input",t.appendChild(n),this.on(n,"change",this.handleSliderChange.bind(this)),o.setAttribute("href","#"),o.className="medium-editor-toolbar-save",o.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"✓",t.appendChild(o),this.on(o,"click",this.handleSaveClick.bind(this),!0),i.setAttribute("href","#"),i.className="medium-editor-toolbar-close",i.innerHTML="fontawesome"===this.getEditorOption("buttonLabels")?'':"×",t.appendChild(i),this.on(i,"click",this.handleCloseClick.bind(this)),t},getInput:function(){return this.getForm().querySelector("input.medium-editor-toolbar-input")},clearFontSize:function(){b.selection.getSelectedElements(this.document).forEach(function(e){"font"===e.nodeName.toLowerCase()&&e.hasAttribute("size")&&e.removeAttribute("size")})},handleSliderChange:function(){var e=this.getInput().value;"4"===e?this.clearFontSize():this.execAction("fontSize",{value:e})},handleFormClick:function(e){e.stopPropagation()},handleSaveClick:function(e){e.preventDefault(),this.doFormSave()},handleCloseClick:function(e){e.preventDefault(),this.doFormCancel()}}),b.extensions.fontSize=u,h="%ME_PASTEBIN%",f=m=null,p=b.Extension.extend({forcePlainText:!0,cleanPastedHTML:!1,preCleanReplacements:[],cleanReplacements:[],cleanAttrs:["class","style","dir"],cleanTags:["meta"],unwrapTags:[],init:function(){b.Extension.prototype.init.apply(this,arguments),(this.forcePlainText||this.cleanPastedHTML)&&(this.subscribe("editableKeydown",this.handleKeydown.bind(this)),this.getEditorElements().forEach(function(e){this.on(e,"paste",this.handlePaste.bind(this))},this),this.subscribe("addElement",this.handleAddElement.bind(this)))},handleAddElement:function(e,t){this.on(t,"paste",this.handlePaste.bind(this))},destroy:function(){(this.forcePlainText||this.cleanPastedHTML)&&this.removePasteBin()},handlePaste:function(e,t){if(!e.defaultPrevented){var n=k(e,this.window,this.document),i=n["text/html"],o=n["text/plain"];this.window.clipboardData&&void 0===e.clipboardData&&!i&&(i=o),(i||o)&&(e.preventDefault(),this.doPaste(i,o,t))}},doPaste:function(e,t,n){var i,o,s="";if(this.cleanPastedHTML&&e)return this.cleanPaste(e);if(t){if(this.getEditorOption("disableReturn")||n&&n.getAttribute("data-disable-return"))s=b.util.htmlEntities(t);else if(1<(i=t.split(/[\r\n]+/g)).length)for(o=0;o"+b.util.htmlEntities(i[o])+"

    ");else s=b.util.htmlEntities(i[0]);b.util.insertHTMLCommand(this.document,s)}},handlePasteBinPaste:function(e){if(e.defaultPrevented)this.removePasteBin();else{var t=k(e,this.window,this.document),n=t["text/html"],i=t["text/plain"],o=f;if(!this.cleanPastedHTML||n)return e.preventDefault(),this.removePasteBin(),this.doPaste(n,i,o),void this.trigger("editablePaste",{currentTarget:o,target:o},o);setTimeout(function(){this.cleanPastedHTML&&(n=this.getPasteBinHtml()),this.removePasteBin(),this.doPaste(n,i,o),this.trigger("editablePaste",{currentTarget:o,target:o},o)}.bind(this),0)}},handleKeydown:function(e,t){b.util.isKey(e,b.util.keyCode.V)&&b.util.isMetaCtrlKey(e)&&(e.stopImmediatePropagation(),this.removePasteBin(),this.createPasteBin(t))},createPasteBin:function(e){var t,n=b.selection.getSelectionRange(this.document),i=this.window.pageYOffset;f=e,n&&((t=n.getClientRects()).length?i+=t[0].top:void 0!==n.startContainer.getBoundingClientRect?i+=n.startContainer.getBoundingClientRect().top:i+=n.getBoundingClientRect().top),m=n;var o=this.document.createElement("div");o.id=this.pasteBinId="medium-editor-pastebin-"+ +Date.now(),o.setAttribute("style","border: 1px red solid; position: absolute; top: "+i+"px; width: 10px; height: 10px; overflow: hidden; opacity: 0"),o.setAttribute("contentEditable",!0),o.innerHTML=h,this.document.body.appendChild(o),this.on(o,"focus",T),this.on(o,"focusin",T),this.on(o,"focusout",T),o.focus(),b.selection.selectNode(o,this.document),this.boundHandlePaste||(this.boundHandlePaste=this.handlePasteBinPaste.bind(this)),this.on(o,"paste",this.boundHandlePaste)},removePasteBin:function(){null!==m&&(b.selection.selectRange(this.document,m),m=null),null!==f&&(f=null);var e=this.getPasteBin();e&&e&&(this.off(e,"focus",T),this.off(e,"focusin",T),this.off(e,"focusout",T),this.off(e,"paste",this.boundHandlePaste),e.parentElement.removeChild(e))},getPasteBin:function(){return this.document.getElementById(this.pasteBinId)},getPasteBinHtml:function(){var e=this.getPasteBin();if(!e)return!1;if(e.firstChild&&"mcepastebin"===e.firstChild.id)return!1;var t=e.innerHTML;return!(!t||t===h)&&t},cleanPaste:function(e){var t,n,i,o,s=/]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g),""],[new RegExp(/|/g),""],[new RegExp(/
    $/i),""],[new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi),""],[new RegExp(/<\/b>(]*>)?$/gi),""],[new RegExp(/\s+<\/span>/g)," "],[new RegExp(/
    /g),"
    "],[new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi),''],[new RegExp(/]*font-style:italic[^>]*>/gi),''],[new RegExp(/]*font-weight:(bold|700)[^>]*>/gi),''],[new RegExp(/<(\/?)([iba])>/gi),"<$1$2>"],[new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi),''],[new RegExp(/<\/p>\n+/gi),"

    "],[new RegExp(/\n+

    /gi),""],[new RegExp(/(((?!/gi),"$1"]],this.cleanReplacements||[]);for(t=0;t"+e.split("

    ").join("

    ")+"

    ",n=i.querySelectorAll("a,p,div,br"),t=0;t"+i.innerHTML+"
    ":o.innerHTML=i.innerHTML,i.parentNode.replaceChild(o,i);for(s=e.querySelectorAll("span"),n=0;n]*>","g"),t=b.selection.getSelectionHtml(this.document).replace(/<[^\/>][^>]*><\/[^>]+>/gim,"").match(e);return!!t&&1r+e.offsetHeight-l-this.stickyTopOffset?(o.style.top=r+e.offsetHeight-l+"px",o.classList.remove("medium-editor-sticky-toolbar")):n>r-l-this.stickyTopOffset?(o.classList.add("medium-editor-sticky-toolbar"),o.style.top=this.stickyTopOffset+"px"):(o.classList.remove("medium-editor-sticky-toolbar"),o.style.top=r-l+"px"):o.style.top=r-l+"px",this.align){case"left":t=s.left;break;case"right":t=s.right-c;break;case"center":t=a-d}t<0?t=0:i'),t.onload=function(){var e=this.document.getElementById(n);e&&(e.removeAttribute("id"),e.removeAttribute("class"),e.src=t.result)}.bind(this))}.bind(this)),e.target.classList.remove("medium-editor-dragover")}}),b.extensions.imageDragging=E,C={},b.prototype={init:function(e,t){return this.options=function(e,t){return t&&[["allowMultiParagraphSelection","toolbar.allowMultiParagraphSelection"]].forEach(function(e){t.hasOwnProperty(e[0])&&void 0!==t[e[0]]&&b.util.deprecated(e[0],e[1],"v6.0.0")}),b.util.defaults({},t,e)}.call(this,this.defaults,t),this.origElements=e,this.options.elementsContainer||(this.options.elementsContainer=this.options.ownerDocument.body),this.setup()},setup:function(){this.isActive||(function(e){e._mediumEditors||(e._mediumEditors=[null]),this.id||(this.id=e._mediumEditors.length),e._mediumEditors[this.id]=this}.call(this,this.options.contentWindow),this.events=new b.Events(this),this.elements=[],this.addElements(this.origElements),0!==this.elements.length&&(this.isActive=!0,M.call(this),F.call(this)))},destroy:function(){this.isActive&&(this.isActive=!1,this.extensions.forEach(function(e){"function"==typeof e.destroy&&e.destroy()},this),this.events.destroy(),this.elements.forEach(function(e){this.options.spellcheck&&(e.innerHTML=e.innerHTML),e.removeAttribute("contentEditable"),e.removeAttribute("spellcheck"),e.removeAttribute("data-medium-editor-element"),e.classList.remove("medium-editor-element"),e.removeAttribute("role"),e.removeAttribute("aria-multiline"),e.removeAttribute("medium-editor-index"),e.removeAttribute("data-medium-editor-editor-index"),e.getAttribute("medium-editor-textarea-id")&&L(e)},this),this.elements=[],this.instanceHandleEditableKeydownEnter=null,this.instanceHandleEditableInput=null,function(e){e._mediumEditors&&e._mediumEditors[this.id]&&(e._mediumEditors[this.id]=null)}.call(this,this.options.contentWindow))},on:function(e,t,n,i){return this.events.attachDOMEvent(e,t,n,i),this},off:function(e,t,n,i){return this.events.detachDOMEvent(e,t,n,i),this},subscribe:function(e,t){return this.events.attachCustomEvent(e,t),this},unsubscribe:function(e,t){return this.events.detachCustomEvent(e,t),this},trigger:function(e,t,n){return this.events.triggerCustomEvent(e,t,n),this},delay:function(e){var t=this;return setTimeout(function(){t.isActive&&e()},this.options.delay)},serialize:function(){var e,t={},n=this.elements.length;for(e=0;e' + + '' + 'Link: http' + '://' + - '' + - '' + + '' + + '' + 'www' + '.google.com' + - '' + - '' + + '' + + '' + '/wow ' + 'impressive' + - ''; + ''; selectElementContentsAndFire(this.el.firstChild); @@ -358,28 +358,28 @@ describe('Autolink', function () { links[0].firstChild.removeAttribute('data-href'); // to make the next innerHTML check work consistently var expectedOutput = '' + - '' + + '' + 'Link: ' + - '' + - '' + + '' + + '' + '' + - '' + - 'http' + - '://' + - '' + - '' + - 'www' + - '.google.com' + - '' + - '' + - '/wow' + - '' + + '' + + 'http' + + '://' + + '' + + '' + + 'www' + + '.google.com' + + '' + + '' + + '/wow' + + '' + '' + - '' + - '' + + '' + + '' + ' ' + 'impressive' + - ''; + ''; expect(this.el.innerHTML).toBe(expectedOutput); }); @@ -433,7 +433,7 @@ describe('Autolink', function () { }); it('should create a link with data-auto-link=true when the text no longer matches the original link' + - ' and it has been unlinked', function () { + ' and it has been unlinked', function () { this.el.innerHTML = 'Click this ' + 'www.example.co.uk link'; diff --git a/spec/buttons.spec.js b/spec/buttons.spec.js index 628b4198f..746e9bc2a 100644 --- a/spec/buttons.spec.js +++ b/spec/buttons.spec.js @@ -869,10 +869,10 @@ describe('Buttons TestCase', function () { it('buttons should deactivate other justify buttons', function () { this.el.innerHTML = '

    lorem ipsum

    ' + - '

    lorem ipsum

    ' + - '

    lorem ipsum

    ' + - '

    lorem ipsum

    ' + - '

    lorem ipsum

    '; + '

    lorem ipsum

    ' + + '

    lorem ipsum

    ' + + '

    lorem ipsum

    ' + + '

    lorem ipsum

    '; var editor = this.newMediumEditor('.editor', { toolbar: { buttons: ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'] @@ -1069,7 +1069,7 @@ describe('Buttons TestCase', function () { }); }); -function stripAttrIfEmpty(element, attribute) { +function stripAttrIfEmpty (element, attribute) { // we want to strip empty attributes (especially styles, // because the tests create style tags, inject style content, // and then remove that style content. diff --git a/spec/dyn-elements.spec.js b/spec/dyn-elements.spec.js index 64fb1bcf9..dc07d1e3f 100644 --- a/spec/dyn-elements.spec.js +++ b/spec/dyn-elements.spec.js @@ -17,14 +17,14 @@ describe('MediumEditor.DynamicElements TestCase', function () { describe('addElements', function () { it('should initialize dom element properly when adding dynamically', function () { var editor = this.newMediumEditor('.editor'), - focusedEditable, - blurredEditable, - focusListener = function (event, editable) { - focusedEditable = editable; - }, - blurListener = function (event, editable) { - blurredEditable = editable; - }; + focusedEditable, + blurredEditable, + focusListener = function (event, editable) { + focusedEditable = editable; + }, + blurListener = function (event, editable) { + blurredEditable = editable; + }; editor.subscribe('focus', focusListener); editor.subscribe('blur', blurListener); @@ -63,7 +63,8 @@ describe('MediumEditor.DynamicElements TestCase', function () { var editor = this.newMediumEditor('.editor', { anchorPreview: false }); expect(this.el.listenerInfo.length).not.toBe(0); var listenerCount = this.el.listenerInfo.length; - editor.subscribe('editableBlur', function blurHandler () { }); + editor.subscribe('editableBlur', function blurHandler () { + }); expect(this.el.listenerInfo.length).toBe(listenerCount + 1); // When adding a new element, all handlers should also be added to that element @@ -72,7 +73,8 @@ describe('MediumEditor.DynamicElements TestCase', function () { expect(this.addOne.listenerInfo.length).toBe(this.el.listenerInfo.length); // When attaching a new handler, the handler should be added to dynamically added elements too - editor.subscribe('editableMouseover', function mouseoverHandler () {}); + editor.subscribe('editableMouseover', function mouseoverHandler () { + }); expect(this.el.listenerInfo.length).toBe(listenerCount + 2); expect(this.addOne.listenerInfo.length).toBe(listenerCount + 2); @@ -127,22 +129,22 @@ describe('MediumEditor.DynamicElements TestCase', function () { expect(spy).toHaveBeenCalledWith({ target: this.addTwo, currentTarget: this.addTwo }, this.addTwo); }); - function runAddTest(inputSupported) { + function runAddTest (inputSupported) { it('should re-attach element properly when removed from dom, cleaned up and injected to dom again', function () { var originalInputSupport = MediumEditor.Events.prototype.InputEventOnContenteditableSupported; MediumEditor.Events.prototype.InputEventOnContenteditableSupported = inputSupported; var editor = this.newMediumEditor('.editor'), - focusedEditable, - firedTarget, - firedCounter, - handler = function (event, editable) { - firedTarget = editable; - firedCounter++; - }, - focusListener = function (event, editable) { - focusedEditable = editable; - }; + focusedEditable, + firedTarget, + firedCounter, + handler = function (event, editable) { + firedTarget = editable; + firedCounter++; + }, + focusListener = function (event, editable) { + focusedEditable = editable; + }; firedCounter = 0; @@ -262,7 +264,7 @@ describe('MediumEditor.DynamicElements TestCase', function () { }); }); -function detach(node, async, fn) { +function detach (node, async, fn) { var parent = node.parentNode, next = node.nextSibling; // No parent node? Abort! @@ -285,8 +287,9 @@ function detach(node, async, fn) { fn.call(node); reattach(); } + // Re-attach node to DOM. - function reattach() { + function reattach () { parent.insertBefore(node, next); } } diff --git a/spec/events.spec.js b/spec/events.spec.js index 1aac19aee..a89b06601 100644 --- a/spec/events.spec.js +++ b/spec/events.spec.js @@ -273,7 +273,8 @@ describe('MediumEditor.Events TestCase', function () { var origExecCommand = document.execCommand, editor = this.newMediumEditor('.editor'); - editor.subscribe('editableInput', function () { }); + editor.subscribe('editableInput', function () { + }); expect(document.execCommand).not.toBe(origExecCommand); editor.selectElement(editor.elements[0].firstChild); @@ -331,7 +332,7 @@ describe('MediumEditor.Events TestCase', function () { describe('Custom EditableInput Listener', function () { - function runEditableInputTests(inputSupported) { + function runEditableInputTests (inputSupported) { var namePrefix = inputSupported ? 'when Input is supported' : 'when Input is NOT supported'; it(namePrefix + ' should trigger with the corresponding editor element passed as an argument', function () { diff --git a/spec/extension.spec.js b/spec/extension.spec.js index 0ef322567..d38f8a413 100644 --- a/spec/extension.spec.js +++ b/spec/extension.spec.js @@ -52,8 +52,10 @@ describe('Extensions TestCase', function () { }); it('should set the name of property of extensions', function () { - var ExtensionOne = function () {}, - ExtensionTwo = function () {}, + var ExtensionOne = function () { + }, + ExtensionTwo = function () { + }, extOne = new ExtensionOne(), extTwo = new ExtensionTwo(), editor = this.newMediumEditor('.editor', { @@ -111,15 +113,16 @@ describe('Extensions TestCase', function () { it('should call destroy on extensions when being destroyed', function () { var TempExtension = MediumEditor.Extension.extend({ - destroy: function () {} + destroy: function () { + } }), extInstance = new TempExtension(); spyOn(extInstance, 'destroy'); var editor = this.newMediumEditor('.editor', { - extensions: { - 'temp-extension': extInstance - } - }); + extensions: { + 'temp-extension': extInstance + } + }); editor.destroy(); expect(extInstance.destroy).toHaveBeenCalled(); }); @@ -225,7 +228,8 @@ describe('Extensions TestCase', function () { describe('All extensions', function () { it('should get helper methods to call into base instance methods', function () { - var noop = function () {}, + var noop = function () { + }, helpers = { 'on': [document, 'click', noop, false], 'off': [document, 'click', noop, false], @@ -328,7 +332,8 @@ describe('Extensions TestCase', function () { button.innerText = 'XXX'; return button; }, - checkState: function () {} + checkState: function () { + } }, ExtensionWithString = { getButton: function () { @@ -336,7 +341,8 @@ describe('Extensions TestCase', function () { } }, ExtensionWithNoButton = function () { - this.init = function () {}; + this.init = function () { + }; }; it('should include extensions button into toolbar', function () { @@ -354,13 +360,13 @@ describe('Extensions TestCase', function () { it('should call checkState on extensions when toolbar selection updates', function () { var editor = this.newMediumEditor('.editor', { - toolbar: { - buttons: ['dummy'] - }, - extensions: { - 'dummy': ExtensionWithElement - } - }); + toolbar: { + buttons: ['dummy'] + }, + extensions: { + 'dummy': ExtensionWithElement + } + }); selectElementContentsAndFire(editor.elements[0].firstChild, { eventToFire: 'focus' }); spyOn(ExtensionWithElement, 'checkState').and.callThrough(); editor.checkSelection(); diff --git a/spec/fontname.spec.js b/spec/fontname.spec.js index 718d1010b..9dbd69833 100644 --- a/spec/fontname.spec.js +++ b/spec/fontname.spec.js @@ -4,7 +4,7 @@ describe('Font Name Button TestCase', function () { 'use strict'; - function testFontNameContents(el, name) { + function testFontNameContents (el, name) { expect(el.childNodes.length).toBe(1); var child = el.childNodes[0]; expect(child.nodeName.toLowerCase()).toBe('font'); @@ -69,7 +69,7 @@ describe('Font Name Button TestCase', function () { expect(document.execCommand).toHaveBeenCalledWith('fontName', false, 'Arial'); - fireEvent(fontNameExtension.getForm().querySelector('a.medium-editor-toobar-save'), 'click'); + fireEvent(fontNameExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); testFontNameContents(this.el, 'Arial'); }); @@ -109,7 +109,7 @@ describe('Font Name Button TestCase', function () { selectElementContents(editor.elements[0]); fireEvent(select, 'change'); - fireEvent(fontNameExtension.getForm().querySelector('a.medium-editor-toobar-save'), 'click'); + fireEvent(fontNameExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); testFontNameContents(this.el, null); // TODO: remove the element entirely instead of just the `size` attribute expect(fontNameExtension.clearFontName).toHaveBeenCalled(); }); @@ -127,7 +127,7 @@ describe('Font Name Button TestCase', function () { selectElementContentsAndFire(editor.elements[0]); button = toolbar.getToolbarElement().querySelector('[data-action="fontName"]'); - cancel = fontNameExtension.getForm().querySelector('a.medium-editor-toobar-close'); + cancel = fontNameExtension.getForm().querySelector('a.medium-editor-toolbar-close'); fireEvent(button, 'click'); expect(fontNameExtension.isDisplayed()).toBe(true); diff --git a/spec/fontsize.spec.js b/spec/fontsize.spec.js index 6d948d1a0..f933af344 100644 --- a/spec/fontsize.spec.js +++ b/spec/fontsize.spec.js @@ -4,7 +4,7 @@ describe('Font Size Button TestCase', function () { 'use strict'; - function testFontSizeContents(el, size) { + function testFontSizeContents (el, size) { expect(el.childNodes.length).toBe(1); var child = el.childNodes[0]; expect(child.nodeName.toLowerCase()).toBe('font'); @@ -69,7 +69,7 @@ describe('Font Size Button TestCase', function () { expect(document.execCommand).toHaveBeenCalledWith('fontSize', false, '7'); - fireEvent(fontSizeExtension.getForm().querySelector('a.medium-editor-toobar-save'), 'click'); + fireEvent(fontSizeExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); testFontSizeContents(this.el, '7'); }); @@ -109,7 +109,7 @@ describe('Font Size Button TestCase', function () { selectElementContents(editor.elements[0]); fireEvent(input, 'change'); - fireEvent(fontSizeExtension.getForm().querySelector('a.medium-editor-toobar-save'), 'click'); + fireEvent(fontSizeExtension.getForm().querySelector('a.medium-editor-toolbar-save'), 'click'); testFontSizeContents(this.el, null); // TODO: remove the element entirely instead of just the `size` attribute expect(fontSizeExtension.clearFontSize).toHaveBeenCalled(); }); @@ -127,7 +127,7 @@ describe('Font Size Button TestCase', function () { selectElementContentsAndFire(editor.elements[0]); button = toolbar.getToolbarElement().querySelector('[data-action="fontSize"]'); - cancel = fontSizeExtension.getForm().querySelector('a.medium-editor-toobar-close'); + cancel = fontSizeExtension.getForm().querySelector('a.medium-editor-toolbar-close'); fireEvent(button, 'click'); expect(fontSizeExtension.isDisplayed()).toBe(true); diff --git a/spec/init.spec.js b/spec/init.spec.js index a16e6d181..9d99747f5 100644 --- a/spec/init.spec.js +++ b/spec/init.spec.js @@ -97,21 +97,21 @@ describe('Initialization TestCase', function () { describe('With a valid element', function () { it('should have a default set of options', function () { var defaultOptions = { - delay: 0, - disableReturn: false, - disableDoubleReturn: false, - disableExtraSpaces: false, - disableEditing: false, - autoLink: false, - elementsContainer: document.body, - contentWindow: window, - ownerDocument: document, - buttonLabels: false, - targetBlank: false, - extensions: {}, - activeButtonClass: 'medium-editor-button-active', - spellcheck: true - }, + delay: 0, + disableReturn: false, + disableDoubleReturn: false, + disableExtraSpaces: false, + disableEditing: false, + autoLink: false, + elementsContainer: document.body, + contentWindow: window, + ownerDocument: document, + buttonLabels: false, + targetBlank: false, + extensions: {}, + activeButtonClass: 'medium-editor-button-active', + spellcheck: true + }, editor = this.newMediumEditor('.editor'); expect(Object.keys(editor.options).length).toBe(Object.keys(defaultOptions).length); expect(_.isEqual(editor.options, defaultOptions)).toBe(true); @@ -119,20 +119,20 @@ describe('Initialization TestCase', function () { it('should accept custom options values', function () { var options = { - delay: 300, - toolbar: { - diffLeft: 10, - diffTop: 5 - }, - anchor: { - placeholderText: 'test', - targetCheckboxText: 'new window?' + delay: 300, + toolbar: { + diffLeft: 10, + diffTop: 5 + }, + anchor: { + placeholderText: 'test', + targetCheckboxText: 'new window?' + }, + paste: { + forcePlainText: false, + cleanPastedHTML: true + } }, - paste: { - forcePlainText: false, - cleanPastedHTML: true - } - }, editor = this.newMediumEditor('.editor', options); Object.keys(options).forEach(function (customOption) { expect(editor.options[customOption]).toBe(options[customOption]); diff --git a/spec/keyboard-commands.spec.js b/spec/keyboard-commands.spec.js index 9488f7f1a..f108c7143 100644 --- a/spec/keyboard-commands.spec.js +++ b/spec/keyboard-commands.spec.js @@ -255,17 +255,17 @@ describe('KeyboardCommands TestCase', function () { spyOn(MediumEditor.prototype, 'execAction'); var result, editor = this.newMediumEditor('.editor', { - keyboardCommands: { - commands: [ - { - command: false, - key: 'J', - meta: true, - shift: false - } - ] - } - }); + keyboardCommands: { + commands: [ + { + command: false, + key: 'J', + meta: true, + shift: false + } + ] + } + }); selectElementContentsAndFire(editor.elements[0]); fireEvent(editor.elements[0], 'keydown', { diff --git a/spec/paste.spec.js b/spec/paste.spec.js index 1ab7f4de9..6fb8a88ea 100644 --- a/spec/paste.spec.js +++ b/spec/paste.spec.js @@ -261,11 +261,11 @@ describe('Pasting content', function () { describe('using keyboard', function () { it('should insert a custom paste-bin on keydown of CTRL + V', function () { var editor = this.newMediumEditor('.editor', { - paste: { - forcePlainText: false, - cleanPastedHTML: true - } - }); + paste: { + forcePlainText: false, + cleanPastedHTML: true + } + }); selectElementContentsAndFire(editor.elements[0].firstChild); @@ -347,7 +347,8 @@ describe('Pasting content', function () { var evt = { type: 'paste', defaultPrevented: false, - preventDefault: function () {}, + preventDefault: function () { + }, clipboardData: { types: ['text/plain', 'text/html'], getData: function () { @@ -360,7 +361,10 @@ describe('Pasting content', function () { pasteExtension.handlePasteBinPaste(evt); jasmine.clock().tick(1); - expect(spy).toHaveBeenCalledWith({ currentTarget: editor.elements[0], target: editor.elements[0] }, editor.elements[0]); + expect(spy).toHaveBeenCalledWith({ + currentTarget: editor.elements[0], + target: editor.elements[0] + }, editor.elements[0]); }); it('should do nothing if default was prevented on paste event of the paste-bin', function () { @@ -387,17 +391,18 @@ describe('Pasting content', function () { expect(contentEditables.length).toBe(2); var evt = { - type: 'paste', - defaultPrevented: true, - preventDefault: function () {}, - clipboardData: { - types: ['text/plain', 'text/html'], - getData: function () { - // do we need to return different results for the different types? text/plain, text/html - return 'pasted content'; - } + type: 'paste', + defaultPrevented: true, + preventDefault: function () { + }, + clipboardData: { + types: ['text/plain', 'text/html'], + getData: function () { + // do we need to return different results for the different types? text/plain, text/html + return 'pasted content'; } - }; + } + }; spyOn(evt, 'preventDefault'); @@ -435,16 +440,17 @@ describe('Pasting content', function () { expect(contentEditables.length).toBe(2); var evt = { - type: 'paste', - preventDefault: function () {}, - clipboardData: { - types: ['text/plain', 'text/html'], - getData: function () { - // do we need to return different results for the different types? text/plain, text/html - return 'pasted content'; - } + type: 'paste', + preventDefault: function () { + }, + clipboardData: { + types: ['text/plain', 'text/html'], + getData: function () { + // do we need to return different results for the different types? text/plain, text/html + return 'pasted content'; } - }; + } + }; spyOn(evt, 'preventDefault'); @@ -483,7 +489,8 @@ describe('Pasting content', function () { var evt = { type: 'paste', - preventDefault: function () {}, + preventDefault: function () { + }, clipboardData: { types: ['text/plain'], getData: function () { @@ -536,7 +543,8 @@ describe('Pasting content', function () { var evt = { type: 'paste', - preventDefault: function () {}, + preventDefault: function () { + }, clipboardData: { types: ['text/plain'], getData: function () { @@ -595,11 +603,11 @@ describe('Pasting content', function () { it('should filter inline rich-text when "insertHTML" command is not supported', function () { var editor = this.newMediumEditor('.editor', { - paste: { - forcePlainText: false, - cleanPastedHTML: true - } - }); + paste: { + forcePlainText: false, + cleanPastedHTML: true + } + }); spyOn(document, 'queryCommandSupported').and.returnValue(false); diff --git a/spec/placeholder.spec.js b/spec/placeholder.spec.js index 62e99a2b7..535b17f15 100644 --- a/spec/placeholder.spec.js +++ b/spec/placeholder.spec.js @@ -75,7 +75,7 @@ describe('MediumEditor.extensions.placeholder TestCase', function () { }); it('should remove the placeholder on input, and NOT on click', function () { - var editor = this.newMediumEditor('.editor', { placeholder: { hideOnClick: false }}); + var editor = this.newMediumEditor('.editor', { placeholder: { hideOnClick: false } }); expect(editor.elements[0].className).toContain('medium-editor-placeholder'); fireEvent(editor.elements[0], 'click'); expect(editor.elements[0].className).toContain('medium-editor-placeholder'); @@ -137,21 +137,21 @@ describe('MediumEditor.extensions.placeholder TestCase', function () { }); /*jslint regexp: true*/ - function validatePlaceholderContent(element, expectedValue) { + function validatePlaceholderContent (element, expectedValue) { var placeholder = window.getComputedStyle(element, ':after').getPropertyValue('content'), regex = /^attr\(([^\)]+)\)$/g, match = regex.exec(placeholder); if (match) { // In firefox, getComputedStyle().getPropertyValue('content') can return attr() instead of what attr() evaluates to expect(match[1]).toBe('data-placeholder'); - } - // When these tests run in firefox in saucelabs, for some reason the content property of the - // placeholder is 'none'. Not sure why this happens, or why this is specific to saucelabs - // but for now, just skipping the assertion in this case - else if (placeholder !== 'none') { + } else if (placeholder !== 'none') { + // When these tests run in firefox in saucelabs, for some reason the content property of the + // placeholder is 'none'. Not sure why this happens, or why this is specific to saucelabs + // but for now, just skipping the assertion in this case expect(placeholder).toMatch(new RegExp('^[\'"]' + expectedValue + '[\'"]$')); } } + /*jslint regexp: false*/ it('should add the default placeholder text when data-placeholder is not present', function () { @@ -212,10 +212,10 @@ describe('MediumEditor.extensions.placeholder TestCase', function () { it('should use custom placeholder text when passed as the placeholder.text option', function () { var placeholderText = 'Custom placeholder', editor = this.newMediumEditor('.editor', { - placeholder: { - text: placeholderText - } - }); + placeholder: { + text: placeholderText + } + }); validatePlaceholderContent(editor.elements[0], placeholderText); }); diff --git a/spec/selection.spec.js b/spec/selection.spec.js index 119dc9c78..4e371604b 100644 --- a/spec/selection.spec.js +++ b/spec/selection.spec.js @@ -43,17 +43,17 @@ describe('MediumEditor.selection TestCase', function () { }); it('should not export a position indicating the cursor is after an empty paragraph (in a complicated markup case)', - function () { - this.el.innerHTML = '

    www.google.com


    ' + - '

    Whatever

    '; - // Before the 'e' in whatever - placeCursorInsideElement(this.el.querySelector('span.target').firstChild, 0); - var exportedSelection = MediumEditor.selection.exportSelection(this.el, document); - expect(exportedSelection.emptyBlocksIndex).toEqual(undefined); - }); + function () { + this.el.innerHTML = '

    www.google.com


    ' + + '

    Whatever

    '; + // Before the 'e' in whatever + placeCursorInsideElement(this.el.querySelector('span.target').firstChild, 0); + var exportedSelection = MediumEditor.selection.exportSelection(this.el, document); + expect(exportedSelection.emptyBlocksIndex).toEqual(undefined); + }); it('should not export a position indicating the cursor is after an empty paragraph ' + - '(in a complicated markup with selection on the element)', function () { + '(in a complicated markup with selection on the element)', function () { this.el.innerHTML = '

    www.google.com


    ' + '

    Whatever

    '; // Before the 'e' in whatever @@ -324,10 +324,10 @@ describe('MediumEditor.selection TestCase', function () { // https://github.com/yabwe/medium-editor/issues/732 it('should support a selection correctly when space + newlines are separating block elements', function () { this.el.innerHTML = '
      \n' + - '
    • a link
    • \n' + - '
    • a list item
    • \n' + - '
    • target
    • \n' + - '
    '; + '
  • a link
  • \n' + + '
  • a list item
  • \n' + + '
  • target
  • \n' + + ''; var lastLi = this.el.querySelectorAll('ul > li')[2]; // Select the
  • with 'target' @@ -346,7 +346,12 @@ describe('MediumEditor.selection TestCase', function () { it('should support a selection that specifies an image is the selection', function () { this.el.innerHTML = '

    lorem ipsum dolor

    '; - MediumEditor.selection.importSelection({ start: 12, end: 12, startsWithImage: true, trailingImageCount: 1 }, this.el, document); + MediumEditor.selection.importSelection({ + start: 12, + end: 12, + startsWithImage: true, + trailingImageCount: 1 + }, this.el, document); var range = window.getSelection().getRangeAt(0); expect(range.toString()).toBe(''); expect(MediumEditor.util.isDescendant(range.endContainer, this.el.querySelector('img'), true)).toBe(true, 'the image is not within the selection'); diff --git a/spec/util.spec.js b/spec/util.spec.js index de688b2bc..ea920648d 100644 --- a/spec/util.spec.js +++ b/spec/util.spec.js @@ -49,7 +49,8 @@ describe('MediumEditor.util', function () { describe('Deprecated', function () { it('should warn when a method is deprecated', function () { var testObj = { - newMethod: function () {} + newMethod: function () { + } }; spyOn(testObj, 'newMethod').and.callThrough(); spyOn(MediumEditor.util, 'warn').and.callThrough(); @@ -193,8 +194,7 @@ describe('MediumEditor.util', function () { * 1 2 3 4 5 6 */ it('should split a complex tree correctly when splitting off right part of tree', function () { - var el = this.createElement('div', '', - '123456'), + var el = this.createElement('div', '', '123456'), splitOn = el.querySelector('u').firstChild, result = MediumEditor.util.splitOffDOMTree(el, splitOn); @@ -219,8 +219,7 @@ describe('MediumEditor.util', function () { * 1 2 3 4 5 6 */ it('should split a complex tree correctly when splitting off left part of tree', function () { - var el = this.createElement('div', '', - '123456'), + var el = this.createElement('div', '', '123456'), splitOn = el.querySelector('u').firstChild, result = MediumEditor.util.splitOffDOMTree(el, splitOn, true); @@ -251,7 +250,8 @@ describe('MediumEditor.util', function () { ':443/' + 'path/to' + 'somewhere#' + - 'index notLink'), + 'index notLink' + ), firstText = el.firstChild.firstChild.splitText('Link = '.length), lastText = el.lastChild.firstChild, para = this.createElement('p', ''); @@ -260,14 +260,14 @@ describe('MediumEditor.util', function () { expect(el.innerHTML).toBe( 'Link = ' + '

    ' + - 'http' + - '://' + - 'www.exam' + - 'ple.com' + - ':443/' + - 'path/to' + - 'somewhere#' + - 'index' + + 'http' + + '://' + + 'www.exam' + + 'ple.com' + + ':443/' + + 'path/to' + + 'somewhere#' + + 'index' + '

    ' + ' notLink' ); @@ -503,10 +503,10 @@ describe('MediumEditor.util', function () { var el = this.createElement('div'); el.innerHTML = '' + '
      ' + - '
    1. Head
    2. ' + - '
    3. List Item
    4. ' + - '
    ' + - '

    paragraph

    ' + + '
  • Head
  • ' + + '
  • List Item
  • ' + + '' + + '

    paragraph

    ' + '
    '; var parts = MediumEditor.util.splitByBlockElements(el); @@ -527,9 +527,9 @@ describe('MediumEditor.util', function () { var el = this.createElement('div'); el.innerHTML = '' + '
    ' + - 'Text bold bold + italics underlined' + - '
    1. List Item
    ' + - 'Text Node' + + 'Text bold bold + italics underlined' + + '
    1. List Item
    ' + + 'Text Node' + '
    '; var parts = MediumEditor.util.splitByBlockElements(el); expect(parts.length).toBe(3); @@ -554,8 +554,8 @@ describe('MediumEditor.util', function () { it('should ignore nested comments', function () { var el = this.createElement('div'); el.innerHTML = '' + - '

    Text

    ' + - ''; + '

    Text

    ' + + ''; var parts = MediumEditor.util.splitByBlockElements(el); expect(parts.length).toBe(1); }); @@ -625,7 +625,10 @@ describe('MediumEditor.util', function () { it('should return text nodes within an element', function () { var el = this.createElement('div'); el.innerHTML = '

    Plain bold link italic underline span1 span2

    '; - var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { start: 0, end: el.textContent.length }); + var textNodes = MediumEditor.util.findOrCreateMatchingTextNodes(document, el, { + start: 0, + end: el.textContent.length + }); expect(textNodes.length).toBe(11); expect(textNodes[0].nodeValue).toBe('Plain '); expect(textNodes[9].nodeValue).toBe('span1 '); diff --git a/src/js/core.js b/src/js/core.js index e70d69618..e530e640f 100644 --- a/src/js/core.js +++ b/src/js/core.js @@ -3,7 +3,7 @@ // Event handlers that shouldn't be exposed externally - function handleDisableExtraSpaces(event) { + function handleDisableExtraSpaces (event) { var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), textContent = node.textContent, caretPositions = MediumEditor.selection.getCaretOffsets(node); @@ -13,7 +13,7 @@ } } - function handleDisabledEnterKeydown(event, element) { + function handleDisabledEnterKeydown (event, element) { if (this.options.disableReturn || element.getAttribute('data-disable-return')) { event.preventDefault(); } else if (this.options.disableDoubleReturn || element.getAttribute('data-disable-double-return')) { @@ -22,13 +22,13 @@ // if current text selection is empty OR previous sibling text is empty OR it is not a list if ((node && node.textContent.trim() === '' && node.nodeName.toLowerCase() !== 'li') || (node.previousElementSibling && node.previousElementSibling.nodeName.toLowerCase() !== 'br' && - node.previousElementSibling.textContent.trim() === '')) { + node.previousElementSibling.textContent.trim() === '')) { event.preventDefault(); } } } - function handleTabKeydown(event) { + function handleTabKeydown (event) { // Override tab only for pre nodes var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tag = node && node.nodeName.toLowerCase(); @@ -51,27 +51,27 @@ } } - function handleBlockDeleteKeydowns(event) { + function handleBlockDeleteKeydowns (event) { var p, node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tagName = node.nodeName.toLowerCase(), isEmpty = /^(\s+|)?$/i, isHeader = /h\d/i; if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.BACKSPACE, MediumEditor.util.keyCode.ENTER]) && - // has a preceeding sibling - node.previousElementSibling && - // in a header - isHeader.test(tagName) && - // at the very end of the block - MediumEditor.selection.getCaretOffsets(node).left === 0) { + // has a preceding sibling + node.previousElementSibling && + // in a header + isHeader.test(tagName) && + // at the very end of the block + MediumEditor.selection.getCaretOffsets(node).left === 0) { if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && isEmpty.test(node.previousElementSibling.innerHTML)) { - // backspacing the begining of a header into an empty previous element will + // backspacing the beginning of a header into an empty previous element will // change the tagName of the current node to prevent one // instead delete previous node and cancel the event. node.previousElementSibling.parentNode.removeChild(node.previousElementSibling); event.preventDefault(); } else if (!this.options.disableDoubleReturn && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) { - // hitting return in the begining of a header will create empty header elements before the current one + // hitting return in the beginning of a header will create empty header elements before the current one // instead, make "


    " element, which are what happens if you hit return in an empty paragraph p = this.options.ownerDocument.createElement('p'); p.innerHTML = '
    '; @@ -79,19 +79,19 @@ event.preventDefault(); } } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.DELETE) && - // between two sibling elements - node.nextElementSibling && - node.previousElementSibling && - // not in a header - !isHeader.test(tagName) && - // in an empty tag - isEmpty.test(node.innerHTML) && - // when the next tag *is* a header - isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) { + // between two sibling elements + node.nextElementSibling && + node.previousElementSibling && + // not in a header + !isHeader.test(tagName) && + // in an empty tag + isEmpty.test(node.innerHTML) && + // when the next tag *is* a header + isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) { // hitting delete in an empty element preceding a header, ex: //

    [CURSOR]

    Header

    // Will cause the h1 to become a paragraph. - // Instead, delete the paragraph node and move the cursor to the begining of the h1 + // Instead, delete the paragraph node and move the cursor to the beginning of the h1 // remove node and move cursor to start of header MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextElementSibling); @@ -100,16 +100,16 @@ event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - tagName === 'li' && - // hitting backspace inside an empty li - isEmpty.test(node.innerHTML) && - // is first element (no preceeding siblings) - !node.previousElementSibling && - // parent also does not have a sibling - !node.parentElement.previousElementSibling && - // is not the only li in a list - node.nextElementSibling && - node.nextElementSibling.nodeName.toLowerCase() === 'li') { + tagName === 'li' && + // hitting backspace inside an empty li + isEmpty.test(node.innerHTML) && + // is first element (no preceding siblings) + !node.previousElementSibling && + // parent also does not have a sibling + !node.parentElement.previousElementSibling && + // is not the only li in a list + node.nextElementSibling && + node.nextElementSibling.nodeName.toLowerCase() === 'li') { // backspacing in an empty first list element in the first list (with more elements) ex: //
    • [CURSOR]
    • List Item 2
    // will remove the first
  • but add some extra element before (varies based on browser) @@ -131,16 +131,16 @@ event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).left === 0) { + (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && + MediumEditor.selection.getCaretOffsets(node).left === 0) { - // when cursor is at the begining of the element and the element is
    + // when cursor is at the beginning of the element and the element is
    // then pressing backspace key should change the
    to a

    tag event.preventDefault(); MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p'); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).right === 0) { + (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && + MediumEditor.selection.getCaretOffsets(node).right === 0) { // when cursor is at the end of

    , // then pressing enter key should create

    tag, not

    @@ -153,10 +153,10 @@ event.preventDefault(); } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - MediumEditor.util.isMediumEditorElement(node.parentElement) && - !node.previousElementSibling && - node.nextElementSibling && - isEmpty.test(node.innerHTML)) { + MediumEditor.util.isMediumEditorElement(node.parentElement) && + !node.previousElementSibling && + node.nextElementSibling && + isEmpty.test(node.innerHTML)) { // when cursor is in the first element, it's empty and user presses backspace, // do delete action instead to get rid of the first element and move caret to 2nd @@ -166,7 +166,7 @@ } } - function handleKeyup(event) { + function handleKeyup (event) { var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), tagName; @@ -211,7 +211,7 @@ } } - function handleEditableInput(event, editable) { + function handleEditableInput (event, editable) { var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]'); if (textarea) { textarea.value = editable.innerHTML.trim(); @@ -220,7 +220,7 @@ // Internal helper methods which shouldn't be exposed externally - function addToEditors(win) { + function addToEditors (win) { if (!win._mediumEditors) { // To avoid breaking users who are assuming that the unique id on // medium-editor elements will start at 1, inserting a 'null' in the @@ -236,7 +236,7 @@ win._mediumEditors[this.id] = this; } - function removeFromEditors(win) { + function removeFromEditors (win) { if (!win._mediumEditors || !win._mediumEditors[this.id]) { return; } @@ -251,7 +251,7 @@ win._mediumEditors[this.id] = null; } - function createElementsArray(selector, doc, filterEditorElements) { + function createElementsArray (selector, doc, filterEditorElements) { var elements = []; if (!selector) { @@ -285,7 +285,7 @@ return elements; } - function cleanupTextareaElement(element) { + function cleanupTextareaElement (element) { var textarea = element.parentNode.querySelector('textarea[medium-editor-textarea-id="' + element.getAttribute('medium-editor-textarea-id') + '"]'); if (textarea) { // Un-hide the textarea @@ -297,7 +297,7 @@ } } - function setExtensionDefaults(extension, defaults) { + function setExtensionDefaults (extension, defaults) { Object.keys(defaults).forEach(function (prop) { if (extension[prop] === undefined) { extension[prop] = defaults[prop]; @@ -306,7 +306,7 @@ return extension; } - function initExtension(extension, name, instance) { + function initExtension (extension, name, instance) { var extensionDefaults = { 'window': instance.options.contentWindow, 'document': instance.options.ownerDocument, @@ -328,19 +328,19 @@ return extension; } - function isToolbarEnabled() { + function isToolbarEnabled () { // If any of the elements don't have the toolbar disabled // We need a toolbar if (this.elements.every(function (element) { - return !!element.getAttribute('data-disable-toolbar'); - })) { + return !!element.getAttribute('data-disable-toolbar'); + })) { return false; } return this.options.toolbar !== false; } - function isAnchorPreviewEnabled() { + function isAnchorPreviewEnabled () { // If toolbar is disabled, don't add if (!isToolbarEnabled.call(this)) { return false; @@ -349,34 +349,34 @@ return this.options.anchorPreview !== false; } - function isPlaceholderEnabled() { + function isPlaceholderEnabled () { return this.options.placeholder !== false; } - function isAutoLinkEnabled() { + function isAutoLinkEnabled () { return this.options.autoLink !== false; } - function isImageDraggingEnabled() { + function isImageDraggingEnabled () { return this.options.imageDragging !== false; } - function isKeyboardCommandsEnabled() { + function isKeyboardCommandsEnabled () { return this.options.keyboardCommands !== false; } - function shouldUseFileDraggingExtension() { + function shouldUseFileDraggingExtension () { // Since the file-dragging extension replaces the image-dragging extension, - // we need to check if the user passed an overrided image-dragging extension. + // we need to check if the user passed an overridden image-dragging extension. // If they have, to avoid breaking users, we won't use file-dragging extension. return !this.options.extensions['imageDragging']; } - function createContentEditable(textarea) { + function createContentEditable (textarea) { var div = this.options.ownerDocument.createElement('div'), now = Date.now(), uniqueId = 'medium-editor-' + now, - atts = textarea.attributes; + attributes = textarea.attributes; // Some browsers can move pretty fast, since we're using a timestamp // to make a unique-id, ensure that the id is actually unique on the page @@ -391,11 +391,11 @@ textarea.setAttribute('medium-editor-textarea-id', uniqueId); - // re-create all attributes from the textearea to the new created div - for (var i = 0, n = atts.length; i < n; i++) { + // re-create all attributes from the textarea to the new created div + for (var i = 0, n = attributes.length; i < n; i++) { // do not re-create existing attributes - if (!div.hasAttribute(atts[i].nodeName)) { - div.setAttribute(atts[i].nodeName, atts[i].value); + if (!div.hasAttribute(attributes[i].nodeName)) { + div.setAttribute(attributes[i].nodeName, attributes[i].value); } } @@ -420,7 +420,7 @@ var initialContent = {}; - function initElement(element, editorId) { + function initElement (element, editorId) { if (!element.getAttribute('data-medium-editor-element')) { if (element.nodeName.toLowerCase() === 'textarea') { element = createContentEditable.call(this, element); @@ -470,7 +470,7 @@ return element; } - function attachHandlers() { + function attachHandlers () { // attach to tabs this.subscribe('editableKeydownTab', handleTabKeydown.bind(this)); @@ -493,7 +493,7 @@ } } - function initExtensions() { + function initExtensions () { this.extensions = []; @@ -505,7 +505,7 @@ } }, this); - // 4 Cases for imageDragging + fileDragging extensons: + // 4 Cases for imageDragging + fileDragging extensions: // // 1. ImageDragging ON + No Custom Image Dragging Extension: // * Use fileDragging extension (default options) @@ -548,7 +548,7 @@ // just create the default toolbar var toolbarExtension = this.options.extensions['toolbar']; if (!toolbarExtension && isToolbarEnabled.call(this)) { - // Backwards compatability + // Backwards compatibility var toolbarOptions = MediumEditor.util.extend({}, this.options.toolbar, { allowMultiParagraphSelection: this.options.allowMultiParagraphSelection // deprecated }); @@ -562,7 +562,7 @@ } } - function mergeOptions(defaults, options) { + function mergeOptions (defaults, options) { var deprecatedProperties = [ ['allowMultiParagraphSelection', 'toolbar.allowMultiParagraphSelection'] ]; @@ -578,7 +578,7 @@ return MediumEditor.util.defaults({}, options, defaults); } - function execActionInternal(action, opts) { + function execActionInternal (action, opts) { /*jslint regexp: true*/ var appendAction = /^append-(.+)$/gi, justifyAction = /justify([A-Za-z]*)$/g, /* Detecting if is justifyCenter|Right|Left */ @@ -645,7 +645,7 @@ * Chrome may have removed
    elements and instead wrapped lines in
    elements * with a text-align property. If so, we want to fix this */ - function cleanupJustifyDivFragments(blockContainer) { + function cleanupJustifyDivFragments (blockContainer) { if (!blockContainer) { return; } @@ -687,7 +687,7 @@ } MediumEditor.prototype = { - // NOT DOCUMENTED - exposed for backwards compatability + // NOT DOCUMENTED - exposed for backwards compatibility init: function (elements, options) { this.options = mergeOptions.call(this, this.defaults, options); this.origElements = elements; @@ -1100,7 +1100,7 @@ // since we are going to create a link from an extracted text, // be sure that if we are updating a link, we won't let an empty link behind (see #754) - // (Workaroung for Chrome) + // (Workaround for Chrome) this.execAction('unlink'); exportedSelection = this.exportSelection(); @@ -1111,7 +1111,7 @@ // as our reference inside this.elements gets detached from the page when insertHTML runs. // If we just use [parentElement, 0] and [parentElement, parentElement.childNodes.length] // as the range boundaries, this happens whenever parentElement === currentEditor. - // The tradeoff to this workaround is that a orphaned tag can sometimes be left behind at + // The trade-off to this workaround is that a orphaned tag can sometimes be left behind at // the end of the editor's content. // In Gecko: // as an empty if parentElement.lastChild is a tag. @@ -1123,7 +1123,7 @@ 0, parentElement.lastChild, parentElement.lastChild.nodeType === 3 ? - parentElement.lastChild.nodeValue.length : parentElement.lastChild.childNodes.length + parentElement.lastChild.nodeValue.length : parentElement.lastChild.childNodes.length ); } else { MediumEditor.selection.select( diff --git a/src/js/events.js b/src/js/events.js index a5f7f1c7c..8c1083495 100644 --- a/src/js/events.js +++ b/src/js/events.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function isElementDescendantOfExtension(extensions, element) { + function isElementDescendantOfExtension (extensions, element) { if (!extensions) { return false; } @@ -226,17 +226,17 @@ // Helper method to call all listeners to execCommand var callListeners = function (args, result) { - if (doc.execCommand.listeners) { - doc.execCommand.listeners.forEach(function (listener) { - listener({ - command: args[0], - value: args[2], - args: args, - result: result + if (doc.execCommand.listeners) { + doc.execCommand.listeners.forEach(function (listener) { + listener({ + command: args[0], + value: args[2], + args: args, + result: result + }); }); - }); - } - }, + } + }, // Create a wrapper method for execCommand which will: // 1) Call document.execCommand with the correct arguments @@ -466,7 +466,7 @@ if (!this.contentCache) { return; } - // An event triggered which signifies that the user may have changed someting + // An event triggered which signifies that the user may have changed something // Look in our cache of input for the contenteditables to see if something changed var index = target.getAttribute('medium-editor-index'), html = target.innerHTML; diff --git a/src/js/extension.js b/src/js/extension.js index 0872a3f10..262538435 100644 --- a/src/js/extension.js +++ b/src/js/extension.js @@ -73,11 +73,12 @@ * current instance of MediumEditor when this is called. * All helper methods will exist as well */ - init: function () {}, + init: function () { + }, /* base: [MediumEditor instance] * - * If not overriden, this will be set to the current instance + * If not overridden, this will be set to the current instance * of MediumEditor, before the init method is called */ base: undefined, @@ -167,7 +168,7 @@ /* setInactive: [function ()] * * If implemented, this function is called when MediumEditor knows - * that this extension is currently disabled. Curently, this + * that this extension is currently disabled. Currently, this * is called at the beginning of each state change for * the editor & toolbar. After calling this, MediumEditor * will attempt to update the extension, either via checkState() @@ -198,7 +199,7 @@ /* window: [Window] * - * If not overriden, this will be set to the window object + * If not overridden, this will be set to the window object * to be used by MediumEditor and its extensions. This is * passed via the 'contentWindow' option to MediumEditor * and is the global 'window' object by default @@ -207,7 +208,7 @@ /* document: [Document] * - * If not overriden, this will be set to the document object + * If not overridden, this will be set to the document object * to be used by MediumEditor and its extensions. This is * passed via the 'ownerDocument' optin to MediumEditor * and is the global 'document' object by default diff --git a/src/js/extensions/README.md b/src/js/extensions/README.md index 82ef2ef9a..0709520af 100644 --- a/src/js/extensions/README.md +++ b/src/js/extensions/README.md @@ -294,7 +294,7 @@ Currently, this method is called when updating the editor & toolbar, and if `que *** ### `setInactive()` -If implemented, this method is called when MediumEditor knows that this extension has not been applied to the current selection. Curently, this is called at the beginning of each state change for the editor & toolbar. +If implemented, this method is called when MediumEditor knows that this extension has not been applied to the current selection. Currently, this is called at the beginning of each state change for the editor & toolbar. After calling this, MediumEditor will attempt to update the extension, either via `checkState()` or the combination of `queryCommandState()`, `isAlreadyApplied(node)`, `isActive()`, and `setActive()` @@ -544,7 +544,7 @@ The `getButton()` method on each button will be called and appended to the toolb *** ## Button Helpers -The following are properties and methods of the built-in button extension implementation (`MediumEditor.extensions.button`) that can be reused and/or overriden to make custom button extensions easier to create. +The following are properties and methods of the built-in button extension implementation (`MediumEditor.extensions.button`) that can be reused and/or overridden to make custom button extensions easier to create. ### `action` _(string)_ diff --git a/src/js/extensions/WALKTHROUGH-BUTTON.md b/src/js/extensions/WALKTHROUGH-BUTTON.md index 94df33d26..ea2418a5c 100644 --- a/src/js/extensions/WALKTHROUGH-BUTTON.md +++ b/src/js/extensions/WALKTHROUGH-BUTTON.md @@ -118,7 +118,7 @@ var editor = new MediumEditor('.editable', { **PREVIEW:**

    -To change the apperances, we have: +To change the appearances, we have: 1. Changed the `innerHTML` of our button to be `` in the `init()` method 1. Added 'Highlight' as a title attribute to enable the tooltip @@ -181,7 +181,7 @@ After highlighting the text and clicking the button, the text now appears highli **NOTE:** -A great convienience of using the `toggleSelection()` method of the **CSS Class Applier** is that it will also unwrap the selection. So, since we're always calling `toggleSelection()` when the button is clicked, if you highlight the same text and click the button again, the text will go back to normal and the `` element will be removed. +A great convenience of using the `toggleSelection()` method of the **CSS Class Applier** is that it will also unwrap the selection. So, since we're always calling `toggleSelection()` when the button is clicked, if you highlight the same text and click the button again, the text will go back to normal and the `` element will be removed. **NOTE:** @@ -249,7 +249,7 @@ var HighlighterButton = MediumEditor.Extension.extend({ As shown above, now our button responds to what the user has selected. To make this final piece work, we've implemented 4 extension methods: 1. **isAlreadyApplied(node)** - * This will be called on each element which contains the user's selection, starting with the lowest element and climbing its ancestors. If any of these elements are a `` element, we return `true` since that means the selection is higlighted. + * This will be called on each element which contains the user's selection, starting with the lowest element and climbing its ancestors. If any of these elements are a `` element, we return `true` since that means the selection is highlighted. 1. **isActive()** * This should return whether the button is already active. We check this by seeing if the `'medium-editor-button-active'` class already exists on the toolbar button. 1. **setActive()** diff --git a/src/js/extensions/anchor-preview.js b/src/js/extensions/anchor-preview.js index 8963f68ba..06e99b7be 100644 --- a/src/js/extensions/anchor-preview.js +++ b/src/js/extensions/anchor-preview.js @@ -79,7 +79,7 @@ showPreview: function (anchorEl) { if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active') || - anchorEl.getAttribute('data-disable-preview')) { + anchorEl.getAttribute('data-disable-preview')) { return true; } diff --git a/src/js/extensions/anchor.js b/src/js/extensions/anchor.js index 86fd7f3d0..b550dda73 100644 --- a/src/js/extensions/anchor.js +++ b/src/js/extensions/anchor.js @@ -107,7 +107,7 @@ if (this.targetCheckbox) { // fixme: ideally, this targetCheckboxText would be a formLabel too, - // figure out how to deprecate? also consider `fa-` icon default implcations. + // figure out how to deprecate? also consider `fa-` icon default implications. template.push( '
    ', '', @@ -151,7 +151,7 @@ buttonCheckbox = this.getAnchorButtonCheckbox(); opts = opts || { value: '' }; - // TODO: This is for backwards compatability + // TODO: This is for backwards compatibility // We don't need to support the 'string' argument in 6.0.0 if (typeof opts === 'string') { opts = { @@ -256,11 +256,11 @@ // Matches protocol relative "//" // Matches common external protocols "mailto:" "tel:" "maps:" // Matches relative hash link, begins with "#" - var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^\#/i, + var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^#/i, hasScheme = urlSchemeRegex.test(value), scheme = '', // telRegex is a regex for checking if the string is a telephone number - telRegex = /^\+?\s?\(?(?:\d\s?\-?\)?){3,20}$/, + telRegex = /^\+?\s?\(?(?:\d\s?-?\)?){3,20}$/, urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/), path = urlParts[1], query = urlParts[2], @@ -273,7 +273,7 @@ if (!hasScheme) { var host = path.split('/')[0]; // if the host part of the path looks like a hostname - if (host.match(/.+(\.|:).+/) || host === 'localhost') { + if (host.match(/.+([.:]).+/) || host === 'localhost') { scheme = 'http://'; } } diff --git a/src/js/extensions/auto-link.js b/src/js/extensions/auto-link.js index 1f2e8f316..0db65b24a 100644 --- a/src/js/extensions/auto-link.js +++ b/src/js/extensions/auto-link.js @@ -8,7 +8,7 @@ LINK_REGEXP; WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003', - '\u2028', '\u2029']; + '\u2028', '\u2029']; KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' + 'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' + 'bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|' + @@ -29,7 +29,7 @@ LINK_REGEXP = new RegExp(LINK_REGEXP_TEXT, 'gi'); - function nodeIsNotInsideAnchorTag(node) { + function nodeIsNotInsideAnchorTag (node) { return !MediumEditor.util.getClosestTag(node, 'a'); } @@ -188,7 +188,7 @@ for (var matchIndex = 0; matchIndex < matches.length; matchIndex++) { var matchingTextNodes = MediumEditor.util.findOrCreateMatchingTextNodes(this.document, element, - matches[matchIndex]); + matches[matchIndex]); if (this.shouldNotLink(matchingTextNodes)) { continue; } diff --git a/src/js/extensions/file-dragging.js b/src/js/extensions/file-dragging.js index 9efc5a751..ddbb068c6 100644 --- a/src/js/extensions/file-dragging.js +++ b/src/js/extensions/file-dragging.js @@ -3,7 +3,7 @@ var CLASS_DRAG_OVER = 'medium-editor-dragover'; - function clearClassNames(element) { + function clearClassNames (element) { var editable = MediumEditor.util.getContainerEditorElement(element), existing = Array.prototype.slice.call(editable.parentElement.querySelectorAll('.' + CLASS_DRAG_OVER)); diff --git a/src/js/extensions/fontname.js b/src/js/extensions/fontname.js index df54d73a2..e8bd9701c 100644 --- a/src/js/extensions/fontname.js +++ b/src/js/extensions/fontname.js @@ -103,7 +103,7 @@ this.on(form, 'click', this.handleFormClick.bind(this)); // Add font names - for (var i = 0; i' : - '✓'; + '' : + '✓'; form.appendChild(save); // Handle save button clicks (capture) @@ -129,10 +129,10 @@ // Add close button close.setAttribute('href', '#'); - close.className = 'medium-editor-toobar-close'; + close.className = 'medium-editor-toolbar-close'; close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; + '' : + '×'; form.appendChild(close); // Handle close button clicks diff --git a/src/js/extensions/fontsize.js b/src/js/extensions/fontsize.js index 3d3247e35..6641dc6f7 100644 --- a/src/js/extensions/fontsize.js +++ b/src/js/extensions/fontsize.js @@ -109,12 +109,12 @@ // Handle typing in the textbox this.on(input, 'change', this.handleSliderChange.bind(this)); - // Add save buton + // Add save button save.setAttribute('href', '#'); - save.className = 'medium-editor-toobar-save'; + save.className = 'medium-editor-toolbar-save'; save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '✓'; + '' : + '✓'; form.appendChild(save); // Handle save button clicks (capture) @@ -122,10 +122,10 @@ // Add close button close.setAttribute('href', '#'); - close.className = 'medium-editor-toobar-close'; + close.className = 'medium-editor-toolbar-close'; close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; + '' : + '×'; form.appendChild(close); // Handle close button clicks diff --git a/src/js/extensions/keyboard-commands.js b/src/js/extensions/keyboard-commands.js index dd214c714..ee8fffa56 100644 --- a/src/js/extensions/keyboard-commands.js +++ b/src/js/extensions/keyboard-commands.js @@ -67,16 +67,15 @@ if (data.meta === isMeta && data.shift === isShift && (data.alt === isAlt || - undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0 + undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0 event.preventDefault(); event.stopPropagation(); // command can be a function to execute if (typeof data.command === 'function') { data.command.apply(this); - } - // command can be false so the shortcut is just disabled - else if (false !== data.command) { + } else if (false !== data.command) { + // command can be false so the shortcut is just disabled this.execAction(data.command); } } diff --git a/src/js/extensions/paste.js b/src/js/extensions/paste.js index c50f91088..c350f1fef 100644 --- a/src/js/extensions/paste.js +++ b/src/js/extensions/paste.js @@ -16,7 +16,7 @@ // will not match any unicode characters. In the regexes in this // block, negation is used specifically to match the end of an html // tag, and in fact unicode characters *should* be allowed. - function createReplacements() { + function createReplacements () { return [ // Remove anything but the contents within the BODY element [new RegExp(/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g), ''], @@ -45,7 +45,7 @@ [new RegExp(/]*font-weight:(bold|700)[^>]*>/gi), ''], // replace manually entered b/i/a tags with real ones - [new RegExp(/<(\/?)(i|b|a)>/gi), '<$1$2>'], + [new RegExp(/<(\/?)([iba])>/gi), '<$1$2>'], // replace manually a tags with real ones, converting smart-quotes from google docs [new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi), ''], @@ -59,7 +59,7 @@ [new RegExp(/<\/?o:[a-z]*>/gi), ''], // Microsoft Word adds some special elements around list items - [new RegExp(/(((?!/gi), '$1'] + [new RegExp(/(((?!/gi), '$1'] ]; } @@ -74,7 +74,7 @@ * @param {doc} reference to document * @return {Object} Object with mime types and data for those mime types. */ - function getClipboardContent(event, win, doc) { + function getClipboardContent (event, win, doc) { var dataTransfer = event.clipboardData || win.clipboardData || doc.dataTransfer, data = {}; diff --git a/src/js/extensions/toolbar.js b/src/js/extensions/toolbar.js index ab94bab43..a7e16756e 100644 --- a/src/js/extensions/toolbar.js +++ b/src/js/extensions/toolbar.js @@ -154,7 +154,7 @@ } // If the button already exists as an extension, it'll be returned - // othwerise it'll create the default built-in button + // otherwise it'll create the default built-in button extension = this.base.addBuiltInExtension(buttonName, buttonOpts); if (extension && typeof extension.getButton === 'function') { @@ -251,8 +251,8 @@ handleDocumentMouseup: function (event) { // Do not trigger checkState when mouseup fires over the toolbar if (event && - event.target && - MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) { + event.target && + MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) { return false; } this.checkState(); @@ -344,7 +344,7 @@ // Responding to changes in user selection - // Checks for existance of multiple block elements in the current selection + // Checks for existence of multiple block elements in the current selection multipleBlockElementsSelected: function () { var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex regexBlockElements = new RegExp('<(' + MediumEditor.util.blockContainerElementNames.join('|') + ')[^>]*>', 'g'), @@ -375,8 +375,8 @@ * adjacent text node that actually has content in it, and move the selectionRange start there. */ if (this.standardizeSelectionStart && - selectionRange.startContainer.nodeValue && - (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) { + selectionRange.startContainer.nodeValue && + (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) { var adjacentNode = MediumEditor.util.findAdjacentTextNodeWithContent(MediumEditor.selection.getSelectionElement(this.window), selectionRange.startContainer, this.document); if (adjacentNode) { var offset = 0; @@ -397,7 +397,7 @@ // If no editable has focus OR selection is inside contenteditable = false // hide toolbar if (!this.base.getFocusedElement() || - MediumEditor.selection.selectionInContentEditableFalse(this.window)) { + MediumEditor.selection.selectionInContentEditableFalse(this.window)) { return this.hideToolbar(); } @@ -406,8 +406,8 @@ // hide toolbar var selectionElement = MediumEditor.selection.getSelectionElement(this.window); if (!selectionElement || - this.getEditorElements().indexOf(selectionElement) === -1 || - selectionElement.getAttribute('data-disable-toolbar')) { + this.getEditorElements().indexOf(selectionElement) === -1 || + selectionElement.getAttribute('data-disable-toolbar')) { return this.hideToolbar(); } @@ -457,8 +457,8 @@ if (typeof extension.checkState === 'function') { extension.checkState(parentNode); } else if (typeof extension.isActive === 'function' && - typeof extension.isAlreadyApplied === 'function' && - typeof extension.setActive === 'function') { + typeof extension.isAlreadyApplied === 'function' && + typeof extension.setActive === 'function') { if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) { extension.setActive(); } @@ -491,8 +491,8 @@ // Make sure the selection parent isn't outside of the contenteditable if (!this.getEditorElements().some(function (element) { - return MediumEditor.util.isDescendant(element, parentNode, true); - })) { + return MediumEditor.util.isDescendant(element, parentNode, true); + })) { return; } @@ -562,11 +562,11 @@ if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) { toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px'; toolbarElement.classList.remove('medium-editor-sticky-toolbar'); - // Stick the toolbar to the top of the window + // Stick the toolbar to the top of the window } else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) { toolbarElement.classList.add('medium-editor-sticky-toolbar'); toolbarElement.style.top = this.stickyTopOffset + 'px'; - // Normal static toolbar position + // Normal static toolbar position } else { toolbarElement.classList.remove('medium-editor-sticky-toolbar'); toolbarElement.style.top = containerTop - toolbarHeight + 'px'; diff --git a/src/js/globals.js b/src/js/globals.js index a69822e13..28bf47df2 100644 --- a/src/js/globals.js +++ b/src/js/globals.js @@ -1,5 +1,5 @@ /*jshint unused: false */ -function MediumEditor(elements, options) { +function MediumEditor (elements, options) { 'use strict'; return this.init(elements, options); } diff --git a/src/js/polyfills.js b/src/js/polyfills.js index 43b830433..c743e6646 100644 --- a/src/js/polyfills.js +++ b/src/js/polyfills.js @@ -191,7 +191,7 @@ if (!("classList" in document.createElement("_"))) { */ /*global self, unescape */ -/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, +/*jslint bitwise: true, regexp: true, confusion: true, vars: true, white: true, plusplus: true */ /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ diff --git a/src/js/selection.js b/src/js/selection.js index 34deaac15..eb936bb56 100644 --- a/src/js/selection.js +++ b/src/js/selection.js @@ -1,7 +1,7 @@ (function () { 'use strict'; - function filterOnlyParentElements(node) { + function filterOnlyParentElements (node) { if (MediumEditor.util.isBlockContainer(node)) { return NodeFilter.FILTER_ACCEPT; } else { @@ -145,12 +145,11 @@ if (allowRangeToStartAtEndOfNode || selectionState.start < nextCharIndex) { range.setStart(node, selectionState.start - charIndex); foundStart = true; - } - // We're at the end of a text node where the selection could start but we shouldn't - // make the selection start here because allowRangeToStartAtEndOfNode is false. - // However, we should keep a reference to this node in case there aren't any more - // text nodes after this, so that we have somewhere to import the selection to - else { + } else { + // We're at the end of a text node where the selection could start but we shouldn't + // make the selection start here because allowRangeToStartAtEndOfNode is false. + // However, we should keep a reference to this node in case there aren't any more + // text nodes after this, so that we have somewhere to import the selection to lastTextNode = node; } } @@ -222,9 +221,9 @@ return node.nodeName.toLowerCase() === 'a'; }; if (selectionState.start === selectionState.end && - range.startContainer.nodeType === 3 && - range.startOffset === range.startContainer.nodeValue.length && - MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { + range.startContainer.nodeType === 3 && + range.startOffset === range.startContainer.nodeValue.length && + MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { var prevNode = range.startContainer, currentNode = range.startContainer.parentNode; while (currentNode !== null && currentNode.nodeName.toLowerCase() !== 'a') { @@ -302,7 +301,7 @@ }, // Returns -1 unless the cursor is at the beginning of a paragraph/block - // If the paragraph/block is preceeded by empty paragraphs/block (with no text) + // If the paragraph/block is preceded by empty paragraphs/block (with no text) // it will return the number of empty paragraphs before the cursor. // Otherwise, it will return 0, which indicates the cursor is at the beginning // of a paragraph/block, and not at the end of the paragraph/block before it @@ -327,9 +326,8 @@ // If there is no previous sibling, this is the first text element in the editor if (!previousSibling) { return -1; - } - // If the previous sibling has text, then there are no empty blocks before this - else if (previousSibling.nodeValue) { + } else if (previousSibling.nodeValue) { + // If the previous sibling has text, then there are no empty blocks before this return -1; } } @@ -379,7 +377,7 @@ if (next === img) { break; } - // If we haven't hit the iamge, but found text that contains content + // If we haven't hit the image, but found text that contains content // then the range doesn't start with an image if (next.nodeValue) { return false; @@ -464,7 +462,7 @@ selectionContainsContent: function (doc) { var sel = doc.getSelection(); - // collapsed selection or selection withour range doesn't contain content + // collapsed selection or selection without range doesn't contain content if (!sel || sel.isCollapsed || !sel.rangeCount) { return false; } @@ -505,7 +503,7 @@ // http://stackoverflow.com/questions/4176923/html-of-selected-text // by Tim Down - getSelectionHtml: function getSelectionHtml(doc) { + getSelectionHtml: function getSelectionHtml (doc) { var i, html = '', sel = doc.getSelection(), @@ -526,9 +524,9 @@ * * @param {DOMElement} An element containing the cursor to find offsets relative to. * @param {Range} A Range representing cursor position. Will window.getSelection if none is passed. - * @return {Object} 'left' and 'right' attributes contain offsets from begining and end of Element + * @return {Object} 'left' and 'right' attributes contain offsets from beginning and end of Element */ - getCaretOffsets: function getCaretOffsets(element, range) { + getCaretOffsets: function getCaretOffsets (element, range) { var preCaretRange, postCaretRange; if (!range) { diff --git a/src/js/util.js b/src/js/util.js index 13aba27e6..d1c9732cb 100644 --- a/src/js/util.js +++ b/src/js/util.js @@ -3,7 +3,7 @@ (function (window) { 'use strict'; - function copyInto(overwrite, dest) { + function copyInto (overwrite, dest) { var prop, sources = Array.prototype.slice.call(arguments, 2); dest = dest || {}; @@ -32,13 +32,14 @@ testText = document.createTextNode(' '); testParent.appendChild(testText); nodeContainsWorksWithTextNodes = testParent.contains(testText); - } catch (exc) {} + } catch (exc) { + } var Util = { // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562 // by rg89 - isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})').exec(navigator.userAgent) !== null))), + isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]+[.0-9]*)').exec(navigator.userAgent) !== null))), isEdge: (/Edge\/\d+/).exec(navigator.userAgent) !== null, @@ -67,11 +68,7 @@ * See #591 */ isMetaCtrlKey: function (event) { - if ((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)) { - return true; - } - - return false; + return !!((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)); }, /** @@ -88,11 +85,7 @@ return keyCode === keys; } - if (-1 === keys.indexOf(keyCode)) { - return false; - } - - return true; + return -1 !== keys.indexOf(keyCode); }, getKeyCode: function (event) { @@ -118,12 +111,12 @@ emptyElementNames: ['br', 'col', 'colgroup', 'hr', 'img', 'input', 'source', 'wbr'], - extend: function extend(/* dest, source1, source2, ...*/) { + extend: function extend (/* dest, source1, source2, ...*/) { var args = [true].concat(Array.prototype.slice.call(arguments)); return copyInto.apply(this, args); }, - defaults: function defaults(/*dest, source1, source2, ...*/) { + defaults: function defaults (/*dest, source1, source2, ...*/) { var args = [false].concat(Array.prototype.slice.call(arguments)); return copyInto.apply(this, args); }, @@ -227,12 +220,12 @@ var textIndexOfEndOfFarthestNode, endSplitPoint; textIndexOfEndOfFarthestNode = currentTextIndex + currentNode.nodeValue.length + - (newNode ? newNode.nodeValue.length : 0) - 1; + (newNode ? newNode.nodeValue.length : 0) - 1; endSplitPoint = matchEndIndex - currentTextIndex - - (newNode ? currentNode.nodeValue.length : 0); + (newNode ? currentNode.nodeValue.length : 0); if (textIndexOfEndOfFarthestNode >= matchEndIndex && - currentTextIndex !== textIndexOfEndOfFarthestNode && - endSplitPoint !== 0) { + currentTextIndex !== textIndexOfEndOfFarthestNode && + endSplitPoint !== 0) { (newNode || currentNode).splitText(endSplitPoint); } }, @@ -242,7 +235,7 @@ * 1) All text content of the elements are in separate blocks. No piece of text content should span * across multiple blocks. This means no element return by this function should have * any blocks as children. - * 2) The union of the textcontent of all of the elements returned here covers all + * 2) The union of the text content of all of the elements returned here covers all * of the text within the element. * * @@ -300,7 +293,7 @@ // - A descendant of a sibling element // - A sibling text node of an ancestor // - A descendant of a sibling element of an ancestor - findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent(rootNode, targetNode, ownerDocument) { + findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent (rootNode, targetNode, ownerDocument) { var pastTarget = false, nextNode, nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false); @@ -338,7 +331,7 @@ return previousSibling; }, - isDescendant: function isDescendant(parent, child, checkEquality) { + isDescendant: function isDescendant (parent, child, checkEquality) { if (!parent || !child) { return false; } @@ -363,7 +356,7 @@ }, // https://github.com/jashkenas/underscore - isElement: function isElement(obj) { + isElement: function isElement (obj) { return !!(obj && obj.nodeType === 1); }, @@ -455,7 +448,8 @@ if (!MediumEditor.util.isEdge && doc.queryCommandSupported('insertHTML')) { try { return doc.execCommand.apply(doc, ecArgs); - } catch (ignore) {} + } catch (ignore) { + } } selection = doc.getSelection(); @@ -469,13 +463,13 @@ if (Util.isMediumEditorElement(toReplace) && !toReplace.firstChild) { range.selectNode(toReplace.appendChild(doc.createTextNode(''))); } else if ((toReplace.nodeType === 3 && range.startOffset === 0 && range.endOffset === toReplace.nodeValue.length) || - (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) { + (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) { // Ensure range covers maximum amount of nodes as possible // By moving up the DOM and selecting ancestors whose only child is the range while (!Util.isMediumEditorElement(toReplace) && - toReplace.parentNode && - toReplace.parentNode.childNodes.length === 1 && - !Util.isMediumEditorElement(toReplace.parentNode)) { + toReplace.parentNode && + toReplace.parentNode.childNodes.length === 1 && + !Util.isMediumEditorElement(toReplace.parentNode)) { toReplace = toReplace.parentNode; } range.selectNode(toReplace); @@ -548,7 +542,7 @@ tagName = '<' + tagName + '>'; } - // When FF, IE and Edge, we have to handle blockquote node seperately as 'formatblock' does not work. + // When FF, IE and Edge, we have to handle blockquote node separately as 'formatblock' does not work. // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') { // For IE, just use outdent @@ -1148,7 +1142,7 @@ }, guid: function () { - function _s4() { + function _s4 () { return Math .floor((1 + Math.random()) * 0x10000) .toString(16) From 2c97e8b94ff1001220887e7b9d94895ba23af395 Mon Sep 17 00:00:00 2001 From: Ronie Martinez Date: Sat, 23 Nov 2019 12:26:04 +0800 Subject: [PATCH 3/4] Remove dist folder --- dist/css/medium-editor.css | 197 - dist/css/medium-editor.min.css | 1 - dist/css/themes/beagle.css | 79 - dist/css/themes/beagle.min.css | 1 - dist/css/themes/bootstrap.css | 66 - dist/css/themes/bootstrap.min.css | 1 - dist/css/themes/default.css | 59 - dist/css/themes/default.min.css | 1 - dist/css/themes/flat.css | 57 - dist/css/themes/flat.min.css | 1 - dist/css/themes/mani.css | 54 - dist/css/themes/mani.min.css | 1 - dist/css/themes/roman.css | 55 - dist/css/themes/roman.min.css | 1 - dist/css/themes/tim.css | 66 - dist/css/themes/tim.min.css | 1 - dist/js/medium-editor.js | 7976 ----------------------------- dist/js/medium-editor.min.js | 1 - 18 files changed, 8618 deletions(-) delete mode 100644 dist/css/medium-editor.css delete mode 100644 dist/css/medium-editor.min.css delete mode 100644 dist/css/themes/beagle.css delete mode 100644 dist/css/themes/beagle.min.css delete mode 100644 dist/css/themes/bootstrap.css delete mode 100644 dist/css/themes/bootstrap.min.css delete mode 100644 dist/css/themes/default.css delete mode 100644 dist/css/themes/default.min.css delete mode 100644 dist/css/themes/flat.css delete mode 100644 dist/css/themes/flat.min.css delete mode 100644 dist/css/themes/mani.css delete mode 100644 dist/css/themes/mani.min.css delete mode 100644 dist/css/themes/roman.css delete mode 100644 dist/css/themes/roman.min.css delete mode 100644 dist/css/themes/tim.css delete mode 100644 dist/css/themes/tim.min.css delete mode 100644 dist/js/medium-editor.js delete mode 100644 dist/js/medium-editor.min.js diff --git a/dist/css/medium-editor.css b/dist/css/medium-editor.css deleted file mode 100644 index a1c058acf..000000000 --- a/dist/css/medium-editor.css +++ /dev/null @@ -1,197 +0,0 @@ -@keyframes medium-editor-image-loading { - 0% { - transform: scale(0); } - 100% { - transform: scale(1); } } - -@keyframes medium-editor-pop-upwards { - 0% { - opacity: 0; - transform: matrix(0.97, 0, 0, 1, 0, 12); } - 20% { - opacity: .7; - transform: matrix(0.99, 0, 0, 1, 0, 2); } - 40% { - opacity: 1; - transform: matrix(1, 0, 0, 1, 0, -1); } - 100% { - transform: matrix(1, 0, 0, 1, 0, 0); } } - -.medium-editor-anchor-preview { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - left: 0; - line-height: 1.4; - max-width: 280px; - position: absolute; - text-align: center; - top: 0; - visibility: hidden; - word-break: break-all; - word-wrap: break-word; - z-index: 2000; } - .medium-editor-anchor-preview a { - color: #fff; - display: inline-block; - margin: 5px 5px 10px; } - -.medium-editor-anchor-preview-active { - visibility: visible; } - -.medium-editor-dragover { - background: #ddd; } - -.medium-editor-image-loading { - animation: medium-editor-image-loading 1s infinite ease-in-out; - background-color: #333; - border-radius: 100%; - display: inline-block; - height: 40px; - width: 40px; } - -.medium-editor-placeholder { - position: relative; } - .medium-editor-placeholder:after { - content: attr(data-placeholder) !important; - font-style: italic; - left: 0; - margin: inherit; - padding: inherit; - position: absolute; - top: 0; - white-space: pre; } - -.medium-editor-placeholder-relative { - position: relative; } - .medium-editor-placeholder-relative:after { - content: attr(data-placeholder) !important; - font-style: italic; - margin: inherit; - padding: inherit; - position: relative; - white-space: pre; } - -.medium-toolbar-arrow-under:after, .medium-toolbar-arrow-over:before { - border-style: solid; - content: ''; - display: block; - height: 0; - left: 50%; - margin-left: -8px; - position: absolute; - width: 0; } - -.medium-toolbar-arrow-under:after { - border-width: 8px 8px 0 8px; } - -.medium-toolbar-arrow-over:before { - border-width: 0 8px 8px 8px; - top: -8px; } - -.medium-editor-toolbar { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - left: 0; - position: absolute; - top: 0; - visibility: hidden; - z-index: 2000; } - .medium-editor-toolbar ul { - margin: 0; - padding: 0; } - .medium-editor-toolbar li { - float: left; - list-style: none; - margin: 0; - padding: 0; } - .medium-editor-toolbar li button { - box-sizing: border-box; - cursor: pointer; - display: block; - font-size: 14px; - line-height: 1.33; - margin: 0; - padding: 15px; - text-decoration: none; } - .medium-editor-toolbar li button:focus { - outline: none; } - .medium-editor-toolbar li .medium-editor-action-underline { - text-decoration: underline; } - .medium-editor-toolbar li .medium-editor-action-pre { - font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace; - font-size: 12px; - font-weight: 100; - padding: 15px 0; } - -.medium-editor-toolbar-active { - visibility: visible; } - -.medium-editor-sticky-toolbar { - position: fixed; - top: 1px; } - -.medium-editor-relative-toolbar { - position: relative; } - -.medium-editor-toolbar-active.medium-editor-stalker-toolbar { - animation: medium-editor-pop-upwards 160ms forwards linear; } - -.medium-editor-action-bold { - font-weight: bolder; } - -.medium-editor-action-italic { - font-style: italic; } - -.medium-editor-toolbar-form { - display: none; } - .medium-editor-toolbar-form input, - .medium-editor-toolbar-form a { - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } - .medium-editor-toolbar-form .medium-editor-toolbar-form-row { - line-height: 14px; - margin-left: 5px; - padding-bottom: 5px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input, - .medium-editor-toolbar-form label { - border: none; - box-sizing: border-box; - display: inline-block; - font-size: 14px; - margin: 0; - padding: 6px; - width: 316px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input:focus, - .medium-editor-toolbar-form label:focus { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; - border: none; - box-shadow: none; - outline: 0; } - .medium-editor-toolbar-form a { - display: inline-block; - font-size: 24px; - font-weight: bolder; - margin: 0 10px; - text-decoration: none; } - -.medium-editor-toolbar-form-active { - display: block; } - -.medium-editor-toolbar-actions:after { - clear: both; - content: ""; - display: table; } - -.medium-editor-element { - min-height: 30px; - word-wrap: break-word; } - .medium-editor-element img { - max-width: 100%; } - .medium-editor-element sub { - vertical-align: sub; } - .medium-editor-element sup { - vertical-align: super; } - -.medium-editor-hidden { - display: none; } diff --git a/dist/css/medium-editor.min.css b/dist/css/medium-editor.min.css deleted file mode 100644 index 697afe214..000000000 --- a/dist/css/medium-editor.min.css +++ /dev/null @@ -1 +0,0 @@ -@keyframes medium-editor-image-loading{0%{transform:scale(0)}100%{transform:scale(1)}}@keyframes medium-editor-pop-upwards{0%{opacity:0;transform:matrix(.97,0,0,1,0,12)}20%{opacity:.7;transform:matrix(.99,0,0,1,0,2)}40%{opacity:1;transform:matrix(1,0,0,1,0,-1)}100%{transform:matrix(1,0,0,1,0,0)}}.medium-editor-anchor-preview{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;left:0;line-height:1.4;max-width:280px;position:absolute;text-align:center;top:0;visibility:hidden;word-break:break-all;word-wrap:break-word;z-index:2000}.medium-editor-anchor-preview a{color:#fff;display:inline-block;margin:5px 5px 10px}.medium-editor-anchor-preview-active{visibility:visible}.medium-editor-dragover{background:#ddd}.medium-editor-image-loading{animation:medium-editor-image-loading 1s infinite ease-in-out;background-color:#333;border-radius:100%;display:inline-block;height:40px;width:40px}.medium-editor-placeholder{position:relative}.medium-editor-placeholder:after{content:attr(data-placeholder)!important;font-style:italic;left:0;margin:inherit;padding:inherit;position:absolute;top:0;white-space:pre}.medium-editor-placeholder-relative{position:relative}.medium-editor-placeholder-relative:after{content:attr(data-placeholder)!important;font-style:italic;margin:inherit;padding:inherit;position:relative;white-space:pre}.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{border-style:solid;content:'';display:block;height:0;left:50%;margin-left:-8px;position:absolute;width:0}.medium-toolbar-arrow-under:after{border-width:8px 8px 0 8px}.medium-toolbar-arrow-over:before{border-width:0 8px 8px 8px;top:-8px}.medium-editor-toolbar{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:16px;left:0;position:absolute;top:0;visibility:hidden;z-index:2000}.medium-editor-toolbar ul{margin:0;padding:0}.medium-editor-toolbar li{float:left;list-style:none;margin:0;padding:0}.medium-editor-toolbar li button{box-sizing:border-box;cursor:pointer;display:block;font-size:14px;line-height:1.33;margin:0;padding:15px;text-decoration:none}.medium-editor-toolbar li button:focus{outline:0}.medium-editor-toolbar li .medium-editor-action-underline{text-decoration:underline}.medium-editor-toolbar li .medium-editor-action-pre{font-family:Consolas,"Liberation Mono",Menlo,Courier,monospace;font-size:12px;font-weight:100;padding:15px 0}.medium-editor-toolbar-active{visibility:visible}.medium-editor-sticky-toolbar{position:fixed;top:1px}.medium-editor-relative-toolbar{position:relative}.medium-editor-toolbar-active.medium-editor-stalker-toolbar{animation:medium-editor-pop-upwards 160ms forwards linear}.medium-editor-action-bold{font-weight:bolder}.medium-editor-action-italic{font-style:italic}.medium-editor-toolbar-form{display:none}.medium-editor-toolbar-form a,.medium-editor-toolbar-form input{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}.medium-editor-toolbar-form .medium-editor-toolbar-form-row{line-height:14px;margin-left:5px;padding-bottom:5px}.medium-editor-toolbar-form .medium-editor-toolbar-input,.medium-editor-toolbar-form label{border:none;box-sizing:border-box;display:inline-block;font-size:14px;margin:0;padding:6px;width:316px}.medium-editor-toolbar-form .medium-editor-toolbar-input:focus,.medium-editor-toolbar-form label:focus{-webkit-appearance:none;-moz-appearance:none;appearance:none;border:none;box-shadow:none;outline:0}.medium-editor-toolbar-form a{display:inline-block;font-size:24px;font-weight:bolder;margin:0 10px;text-decoration:none}.medium-editor-toolbar-form-active{display:block}.medium-editor-toolbar-actions:after{clear:both;content:"";display:table}.medium-editor-element{min-height:30px;word-wrap:break-word}.medium-editor-element img{max-width:100%}.medium-editor-element sub{vertical-align:sub}.medium-editor-element sup{vertical-align:super}.medium-editor-hidden{display:none} \ No newline at end of file diff --git a/dist/css/themes/beagle.css b/dist/css/themes/beagle.css deleted file mode 100644 index 1135bf85c..000000000 --- a/dist/css/themes/beagle.css +++ /dev/null @@ -1,79 +0,0 @@ -.medium-toolbar-arrow-under:after { - border-color: #000 transparent transparent transparent; - top: 40px; } - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #000 transparent; } - -.medium-editor-toolbar { - background-color: #000; - border: none; - border-radius: 50px; } - .medium-editor-toolbar li button { - background-color: transparent; - border: none; - box-sizing: border-box; - color: #ccc; - height: 40px; - min-width: 40px; - padding: 5px 12px; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #000; - color: #a2d7c7; } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 50px; - border-top-left-radius: 50px; - padding-left: 24px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 50px; - border-right: none; - border-top-right-radius: 50px; - padding-right: 24px; } - .medium-editor-toolbar li .medium-editor-button-active { - background-color: #000; - color: #a2d7c7; } - -.medium-editor-toolbar-form { - background: #000; - border-radius: 50px; - color: #ccc; - overflow: hidden; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #000; - box-sizing: border-box; - color: #ccc; - height: 40px; - padding-left: 16px; - width: 220px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { - color: #f8f5f3; - color: rgba(248, 245, 243, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder { - /* Firefox 18- */ - color: #f8f5f3; - color: rgba(248, 245, 243, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder { - /* Firefox 19+ */ - color: #f8f5f3; - color: rgba(248, 245, 243, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder { - color: #f8f5f3; - color: rgba(248, 245, 243, 0.8); } - .medium-editor-toolbar-form a { - color: #ccc; - transform: translateY(2px); } - .medium-editor-toolbar-form .medium-editor-toolbar-close { - margin-right: 16px; } - -.medium-editor-toolbar-anchor-preview { - background: #000; - border-radius: 50px; - padding: 5px 12px; } - -.medium-editor-anchor-preview a { - color: #ccc; - text-decoration: none; } - -.medium-editor-toolbar-actions li, .medium-editor-toolbar-actions button { - border-radius: 50px; } diff --git a/dist/css/themes/beagle.min.css b/dist/css/themes/beagle.min.css deleted file mode 100644 index 86489ff7f..000000000 --- a/dist/css/themes/beagle.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-under:after{border-color:#000 transparent transparent transparent;top:40px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #000 transparent}.medium-editor-toolbar{background-color:#000;border:none;border-radius:50px}.medium-editor-toolbar li button{background-color:transparent;border:none;box-sizing:border-box;color:#ccc;height:40px;min-width:40px;padding:5px 12px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#000;color:#a2d7c7}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:50px;border-top-left-radius:50px;padding-left:24px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:50px;border-right:none;border-top-right-radius:50px;padding-right:24px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#000;color:#a2d7c7}.medium-editor-toolbar-form{background:#000;border-radius:50px;color:#ccc;overflow:hidden}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#000;box-sizing:border-box;color:#ccc;height:40px;padding-left:16px;width:220px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#f8f5f3;color:rgba(248,245,243,.8)}.medium-editor-toolbar-form a{color:#ccc;transform:translateY(2px)}.medium-editor-toolbar-form .medium-editor-toolbar-close{margin-right:16px}.medium-editor-toolbar-anchor-preview{background:#000;border-radius:50px;padding:5px 12px}.medium-editor-anchor-preview a{color:#ccc;text-decoration:none}.medium-editor-toolbar-actions button,.medium-editor-toolbar-actions li{border-radius:50px} \ No newline at end of file diff --git a/dist/css/themes/bootstrap.css b/dist/css/themes/bootstrap.css deleted file mode 100644 index 929f1faaa..000000000 --- a/dist/css/themes/bootstrap.css +++ /dev/null @@ -1,66 +0,0 @@ -.medium-toolbar-arrow-under:after { - border-color: #428bca transparent transparent transparent; - top: 60px; } - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #428bca transparent; } - -.medium-editor-toolbar { - background-color: #428bca; - border: 1px solid #357ebd; - border-radius: 4px; } - .medium-editor-toolbar li button { - background-color: transparent; - border: none; - border-right: 1px solid #357ebd; - box-sizing: border-box; - color: #fff; - height: 60px; - min-width: 60px; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #3276b1; - color: #fff; } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 4px; - border-top-left-radius: 4px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 4px; - border-right: none; - border-top-right-radius: 4px; } - .medium-editor-toolbar li .medium-editor-button-active { - background-color: #3276b1; - color: #fff; } - -.medium-editor-toolbar-form { - background: #428bca; - border-radius: 4px; - color: #fff; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #428bca; - color: #fff; - height: 60px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder { - /* Firefox 18- */ - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder { - /* Firefox 19+ */ - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder { - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form a { - color: #fff; } - -.medium-editor-toolbar-anchor-preview { - background: #428bca; - border-radius: 4px; - color: #fff; } - -.medium-editor-placeholder:after { - color: #357ebd; } diff --git a/dist/css/themes/bootstrap.min.css b/dist/css/themes/bootstrap.min.css deleted file mode 100644 index f36daf480..000000000 --- a/dist/css/themes/bootstrap.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-under:after{border-color:#428bca transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #428bca transparent}.medium-editor-toolbar{background-color:#428bca;border:1px solid #357ebd;border-radius:4px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #357ebd;box-sizing:border-box;color:#fff;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#3276b1;color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:4px;border-top-left-radius:4px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:4px;border-right:none;border-top-right-radius:4px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#3276b1;color:#fff}.medium-editor-toolbar-form{background:#428bca;border-radius:4px;color:#fff}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#428bca;color:#fff;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#428bca;border-radius:4px;color:#fff}.medium-editor-placeholder:after{color:#357ebd} \ No newline at end of file diff --git a/dist/css/themes/default.css b/dist/css/themes/default.css deleted file mode 100644 index a30157655..000000000 --- a/dist/css/themes/default.css +++ /dev/null @@ -1,59 +0,0 @@ -.medium-toolbar-arrow-under:after { - border-color: #242424 transparent transparent transparent; - top: 50px; } - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #242424 transparent; - top: -8px; } - -.medium-editor-toolbar { - background: linear-gradient(to bottom, #242424, rgba(36, 36, 36, 0.75)); - background-color: #242424; - border: 1px solid #000; - border-radius: 5px; - box-shadow: 0 0 3px #000; } - .medium-editor-toolbar li button { - background: linear-gradient(to bottom, #242424, rgba(36, 36, 36, 0.89)); - background-color: #242424; - border: 0; - border-left: 1px solid #333; - border-left: 1px solid rgba(255, 255, 255, 0.1); - border-right: 1px solid #000; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3); - color: #fff; - height: 50px; - min-width: 50px; - transition: background-color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #000; - color: yellow; } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 5px; - border-top-left-radius: 5px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 5px; - border-top-right-radius: 5px; } - .medium-editor-toolbar li .medium-editor-button-active { - background: linear-gradient(to bottom, #242424, rgba(0, 0, 0, 0.89)); - background-color: #000; - color: #fff; } - -.medium-editor-toolbar-form { - background: #242424; - border-radius: 5px; - color: #999; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #242424; - box-sizing: border-box; - color: #ccc; - height: 50px; } - .medium-editor-toolbar-form a { - color: #fff; } - -.medium-editor-toolbar-anchor-preview { - background: #242424; - border-radius: 5px; - color: #fff; } - -.medium-editor-placeholder:after { - color: #b3b3b1; } diff --git a/dist/css/themes/default.min.css b/dist/css/themes/default.min.css deleted file mode 100644 index bad2e21bd..000000000 --- a/dist/css/themes/default.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-under:after{border-color:#242424 transparent transparent transparent;top:50px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #242424 transparent;top:-8px}.medium-editor-toolbar{background:linear-gradient(to bottom,#242424,rgba(36,36,36,.75));background-color:#242424;border:1px solid #000;border-radius:5px;box-shadow:0 0 3px #000}.medium-editor-toolbar li button{background:linear-gradient(to bottom,#242424,rgba(36,36,36,.89));background-color:#242424;border:0;border-left:1px solid #333;border-left:1px solid rgba(255,255,255,.1);border-right:1px solid #000;box-shadow:0 2px 2px rgba(0,0,0,.3);color:#fff;height:50px;min-width:50px;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#000;color:#ff0}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:5px;border-top-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:5px;border-top-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to bottom,#242424,rgba(0,0,0,.89));background-color:#000;color:#fff}.medium-editor-toolbar-form{background:#242424;border-radius:5px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#242424;box-sizing:border-box;color:#ccc;height:50px}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#242424;border-radius:5px;color:#fff}.medium-editor-placeholder:after{color:#b3b3b1} \ No newline at end of file diff --git a/dist/css/themes/flat.css b/dist/css/themes/flat.css deleted file mode 100644 index fdd608133..000000000 --- a/dist/css/themes/flat.css +++ /dev/null @@ -1,57 +0,0 @@ -.medium-toolbar-arrow-under:after { - border-color: #57ad68 transparent transparent transparent; - top: 60px; } - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #57ad68 transparent; - top: -8px; } - -.medium-editor-toolbar { - background-color: #57ad68; } - .medium-editor-toolbar li { - padding: 0; } - .medium-editor-toolbar li button { - background-color: transparent; - border: none; - border-right: 1px solid #9ccea6; - color: #fff; - height: 60px; - min-width: 60px; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #346a3f; - color: #fff; } - .medium-editor-toolbar li .medium-editor-button-active { - background-color: #23482a; - color: #fff; } - .medium-editor-toolbar li .medium-editor-button-last { - border-right: none; } - -.medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #57ad68; - color: #fff; - height: 60px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder { - /* Firefox 18- */ - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder { - /* Firefox 19+ */ - color: #fff; - color: rgba(255, 255, 255, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder { - color: #fff; - color: rgba(255, 255, 255, 0.8); } - -.medium-editor-toolbar-form a { - color: #fff; } - -.medium-editor-toolbar-anchor-preview { - background: #57ad68; - color: #fff; } - -.medium-editor-placeholder:after { - color: #9ccea6; } diff --git a/dist/css/themes/flat.min.css b/dist/css/themes/flat.min.css deleted file mode 100644 index ccfeb5da3..000000000 --- a/dist/css/themes/flat.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-under:after{border-color:#57ad68 transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #57ad68 transparent;top:-8px}.medium-editor-toolbar{background-color:#57ad68}.medium-editor-toolbar li{padding:0}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #9ccea6;color:#fff;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#346a3f;color:#fff}.medium-editor-toolbar li .medium-editor-button-active{background-color:#23482a;color:#fff}.medium-editor-toolbar li .medium-editor-button-last{border-right:none}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#57ad68;color:#fff;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#fff;color:rgba(255,255,255,.8)}.medium-editor-toolbar-form a{color:#fff}.medium-editor-toolbar-anchor-preview{background:#57ad68;color:#fff}.medium-editor-placeholder:after{color:#9ccea6} \ No newline at end of file diff --git a/dist/css/themes/mani.css b/dist/css/themes/mani.css deleted file mode 100644 index 498db2ab1..000000000 --- a/dist/css/themes/mani.css +++ /dev/null @@ -1,54 +0,0 @@ -.medium-toolbar-arrow-under:after, -.medium-toolbar-arrow-over:before { - display: none; } - -.medium-editor-toolbar { - background: linear-gradient(to top, #dee7f0, white); - background-color: #dee7f0; - background-color: rgba(222, 231, 240, 0.95); - border: 1px solid #cdd6e0; - border-radius: 2px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45); } - .medium-editor-toolbar li button { - background-color: transparent; - border: none; - border-right: 1px solid #cdd6e0; - color: #40648a; - height: 50px; - min-width: 50px; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #5c90c7; - background-color: rgba(92, 144, 199, 0.45); - color: #fff; } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 2px; - border-top-left-radius: 2px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 2px; - border-top-right-radius: 2px; } - .medium-editor-toolbar li .medium-editor-button-active { - background: linear-gradient(to bottom, #dee7f0, rgba(0, 0, 0, 0.1)); - background-color: #5c90c7; - background-color: rgba(92, 144, 199, 0.45); - color: #000; } - -.medium-editor-toolbar-form { - background: #dee7f0; - border-radius: 2px; - color: #999; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #dee7f0; - box-sizing: border-box; - color: #40648a; - height: 50px; } - .medium-editor-toolbar-form a { - color: #40648a; } - -.medium-editor-toolbar-anchor-preview { - background: #dee7f0; - border-radius: 2px; - color: #40648a; } - -.medium-editor-placeholder:after { - color: #cdd6e0; } diff --git a/dist/css/themes/mani.min.css b/dist/css/themes/mani.min.css deleted file mode 100644 index 3911e304a..000000000 --- a/dist/css/themes/mani.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{background:linear-gradient(to top,#dee7f0,#fff);background-color:#dee7f0;background-color:rgba(222,231,240,.95);border:1px solid #cdd6e0;border-radius:2px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #cdd6e0;color:#40648a;height:50px;min-width:50px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#fff}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:2px;border-top-left-radius:2px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:2px;border-top-right-radius:2px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to bottom,#dee7f0,rgba(0,0,0,.1));background-color:#5c90c7;background-color:rgba(92,144,199,.45);color:#000}.medium-editor-toolbar-form{background:#dee7f0;border-radius:2px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#dee7f0;box-sizing:border-box;color:#40648a;height:50px}.medium-editor-toolbar-form a{color:#40648a}.medium-editor-toolbar-anchor-preview{background:#dee7f0;border-radius:2px;color:#40648a}.medium-editor-placeholder:after{color:#cdd6e0} \ No newline at end of file diff --git a/dist/css/themes/roman.css b/dist/css/themes/roman.css deleted file mode 100644 index 136b15c00..000000000 --- a/dist/css/themes/roman.css +++ /dev/null @@ -1,55 +0,0 @@ -.medium-toolbar-arrow-under:after, -.medium-toolbar-arrow-over:before { - display: none; } - -.medium-editor-toolbar { - background-color: #fff; - background-color: rgba(255, 255, 255, 0.95); - border-radius: 5px; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45); } - .medium-editor-toolbar li button { - background: linear-gradient(to bottom, #fff, rgba(0, 0, 0, 0.2)); - background-color: transparent; - border: none; - border-right: 1px solid #a8a8a8; - box-shadow: inset 0 0 3px #f8f8e6; - color: #889aac; - height: 50px; - min-width: 50px; - text-shadow: 1px 4px 6px #def, 0 0 0 #000, 1px 4px 6px #def; - transition: background-color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #fff; - color: #fff; - color: rgba(0, 0, 0, 0.8); } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 5px; - border-top-left-radius: 5px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 5px; - border-top-right-radius: 5px; } - .medium-editor-toolbar li .medium-editor-button-active { - background: linear-gradient(to top, #fff, rgba(0, 0, 0, 0.1)); - background-color: #ccc; - color: #000; - color: rgba(0, 0, 0, 0.8); } - -.medium-editor-toolbar-form { - background: #fff; - border-radius: 5px; - color: #999; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #fff; - color: #a8a8a8; - height: 50px; - margin: 0; } - .medium-editor-toolbar-form a { - color: #889aac; } - -.medium-editor-toolbar-anchor-preview { - background: #fff; - border-radius: 5px; - color: #889aac; } - -.medium-editor-placeholder:after { - color: #a8a8a8; } diff --git a/dist/css/themes/roman.min.css b/dist/css/themes/roman.min.css deleted file mode 100644 index ca567eeb3..000000000 --- a/dist/css/themes/roman.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-over:before,.medium-toolbar-arrow-under:after{display:none}.medium-editor-toolbar{background-color:#fff;background-color:rgba(255,255,255,.95);border-radius:5px;box-shadow:0 2px 6px rgba(0,0,0,.45)}.medium-editor-toolbar li button{background:linear-gradient(to bottom,#fff,rgba(0,0,0,.2));background-color:transparent;border:none;border-right:1px solid #a8a8a8;box-shadow:inset 0 0 3px #f8f8e6;color:#889aac;height:50px;min-width:50px;text-shadow:1px 4px 6px #def,0 0 0 #000,1px 4px 6px #def;transition:background-color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#fff;color:#fff;color:rgba(0,0,0,.8)}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:5px;border-top-left-radius:5px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:5px;border-top-right-radius:5px}.medium-editor-toolbar li .medium-editor-button-active{background:linear-gradient(to top,#fff,rgba(0,0,0,.1));background-color:#ccc;color:#000;color:rgba(0,0,0,.8)}.medium-editor-toolbar-form{background:#fff;border-radius:5px;color:#999}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#fff;color:#a8a8a8;height:50px;margin:0}.medium-editor-toolbar-form a{color:#889aac}.medium-editor-toolbar-anchor-preview{background:#fff;border-radius:5px;color:#889aac}.medium-editor-placeholder:after{color:#a8a8a8} \ No newline at end of file diff --git a/dist/css/themes/tim.css b/dist/css/themes/tim.css deleted file mode 100644 index 0e122d3e6..000000000 --- a/dist/css/themes/tim.css +++ /dev/null @@ -1,66 +0,0 @@ -.medium-toolbar-arrow-under:after { - border-color: #2f1e07 transparent transparent transparent; - top: 60px; } - -.medium-toolbar-arrow-over:before { - border-color: transparent transparent #2f1e07 transparent; } - -.medium-editor-toolbar { - background-color: #2f1e07; - border: 1px solid #5b3a0e; - border-radius: 6px; } - .medium-editor-toolbar li button { - background-color: transparent; - border: none; - border-right: 1px solid #5b3a0e; - box-sizing: border-box; - color: #ffedd5; - height: 60px; - min-width: 60px; - transition: background-color .2s ease-in, color .2s ease-in; } - .medium-editor-toolbar li button:hover { - background-color: #030200; - color: #ffedd5; } - .medium-editor-toolbar li .medium-editor-button-first { - border-bottom-left-radius: 6px; - border-top-left-radius: 6px; } - .medium-editor-toolbar li .medium-editor-button-last { - border-bottom-right-radius: 6px; - border-right: none; - border-top-right-radius: 6px; } - .medium-editor-toolbar li .medium-editor-button-active { - background-color: #030200; - color: #ffedd5; } - -.medium-editor-toolbar-form { - background: #2f1e07; - border-radius: 6px; - color: #ffedd5; } - .medium-editor-toolbar-form .medium-editor-toolbar-input { - background: #2f1e07; - color: #ffedd5; - height: 60px; } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder { - color: #ffedd5; - color: rgba(255, 237, 213, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder { - /* Firefox 18- */ - color: #ffedd5; - color: rgba(255, 237, 213, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder { - /* Firefox 19+ */ - color: #ffedd5; - color: rgba(255, 237, 213, 0.8); } - .medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder { - color: #ffedd5; - color: rgba(255, 237, 213, 0.8); } - .medium-editor-toolbar-form a { - color: #ffedd5; } - -.medium-editor-toolbar-anchor-preview { - background: #2f1e07; - border-radius: 6px; - color: #ffedd5; } - -.medium-editor-placeholder:after { - color: #5b3a0e; } diff --git a/dist/css/themes/tim.min.css b/dist/css/themes/tim.min.css deleted file mode 100644 index c6f62d257..000000000 --- a/dist/css/themes/tim.min.css +++ /dev/null @@ -1 +0,0 @@ -.medium-toolbar-arrow-under:after{border-color:#2f1e07 transparent transparent transparent;top:60px}.medium-toolbar-arrow-over:before{border-color:transparent transparent #2f1e07 transparent}.medium-editor-toolbar{background-color:#2f1e07;border:1px solid #5b3a0e;border-radius:6px}.medium-editor-toolbar li button{background-color:transparent;border:none;border-right:1px solid #5b3a0e;box-sizing:border-box;color:#ffedd5;height:60px;min-width:60px;transition:background-color .2s ease-in,color .2s ease-in}.medium-editor-toolbar li button:hover{background-color:#030200;color:#ffedd5}.medium-editor-toolbar li .medium-editor-button-first{border-bottom-left-radius:6px;border-top-left-radius:6px}.medium-editor-toolbar li .medium-editor-button-last{border-bottom-right-radius:6px;border-right:none;border-top-right-radius:6px}.medium-editor-toolbar li .medium-editor-button-active{background-color:#030200;color:#ffedd5}.medium-editor-toolbar-form{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-toolbar-form .medium-editor-toolbar-input{background:#2f1e07;color:#ffedd5;height:60px}.medium-editor-toolbar-form .medium-editor-toolbar-input::-webkit-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input::-moz-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form .medium-editor-toolbar-input:-ms-input-placeholder{color:#ffedd5;color:rgba(255,237,213,.8)}.medium-editor-toolbar-form a{color:#ffedd5}.medium-editor-toolbar-anchor-preview{background:#2f1e07;border-radius:6px;color:#ffedd5}.medium-editor-placeholder:after{color:#5b3a0e} \ No newline at end of file diff --git a/dist/js/medium-editor.js b/dist/js/medium-editor.js deleted file mode 100644 index 1fbad845c..000000000 --- a/dist/js/medium-editor.js +++ /dev/null @@ -1,7976 +0,0 @@ -/*global self, document, DOMException */ - -/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ - -// Full polyfill for browsers with no classList support -if (!("classList" in document.createElement("_"))) { - (function (view) { - - "use strict"; - - if (!('Element' in view)) return; - - var - classListProp = "classList" - , protoProp = "prototype" - , elemCtrProto = view.Element[protoProp] - , objCtr = Object - , strTrim = String[protoProp].trim || function () { - return this.replace(/^\s+|\s+$/g, ""); - } - , arrIndexOf = Array[protoProp].indexOf || function (item) { - var - i = 0 - , len = this.length - ; - for (; i < len; i++) { - if (i in this && this[i] === item) { - return i; - } - } - return -1; - } - // Vendors: please allow content code to instantiate DOMExceptions - , DOMEx = function (type, message) { - this.name = type; - this.code = DOMException[type]; - this.message = message; - } - , checkTokenAndGetIndex = function (classList, token) { - if (token === "") { - throw new DOMEx( - "SYNTAX_ERR" - , "An invalid or illegal string was specified" - ); - } - if (/\s/.test(token)) { - throw new DOMEx( - "INVALID_CHARACTER_ERR" - , "String contains an invalid character" - ); - } - return arrIndexOf.call(classList, token); - } - , ClassList = function (elem) { - var - trimmedClasses = strTrim.call(elem.getAttribute("class") || "") - , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] - , i = 0 - , len = classes.length - ; - for (; i < len; i++) { - this.push(classes[i]); - } - this._updateClassName = function () { - elem.setAttribute("class", this.toString()); - }; - } - , classListProto = ClassList[protoProp] = [] - , classListGetter = function () { - return new ClassList(this); - } - ; - // Most DOMException implementations don't allow calling DOMException's toString() - // on non-DOMExceptions. Error's toString() is sufficient here. - DOMEx[protoProp] = Error[protoProp]; - classListProto.item = function (i) { - return this[i] || null; - }; - classListProto.contains = function (token) { - token += ""; - return checkTokenAndGetIndex(this, token) !== -1; - }; - classListProto.add = function () { - var - tokens = arguments - , i = 0 - , l = tokens.length - , token - , updated = false - ; - do { - token = tokens[i] + ""; - if (checkTokenAndGetIndex(this, token) === -1) { - this.push(token); - updated = true; - } - } - while (++i < l); - - if (updated) { - this._updateClassName(); - } - }; - classListProto.remove = function () { - var - tokens = arguments - , i = 0 - , l = tokens.length - , token - , updated = false - , index - ; - do { - token = tokens[i] + ""; - index = checkTokenAndGetIndex(this, token); - while (index !== -1) { - this.splice(index, 1); - updated = true; - index = checkTokenAndGetIndex(this, token); - } - } - while (++i < l); - - if (updated) { - this._updateClassName(); - } - }; - classListProto.toggle = function (token, force) { - token += ""; - - var - result = this.contains(token) - , method = result ? - force !== true && "remove" - : - force !== false && "add" - ; - - if (method) { - this[method](token); - } - - if (force === true || force === false) { - return force; - } else { - return !result; - } - }; - classListProto.toString = function () { - return this.join(" "); - }; - - if (objCtr.defineProperty) { - var classListPropDesc = { - get: classListGetter - , enumerable: true - , configurable: true - }; - try { - objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); - } catch (ex) { // IE 8 doesn't support enumerable:true - if (ex.number === -0x7FF5EC54) { - classListPropDesc.enumerable = false; - objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); - } - } - } else if (objCtr[protoProp].__defineGetter__) { - elemCtrProto.__defineGetter__(classListProp, classListGetter); - } - - }(self)); -} - -/* Blob.js - * A Blob implementation. - * 2014-07-24 - * - * By Eli Grey, http://eligrey.com - * By Devin Samarin, https://github.com/dsamarin - * License: X11/MIT - * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md - */ - -/*global self, unescape */ -/*jslint bitwise: true, regexp: true, confusion: true, vars: true, white: true, - plusplus: true */ - -/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ - -(function (view) { - "use strict"; - - view.URL = view.URL || view.webkitURL; - - if (view.Blob && view.URL) { - try { - new Blob; - return; - } catch (e) {} - } - - // Internally we use a BlobBuilder implementation to base Blob off of - // in order to support older browsers that only have BlobBuilder - var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { - var - get_class = function(object) { - return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; - } - , FakeBlobBuilder = function BlobBuilder() { - this.data = []; - } - , FakeBlob = function Blob(data, type, encoding) { - this.data = data; - this.size = data.length; - this.type = type; - this.encoding = encoding; - } - , FBB_proto = FakeBlobBuilder.prototype - , FB_proto = FakeBlob.prototype - , FileReaderSync = view.FileReaderSync - , FileException = function(type) { - this.code = this[this.name = type]; - } - , file_ex_codes = ( - "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " - + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" - ).split(" ") - , file_ex_code = file_ex_codes.length - , real_URL = view.URL || view.webkitURL || view - , real_create_object_URL = real_URL.createObjectURL - , real_revoke_object_URL = real_URL.revokeObjectURL - , URL = real_URL - , btoa = view.btoa - , atob = view.atob - - , ArrayBuffer = view.ArrayBuffer - , Uint8Array = view.Uint8Array - - , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ - ; - FakeBlob.fake = FB_proto.fake = true; - while (file_ex_code--) { - FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; - } - // Polyfill URL - if (!real_URL.createObjectURL) { - URL = view.URL = function(uri) { - var - uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") - , uri_origin - ; - uri_info.href = uri; - if (!("origin" in uri_info)) { - if (uri_info.protocol.toLowerCase() === "data:") { - uri_info.origin = null; - } else { - uri_origin = uri.match(origin); - uri_info.origin = uri_origin && uri_origin[1]; - } - } - return uri_info; - }; - } - URL.createObjectURL = function(blob) { - var - type = blob.type - , data_URI_header - ; - if (type === null) { - type = "application/octet-stream"; - } - if (blob instanceof FakeBlob) { - data_URI_header = "data:" + type; - if (blob.encoding === "base64") { - return data_URI_header + ";base64," + blob.data; - } else if (blob.encoding === "URI") { - return data_URI_header + "," + decodeURIComponent(blob.data); - } if (btoa) { - return data_URI_header + ";base64," + btoa(blob.data); - } else { - return data_URI_header + "," + encodeURIComponent(blob.data); - } - } else if (real_create_object_URL) { - return real_create_object_URL.call(real_URL, blob); - } - }; - URL.revokeObjectURL = function(object_URL) { - if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { - real_revoke_object_URL.call(real_URL, object_URL); - } - }; - FBB_proto.append = function(data/*, endings*/) { - var bb = this.data; - // decode data to a binary string - if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { - var - str = "" - , buf = new Uint8Array(data) - , i = 0 - , buf_len = buf.length - ; - for (; i < buf_len; i++) { - str += String.fromCharCode(buf[i]); - } - bb.push(str); - } else if (get_class(data) === "Blob" || get_class(data) === "File") { - if (FileReaderSync) { - var fr = new FileReaderSync; - bb.push(fr.readAsBinaryString(data)); - } else { - // async FileReader won't work as BlobBuilder is sync - throw new FileException("NOT_READABLE_ERR"); - } - } else if (data instanceof FakeBlob) { - if (data.encoding === "base64" && atob) { - bb.push(atob(data.data)); - } else if (data.encoding === "URI") { - bb.push(decodeURIComponent(data.data)); - } else if (data.encoding === "raw") { - bb.push(data.data); - } - } else { - if (typeof data !== "string") { - data += ""; // convert unsupported types to strings - } - // decode UTF-16 to binary string - bb.push(unescape(encodeURIComponent(data))); - } - }; - FBB_proto.getBlob = function(type) { - if (!arguments.length) { - type = null; - } - return new FakeBlob(this.data.join(""), type, "raw"); - }; - FBB_proto.toString = function() { - return "[object BlobBuilder]"; - }; - FB_proto.slice = function(start, end, type) { - var args = arguments.length; - if (args < 3) { - type = null; - } - return new FakeBlob( - this.data.slice(start, args > 1 ? end : this.data.length) - , type - , this.encoding - ); - }; - FB_proto.toString = function() { - return "[object Blob]"; - }; - FB_proto.close = function() { - this.size = 0; - delete this.data; - }; - return FakeBlobBuilder; - }(view)); - - view.Blob = function(blobParts, options) { - var type = options ? (options.type || "") : ""; - var builder = new BlobBuilder(); - if (blobParts) { - for (var i = 0, len = blobParts.length; i < len; i++) { - if (Uint8Array && blobParts[i] instanceof Uint8Array) { - builder.append(blobParts[i].buffer); - } - else { - builder.append(blobParts[i]); - } - } - } - var blob = builder.getBlob(type); - if (!blob.slice && blob.webkitSlice) { - blob.slice = blob.webkitSlice; - } - return blob; - }; - - var getPrototypeOf = Object.getPrototypeOf || function(object) { - return object.__proto__; - }; - view.Blob.prototype = getPrototypeOf(new view.Blob()); -}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); - -(function (root, factory) { - 'use strict'; - var isElectron = typeof module === 'object' && typeof process !== 'undefined' && process && process.versions && process.versions.electron; - if (!isElectron && typeof module === 'object') { - module.exports = factory; - } else if (typeof define === 'function' && define.amd) { - define(function () { - return factory; - }); - } else { - root.MediumEditor = factory; - } -}(this, function () { - - 'use strict'; - -function MediumEditor (elements, options) { - 'use strict'; - return this.init(elements, options); -} - -MediumEditor.extensions = {}; -/*jshint unused: true */ -(function (window) { - 'use strict'; - - function copyInto (overwrite, dest) { - var prop, - sources = Array.prototype.slice.call(arguments, 2); - dest = dest || {}; - for (var i = 0; i < sources.length; i++) { - var source = sources[i]; - if (source) { - for (prop in source) { - if (source.hasOwnProperty(prop) && - typeof source[prop] !== 'undefined' && - (overwrite || dest.hasOwnProperty(prop) === false)) { - dest[prop] = source[prop]; - } - } - } - } - return dest; - } - - // https://developer.mozilla.org/en-US/docs/Web/API/Node/contains - // Some browsers (including phantom) don't return true for Node.contains(child) - // if child is a text node. Detect these cases here and use a fallback - // for calls to Util.isDescendant() - var nodeContainsWorksWithTextNodes = false; - try { - var testParent = document.createElement('div'), - testText = document.createTextNode(' '); - testParent.appendChild(testText); - nodeContainsWorksWithTextNodes = testParent.contains(testText); - } catch (exc) { - } - - var Util = { - - // http://stackoverflow.com/questions/17907445/how-to-detect-ie11#comment30165888_17907562 - // by rg89 - isIE: ((navigator.appName === 'Microsoft Internet Explorer') || ((navigator.appName === 'Netscape') && (new RegExp('Trident/.*rv:([0-9]+[.0-9]*)').exec(navigator.userAgent) !== null))), - - isEdge: (/Edge\/\d+/).exec(navigator.userAgent) !== null, - - // if firefox - isFF: (navigator.userAgent.toLowerCase().indexOf('firefox') > -1), - - // http://stackoverflow.com/a/11752084/569101 - isMac: (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0), - - // https://github.com/jashkenas/underscore - // Lonely letter MUST USE the uppercase code - keyCode: { - BACKSPACE: 8, - TAB: 9, - ENTER: 13, - ESCAPE: 27, - SPACE: 32, - DELETE: 46, - K: 75, // K keycode, and not k - M: 77, - V: 86 - }, - - /** - * Returns true if it's metaKey on Mac, or ctrlKey on non-Mac. - * See #591 - */ - isMetaCtrlKey: function (event) { - return !!((Util.isMac && event.metaKey) || (!Util.isMac && event.ctrlKey)); - }, - - /** - * Returns true if the key associated to the event is inside keys array - * - * @see : https://github.com/jquery/jquery/blob/0705be475092aede1eddae01319ec931fb9c65fc/src/event.js#L473-L484 - * @see : http://stackoverflow.com/q/4471582/569101 - */ - isKey: function (event, keys) { - var keyCode = Util.getKeyCode(event); - - // it's not an array let's just compare strings! - if (false === Array.isArray(keys)) { - return keyCode === keys; - } - - return -1 !== keys.indexOf(keyCode); - }, - - getKeyCode: function (event) { - var keyCode = event.which; - - // getting the key code from event - if (null === keyCode) { - keyCode = event.charCode !== null ? event.charCode : event.keyCode; - } - - return keyCode; - }, - - blockContainerElementNames: [ - // elements our editor generates - 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'ul', 'li', 'ol', - // all other known block elements - 'address', 'article', 'aside', 'audio', 'canvas', 'dd', 'dl', 'dt', 'fieldset', - 'figcaption', 'figure', 'footer', 'form', 'header', 'hgroup', 'main', 'nav', - 'noscript', 'output', 'section', 'video', - 'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td' - ], - - emptyElementNames: ['br', 'col', 'colgroup', 'hr', 'img', 'input', 'source', 'wbr'], - - extend: function extend (/* dest, source1, source2, ...*/) { - var args = [true].concat(Array.prototype.slice.call(arguments)); - return copyInto.apply(this, args); - }, - - defaults: function defaults (/*dest, source1, source2, ...*/) { - var args = [false].concat(Array.prototype.slice.call(arguments)); - return copyInto.apply(this, args); - }, - - /* - * Create a link around the provided text nodes which must be adjacent to each other and all be - * descendants of the same closest block container. If the preconditions are not met, unexpected - * behavior will result. - */ - createLink: function (document, textNodes, href, target) { - var anchor = document.createElement('a'); - Util.moveTextRangeIntoElement(textNodes[0], textNodes[textNodes.length - 1], anchor); - anchor.setAttribute('href', href); - if (target) { - if (target === '_blank') { - anchor.setAttribute('rel', 'noopener noreferrer'); - } - anchor.setAttribute('target', target); - } - return anchor; - }, - - /* - * Given the provided match in the format {start: 1, end: 2} where start and end are indices into the - * textContent of the provided element argument, modify the DOM inside element to ensure that the text - * identified by the provided match can be returned as text nodes that contain exactly that text, without - * any additional text at the beginning or end of the returned array of adjacent text nodes. - * - * The only DOM manipulation performed by this function is splitting the text nodes, non-text nodes are - * not affected in any way. - */ - findOrCreateMatchingTextNodes: function (document, element, match) { - var treeWalker = document.createTreeWalker(element, NodeFilter.SHOW_ALL, null, false), - matchedNodes = [], - currentTextIndex = 0, - startReached = false, - currentNode = null, - newNode = null; - - while ((currentNode = treeWalker.nextNode()) !== null) { - if (currentNode.nodeType > 3) { - continue; - } else if (currentNode.nodeType === 3) { - if (!startReached && match.start < (currentTextIndex + currentNode.nodeValue.length)) { - startReached = true; - newNode = Util.splitStartNodeIfNeeded(currentNode, match.start, currentTextIndex); - } - if (startReached) { - Util.splitEndNodeIfNeeded(currentNode, newNode, match.end, currentTextIndex); - } - if (startReached && currentTextIndex === match.end) { - break; // Found the node(s) corresponding to the link. Break out and move on to the next. - } else if (startReached && currentTextIndex > (match.end + 1)) { - throw new Error('PerformLinking overshot the target!'); // should never happen... - } - - if (startReached) { - matchedNodes.push(newNode || currentNode); - } - - currentTextIndex += currentNode.nodeValue.length; - if (newNode !== null) { - currentTextIndex += newNode.nodeValue.length; - // Skip the newNode as we'll already have pushed it to the matches - treeWalker.nextNode(); - } - newNode = null; - } else if (currentNode.tagName.toLowerCase() === 'img') { - if (!startReached && (match.start <= currentTextIndex)) { - startReached = true; - } - if (startReached) { - matchedNodes.push(currentNode); - } - } - } - return matchedNodes; - }, - - /* - * Given the provided text node and text coordinates, split the text node if needed to make it align - * precisely with the coordinates. - * - * This function is intended to be called from Util.findOrCreateMatchingTextNodes. - */ - splitStartNodeIfNeeded: function (currentNode, matchStartIndex, currentTextIndex) { - if (matchStartIndex !== currentTextIndex) { - return currentNode.splitText(matchStartIndex - currentTextIndex); - } - return null; - }, - - /* - * Given the provided text node and text coordinates, split the text node if needed to make it align - * precisely with the coordinates. The newNode argument should from the result of Util.splitStartNodeIfNeeded, - * if that function has been called on the same currentNode. - * - * This function is intended to be called from Util.findOrCreateMatchingTextNodes. - */ - splitEndNodeIfNeeded: function (currentNode, newNode, matchEndIndex, currentTextIndex) { - var textIndexOfEndOfFarthestNode, - endSplitPoint; - textIndexOfEndOfFarthestNode = currentTextIndex + currentNode.nodeValue.length + - (newNode ? newNode.nodeValue.length : 0) - 1; - endSplitPoint = matchEndIndex - currentTextIndex - - (newNode ? currentNode.nodeValue.length : 0); - if (textIndexOfEndOfFarthestNode >= matchEndIndex && - currentTextIndex !== textIndexOfEndOfFarthestNode && - endSplitPoint !== 0) { - (newNode || currentNode).splitText(endSplitPoint); - } - }, - - /* - * Take an element, and break up all of its text content into unique pieces such that: - * 1) All text content of the elements are in separate blocks. No piece of text content should span - * across multiple blocks. This means no element return by this function should have - * any blocks as children. - * 2) The union of the text content of all of the elements returned here covers all - * of the text within the element. - * - * - * EXAMPLE: - * In the event that we have something like: - * - *
    - *

    Some Text

    - *
      - *
    1. List Item 1
    2. - *
    3. List Item 2
    4. - *
    - *
    - * - * This function would return these elements as an array: - * [

    Some Text

    ,
  • List Item 1
  • ,
  • List Item 2
  • ] - * - * Since the
    and
      elements contain blocks within them they are not returned. - * Since the

      and

    1. 's don't contain block elements and cover all the text content of the - *
      container, they are the elements returned. - */ - splitByBlockElements: function (element) { - if (element.nodeType !== 3 && element.nodeType !== 1) { - return []; - } - - var toRet = [], - blockElementQuery = MediumEditor.util.blockContainerElementNames.join(','); - - if (element.nodeType === 3 || element.querySelectorAll(blockElementQuery).length === 0) { - return [element]; - } - - for (var i = 0; i < element.childNodes.length; i++) { - var child = element.childNodes[i]; - if (child.nodeType === 3) { - toRet.push(child); - } else if (child.nodeType === 1) { - var blockElements = child.querySelectorAll(blockElementQuery); - if (blockElements.length === 0) { - toRet.push(child); - } else { - toRet = toRet.concat(MediumEditor.util.splitByBlockElements(child)); - } - } - } - - return toRet; - }, - - // Find the next node in the DOM tree that represents any text that is being - // displayed directly next to the targetNode (passed as an argument) - // Text that appears directly next to the current node can be: - // - A sibling text node - // - A descendant of a sibling element - // - A sibling text node of an ancestor - // - A descendant of a sibling element of an ancestor - findAdjacentTextNodeWithContent: function findAdjacentTextNodeWithContent (rootNode, targetNode, ownerDocument) { - var pastTarget = false, - nextNode, - nodeIterator = ownerDocument.createNodeIterator(rootNode, NodeFilter.SHOW_TEXT, null, false); - - // Use a native NodeIterator to iterate over all the text nodes that are descendants - // of the rootNode. Once past the targetNode, choose the first non-empty text node - nextNode = nodeIterator.nextNode(); - while (nextNode) { - if (nextNode === targetNode) { - pastTarget = true; - } else if (pastTarget) { - if (nextNode.nodeType === 3 && nextNode.nodeValue && nextNode.nodeValue.trim().length > 0) { - break; - } - } - nextNode = nodeIterator.nextNode(); - } - - return nextNode; - }, - - // Find an element's previous sibling within a medium-editor element - // If one doesn't exist, find the closest ancestor's previous sibling - findPreviousSibling: function (node) { - if (!node || Util.isMediumEditorElement(node)) { - return false; - } - - var previousSibling = node.previousSibling; - while (!previousSibling && !Util.isMediumEditorElement(node.parentNode)) { - node = node.parentNode; - previousSibling = node.previousSibling; - } - - return previousSibling; - }, - - isDescendant: function isDescendant (parent, child, checkEquality) { - if (!parent || !child) { - return false; - } - if (parent === child) { - return !!checkEquality; - } - // If parent is not an element, it can't have any descendants - if (parent.nodeType !== 1) { - return false; - } - if (nodeContainsWorksWithTextNodes || child.nodeType !== 3) { - return parent.contains(child); - } - var node = child.parentNode; - while (node !== null) { - if (node === parent) { - return true; - } - node = node.parentNode; - } - return false; - }, - - // https://github.com/jashkenas/underscore - isElement: function isElement (obj) { - return !!(obj && obj.nodeType === 1); - }, - - // https://github.com/jashkenas/underscore - throttle: function (func, wait) { - var THROTTLE_INTERVAL = 50, - context, - args, - result, - timeout = null, - previous = 0, - later = function () { - previous = Date.now(); - timeout = null; - result = func.apply(context, args); - if (!timeout) { - context = args = null; - } - }; - - if (!wait && wait !== 0) { - wait = THROTTLE_INTERVAL; - } - - return function () { - var now = Date.now(), - remaining = wait - (now - previous); - - context = this; - args = arguments; - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - previous = now; - result = func.apply(context, args); - if (!timeout) { - context = args = null; - } - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - - traverseUp: function (current, testElementFunction) { - if (!current) { - return false; - } - - do { - if (current.nodeType === 1) { - if (testElementFunction(current)) { - return current; - } - // do not traverse upwards past the nearest containing editor - if (Util.isMediumEditorElement(current)) { - return false; - } - } - - current = current.parentNode; - } while (current); - - return false; - }, - - htmlEntities: function (str) { - // converts special characters (like <) into their escaped/encoded values (like <). - // This allows you to show to display the string without the browser reading it as HTML. - return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); - }, - - // http://stackoverflow.com/questions/6690752/insert-html-at-caret-in-a-contenteditable-div - insertHTMLCommand: function (doc, html) { - var selection, range, el, fragment, node, lastNode, toReplace, - res = false, - ecArgs = ['insertHTML', false, html]; - - /* Edge's implementation of insertHTML is just buggy right now: - * - Doesn't allow leading white space at the beginning of an element - * - Found a case when a tag was inserted when calling alignCenter inside a blockquote - * - * There are likely other bugs, these are just the ones we found so far. - * For now, let's just use the same fallback we did for IE - */ - if (!MediumEditor.util.isEdge && doc.queryCommandSupported('insertHTML')) { - try { - return doc.execCommand.apply(doc, ecArgs); - } catch (ignore) { - } - } - - selection = doc.getSelection(); - if (selection.rangeCount) { - range = selection.getRangeAt(0); - toReplace = range.commonAncestorContainer; - - // https://github.com/yabwe/medium-editor/issues/748 - // If the selection is an empty editor element, create a temporary text node inside of the editor - // and select it so that we don't delete the editor element - if (Util.isMediumEditorElement(toReplace) && !toReplace.firstChild) { - range.selectNode(toReplace.appendChild(doc.createTextNode(''))); - } else if ((toReplace.nodeType === 3 && range.startOffset === 0 && range.endOffset === toReplace.nodeValue.length) || - (toReplace.nodeType !== 3 && toReplace.innerHTML === range.toString())) { - // Ensure range covers maximum amount of nodes as possible - // By moving up the DOM and selecting ancestors whose only child is the range - while (!Util.isMediumEditorElement(toReplace) && - toReplace.parentNode && - toReplace.parentNode.childNodes.length === 1 && - !Util.isMediumEditorElement(toReplace.parentNode)) { - toReplace = toReplace.parentNode; - } - range.selectNode(toReplace); - } - range.deleteContents(); - - el = doc.createElement('div'); - el.innerHTML = html; - fragment = doc.createDocumentFragment(); - while (el.firstChild) { - node = el.firstChild; - lastNode = fragment.appendChild(node); - } - range.insertNode(fragment); - - // Preserve the selection: - if (lastNode) { - range = range.cloneRange(); - range.setStartAfter(lastNode); - range.collapse(true); - MediumEditor.selection.selectRange(doc, range); - } - res = true; - } - - // https://github.com/yabwe/medium-editor/issues/992 - // If we're monitoring calls to execCommand, notify listeners as if a real call had happened - if (doc.execCommand.callListeners) { - doc.execCommand.callListeners(ecArgs, res); - } - return res; - }, - - execFormatBlock: function (doc, tagName) { - // Get the top level block element that contains the selection - var blockContainer = Util.getTopBlockContainer(MediumEditor.selection.getSelectionStart(doc)), - childNodes; - - // Special handling for blockquote - if (tagName === 'blockquote') { - if (blockContainer) { - childNodes = Array.prototype.slice.call(blockContainer.childNodes); - // Check if the blockquote has a block element as a child (nested blocks) - if (childNodes.some(function (childNode) { - return Util.isBlockContainer(childNode); - })) { - // FF handles blockquote differently on formatBlock - // allowing nesting, we need to use outdent - // https://developer.mozilla.org/en-US/docs/Rich-Text_Editing_in_Mozilla - return doc.execCommand('outdent', false, null); - } - } - - // When IE blockquote needs to be called as indent - // http://stackoverflow.com/questions/1816223/rich-text-editor-with-blockquote-function/1821777#1821777 - if (Util.isIE) { - return doc.execCommand('indent', false, tagName); - } - } - - // If the blockContainer is already the element type being passed in - // treat it as 'undo' formatting and just convert it to a

      - if (blockContainer && tagName === blockContainer.nodeName.toLowerCase()) { - tagName = 'p'; - } - - // When IE we need to add <> to heading elements - // http://stackoverflow.com/questions/10741831/execcommand-formatblock-headings-in-ie - if (Util.isIE) { - tagName = '<' + tagName + '>'; - } - - // When FF, IE and Edge, we have to handle blockquote node separately as 'formatblock' does not work. - // https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand#Commands - if (blockContainer && blockContainer.nodeName.toLowerCase() === 'blockquote') { - // For IE, just use outdent - if (Util.isIE && tagName === '

      ') { - return doc.execCommand('outdent', false, tagName); - } - - // For Firefox and Edge, make sure there's a nested block element before calling outdent - if ((Util.isFF || Util.isEdge) && tagName === 'p') { - childNodes = Array.prototype.slice.call(blockContainer.childNodes); - // If there are some non-block elements we need to wrap everything in a

      before we outdent - if (childNodes.some(function (childNode) { - return !Util.isBlockContainer(childNode); - })) { - doc.execCommand('formatBlock', false, tagName); - } - return doc.execCommand('outdent', false, tagName); - } - } - - return doc.execCommand('formatBlock', false, tagName); - }, - - /** - * Set target to blank on the given el element - * - * TODO: not sure if this should be here - * - * When creating a link (using core -> createLink) the selection returned by Firefox will be the parent of the created link - * instead of the created link itself (as it is for Chrome for example), so we retrieve all "a" children to grab the good one by - * using `anchorUrl` to ensure that we are adding target="_blank" on the good one. - * This isn't a bulletproof solution anyway .. - */ - setTargetBlank: function (el, anchorUrl) { - var i, url = anchorUrl || false; - if (el.nodeName.toLowerCase() === 'a') { - el.target = '_blank'; - el.rel = 'noopener noreferrer'; - } else { - el = el.getElementsByTagName('a'); - - for (i = 0; i < el.length; i += 1) { - if (false === url || url === el[i].attributes.href.value) { - el[i].target = '_blank'; - el[i].rel = 'noopener noreferrer'; - } - } - } - }, - - /* - * this function is called to explicitly remove the target='_blank' as FF holds on to _blank value even - * after unchecking the checkbox on anchor form - */ - removeTargetBlank: function (el, anchorUrl) { - var i; - if (el.nodeName.toLowerCase() === 'a') { - el.removeAttribute('target'); - el.removeAttribute('rel'); - } else { - el = el.getElementsByTagName('a'); - - for (i = 0; i < el.length; i += 1) { - if (anchorUrl === el[i].attributes.href.value) { - el[i].removeAttribute('target'); - el[i].removeAttribute('rel'); - } - } - } - }, - - /* - * this function adds one or several classes on an a element. - * if el parameter is not an a, it will look for a children of el. - * if no a children are found, it will look for the a parent. - */ - addClassToAnchors: function (el, buttonClass) { - var classes = buttonClass.split(' '), - i, - j; - if (el.nodeName.toLowerCase() === 'a') { - for (j = 0; j < classes.length; j += 1) { - el.classList.add(classes[j]); - } - } else { - var aChildren = el.getElementsByTagName('a'); - if (aChildren.length === 0) { - var parentAnchor = Util.getClosestTag(el, 'a'); - el = parentAnchor ? [parentAnchor] : []; - } else { - el = aChildren; - } - for (i = 0; i < el.length; i += 1) { - for (j = 0; j < classes.length; j += 1) { - el[i].classList.add(classes[j]); - } - } - } - }, - - isListItem: function (node) { - if (!node) { - return false; - } - if (node.nodeName.toLowerCase() === 'li') { - return true; - } - - var parentNode = node.parentNode, - tagName = parentNode.nodeName.toLowerCase(); - while (tagName === 'li' || (!Util.isBlockContainer(parentNode) && tagName !== 'div')) { - if (tagName === 'li') { - return true; - } - parentNode = parentNode.parentNode; - if (parentNode) { - tagName = parentNode.nodeName.toLowerCase(); - } else { - return false; - } - } - return false; - }, - - findFirstTextNodeInSelection: function (selection) { - if (selection.anchorNode.nodeType === 3) { - return selection.anchorNode; - } - - var node = selection.anchorNode.firstChild; - - while (node) { - if (selection.containsNode(node, true)) { - if (node.nodeType === 3) { - return node; - } else { - node = node.firstChild; - } - } else { - node = node.nextSibling; - } - } - - return null; - }, - - cleanListDOM: function (ownerDocument, element) { - if (element.nodeName.toLowerCase() !== 'li') { - if (this.isIE || this.isEdge) { - return; - } - - var selection = ownerDocument.getSelection(), - newRange = ownerDocument.createRange(), - oldRange = selection.getRangeAt(0), - startContainer = oldRange.startContainer, - startOffset = oldRange.startOffset, - endContainer = oldRange.endContainer, - endOffset = oldRange.endOffset, - node, newNode, nextNode, moveEndOffset; - - if (element.nodeName.toLowerCase() === 'span') { - // Chrome & Safari unwraps removed li elements into a span - node = element; - moveEndOffset = false; - } else { - // FF leaves them as text nodes - node = this.findFirstTextNodeInSelection(selection); - moveEndOffset = startContainer.nodeType !== 3; - } - - while (node) { - if (node.nodeName.toLowerCase() !== 'span' && node.nodeType !== 3) { - break; - } - - if (node.nextSibling && node.nextSibling.nodeName.toLowerCase() === 'br') { - node.nextSibling.remove(); - - if (moveEndOffset) { - endOffset--; - } - } - - nextNode = node.nextSibling; - - newNode = ownerDocument.createElement('p'); - node.parentNode.replaceChild(newNode, node); - newNode.appendChild(node); - - node = nextNode; - } - - // Restore selection - newRange.setStart(startContainer, startOffset); - newRange.setEnd(endContainer, endOffset); - selection.removeAllRanges(); - selection.addRange(newRange); - } else { - var list = element.parentElement; - - if (list.parentElement.nodeName.toLowerCase() === 'p') { // yes we need to clean up - Util.unwrap(list.parentElement, ownerDocument); - - // move cursor at the end of the text inside the list - // for some unknown reason, the cursor is moved to end of the "visual" line - MediumEditor.selection.moveCursor(ownerDocument, element.firstChild, element.firstChild.textContent.length); - } - } - }, - - /* splitDOMTree - * - * Given a root element some descendant element, split the root element - * into its own element containing the descendant element and all elements - * on the left or right side of the descendant ('right' is default) - * - * example: - * - *

      - * / | \ - * - * / \ / \ / \ - * 1 2 3 4 5 6 - * - * If I wanted to split this tree given the
      as the root and "4" as the leaf - * the result would be (the prime ' marks indicates nodes that are created as clones): - * - * SPLITTING OFF 'RIGHT' TREE SPLITTING OFF 'LEFT' TREE - * - *
      '
      '
      - * / \ / \ / \ | - * ' - * / \ | | / \ /\ /\ /\ - * 1 2 3 4 5 6 1 2 3 4 5 6 - * - * The above example represents splitting off the 'right' or 'left' part of a tree, where - * the
      ' would be returned as an element not appended to the DOM, and the
      - * would remain in place where it was - * - */ - splitOffDOMTree: function (rootNode, leafNode, splitLeft) { - var splitOnNode = leafNode, - createdNode = null, - splitRight = !splitLeft; - - // loop until we hit the root - while (splitOnNode !== rootNode) { - var currParent = splitOnNode.parentNode, - newParent = currParent.cloneNode(false), - targetNode = (splitRight ? splitOnNode : currParent.firstChild), - appendLast; - - // Create a new parent element which is a clone of the current parent - if (createdNode) { - if (splitRight) { - // If we're splitting right, add previous created element before siblings - newParent.appendChild(createdNode); - } else { - // If we're splitting left, add previous created element last - appendLast = createdNode; - } - } - createdNode = newParent; - - while (targetNode) { - var sibling = targetNode.nextSibling; - // Special handling for the 'splitNode' - if (targetNode === splitOnNode) { - if (!targetNode.hasChildNodes()) { - targetNode.parentNode.removeChild(targetNode); - } else { - // For the node we're splitting on, if it has children, we need to clone it - // and not just move it - targetNode = targetNode.cloneNode(false); - } - // If the resulting split node has content, add it - if (targetNode.textContent) { - createdNode.appendChild(targetNode); - } - - targetNode = (splitRight ? sibling : null); - } else { - // For general case, just remove the element and only - // add it to the split tree if it contains something - targetNode.parentNode.removeChild(targetNode); - if (targetNode.hasChildNodes() || targetNode.textContent) { - createdNode.appendChild(targetNode); - } - - targetNode = sibling; - } - } - - // If we had an element we wanted to append at the end, do that now - if (appendLast) { - createdNode.appendChild(appendLast); - } - - splitOnNode = currParent; - } - - return createdNode; - }, - - moveTextRangeIntoElement: function (startNode, endNode, newElement) { - if (!startNode || !endNode) { - return false; - } - - var rootNode = Util.findCommonRoot(startNode, endNode); - if (!rootNode) { - return false; - } - - if (endNode === startNode) { - var temp = startNode.parentNode, - sibling = startNode.nextSibling; - temp.removeChild(startNode); - newElement.appendChild(startNode); - if (sibling) { - temp.insertBefore(newElement, sibling); - } else { - temp.appendChild(newElement); - } - return newElement.hasChildNodes(); - } - - // create rootChildren array which includes all the children - // we care about - var rootChildren = [], - firstChild, - lastChild, - nextNode; - for (var i = 0; i < rootNode.childNodes.length; i++) { - nextNode = rootNode.childNodes[i]; - if (!firstChild) { - if (Util.isDescendant(nextNode, startNode, true)) { - firstChild = nextNode; - } - } else { - if (Util.isDescendant(nextNode, endNode, true)) { - lastChild = nextNode; - break; - } else { - rootChildren.push(nextNode); - } - } - } - - var afterLast = lastChild.nextSibling, - fragment = rootNode.ownerDocument.createDocumentFragment(); - - // build up fragment on startNode side of tree - if (firstChild === startNode) { - firstChild.parentNode.removeChild(firstChild); - fragment.appendChild(firstChild); - } else { - fragment.appendChild(Util.splitOffDOMTree(firstChild, startNode)); - } - - // add any elements between firstChild & lastChild - rootChildren.forEach(function (element) { - element.parentNode.removeChild(element); - fragment.appendChild(element); - }); - - // build up fragment on endNode side of the tree - if (lastChild === endNode) { - lastChild.parentNode.removeChild(lastChild); - fragment.appendChild(lastChild); - } else { - fragment.appendChild(Util.splitOffDOMTree(lastChild, endNode, true)); - } - - // Add fragment into passed in element - newElement.appendChild(fragment); - - if (lastChild.parentNode === rootNode) { - // If last child is in the root, insert newElement in front of it - rootNode.insertBefore(newElement, lastChild); - } else if (afterLast) { - // If last child was removed, but it had a sibling, insert in front of it - rootNode.insertBefore(newElement, afterLast); - } else { - // lastChild was removed and was the last actual element just append - rootNode.appendChild(newElement); - } - - return newElement.hasChildNodes(); - }, - - /* based on http://stackoverflow.com/a/6183069 */ - depthOfNode: function (inNode) { - var theDepth = 0, - node = inNode; - while (node.parentNode !== null) { - node = node.parentNode; - theDepth++; - } - return theDepth; - }, - - findCommonRoot: function (inNode1, inNode2) { - var depth1 = Util.depthOfNode(inNode1), - depth2 = Util.depthOfNode(inNode2), - node1 = inNode1, - node2 = inNode2; - - while (depth1 !== depth2) { - if (depth1 > depth2) { - node1 = node1.parentNode; - depth1 -= 1; - } else { - node2 = node2.parentNode; - depth2 -= 1; - } - } - - while (node1 !== node2) { - node1 = node1.parentNode; - node2 = node2.parentNode; - } - - return node1; - }, - /* END - based on http://stackoverflow.com/a/6183069 */ - - isElementAtBeginningOfBlock: function (node) { - var textVal, - sibling; - while (!Util.isBlockContainer(node) && !Util.isMediumEditorElement(node)) { - sibling = node; - while (sibling = sibling.previousSibling) { - textVal = sibling.nodeType === 3 ? sibling.nodeValue : sibling.textContent; - if (textVal.length > 0) { - return false; - } - } - node = node.parentNode; - } - return true; - }, - - isMediumEditorElement: function (element) { - return element && element.getAttribute && !!element.getAttribute('data-medium-editor-element'); - }, - - getContainerEditorElement: function (element) { - return Util.traverseUp(element, function (node) { - return Util.isMediumEditorElement(node); - }); - }, - - isBlockContainer: function (element) { - return element && element.nodeType !== 3 && Util.blockContainerElementNames.indexOf(element.nodeName.toLowerCase()) !== -1; - }, - - /* Finds the closest ancestor which is a block container element - * If element is within editor element but not within any other block element, - * the editor element is returned - */ - getClosestBlockContainer: function (node) { - return Util.traverseUp(node, function (node) { - return Util.isBlockContainer(node) || Util.isMediumEditorElement(node); - }); - }, - - /* Finds highest level ancestor element which is a block container element - * If element is within editor element but not within any other block element, - * the editor element is returned - */ - getTopBlockContainer: function (element) { - var topBlock = Util.isBlockContainer(element) ? element : false; - Util.traverseUp(element, function (el) { - if (Util.isBlockContainer(el)) { - topBlock = el; - } - if (!topBlock && Util.isMediumEditorElement(el)) { - topBlock = el; - return true; - } - return false; - }); - return topBlock; - }, - - getFirstSelectableLeafNode: function (element) { - while (element && element.firstChild) { - element = element.firstChild; - } - - // We don't want to set the selection to an element that can't have children, this messes up Gecko. - element = Util.traverseUp(element, function (el) { - return Util.emptyElementNames.indexOf(el.nodeName.toLowerCase()) === -1; - }); - // Selecting at the beginning of a table doesn't work in PhantomJS. - if (element.nodeName.toLowerCase() === 'table') { - var firstCell = element.querySelector('th, td'); - if (firstCell) { - element = firstCell; - } - } - return element; - }, - - // TODO: remove getFirstTextNode AND _getFirstTextNode when jumping in 6.0.0 (no code references) - getFirstTextNode: function (element) { - Util.warn('getFirstTextNode is deprecated and will be removed in version 6.0.0'); - return Util._getFirstTextNode(element); - }, - - _getFirstTextNode: function (element) { - if (element.nodeType === 3) { - return element; - } - - for (var i = 0; i < element.childNodes.length; i++) { - var textNode = Util._getFirstTextNode(element.childNodes[i]); - if (textNode !== null) { - return textNode; - } - } - return null; - }, - - ensureUrlHasProtocol: function (url) { - if (url.indexOf('://') === -1) { - return 'http://' + url; - } - return url; - }, - - warn: function () { - if (window.console !== undefined && typeof window.console.warn === 'function') { - window.console.warn.apply(window.console, arguments); - } - }, - - deprecated: function (oldName, newName, version) { - // simple deprecation warning mechanism. - var m = oldName + ' is deprecated, please use ' + newName + ' instead.'; - if (version) { - m += ' Will be removed in ' + version; - } - Util.warn(m); - }, - - deprecatedMethod: function (oldName, newName, args, version) { - // run the replacement and warn when someone calls a deprecated method - Util.deprecated(oldName, newName, version); - if (typeof this[newName] === 'function') { - this[newName].apply(this, args); - } - }, - - cleanupAttrs: function (el, attrs) { - attrs.forEach(function (attr) { - el.removeAttribute(attr); - }); - }, - - cleanupTags: function (el, tags) { - if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) { - el.parentNode.removeChild(el); - } - }, - - unwrapTags: function (el, tags) { - if (tags.indexOf(el.nodeName.toLowerCase()) !== -1) { - MediumEditor.util.unwrap(el, document); - } - }, - - // get the closest parent - getClosestTag: function (el, tag) { - return Util.traverseUp(el, function (element) { - return element.nodeName.toLowerCase() === tag.toLowerCase(); - }); - }, - - unwrap: function (el, doc) { - var fragment = doc.createDocumentFragment(), - nodes = Array.prototype.slice.call(el.childNodes); - - // cast nodeList to array since appending child - // to a different node will alter length of el.childNodes - for (var i = 0; i < nodes.length; i++) { - fragment.appendChild(nodes[i]); - } - - if (fragment.childNodes.length) { - el.parentNode.replaceChild(fragment, el); - } else { - el.parentNode.removeChild(el); - } - }, - - guid: function () { - function _s4 () { - return Math - .floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - - return _s4() + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + '-' + _s4() + _s4() + _s4(); - } - }; - - MediumEditor.util = Util; -}(window)); - -(function () { - 'use strict'; - - var Extension = function (options) { - MediumEditor.util.extend(this, options); - }; - - Extension.extend = function (protoProps) { - // magic extender thinger. mostly borrowed from backbone/goog.inherits - // place this function on some thing you want extend-able. - // - // example: - // - // function Thing(args){ - // this.options = args; - // } - // - // Thing.prototype = { foo: "bar" }; - // Thing.extend = extenderify; - // - // var ThingTwo = Thing.extend({ foo: "baz" }); - // - // var thingOne = new Thing(); // foo === "bar" - // var thingTwo = new ThingTwo(); // foo === "baz" - // - // which seems like some simply shallow copy nonsense - // at first, but a lot more is going on there. - // - // passing a `constructor` to the extend props - // will cause the instance to instantiate through that - // instead of the parent's constructor. - - var parent = this, - child; - - // The constructor function for the new subclass is either defined by you - // (the "constructor" property in your `extend` definition), or defaulted - // by us to simply call the parent's constructor. - - if (protoProps && protoProps.hasOwnProperty('constructor')) { - child = protoProps.constructor; - } else { - child = function () { - return parent.apply(this, arguments); - }; - } - - // das statics (.extend comes over, so your subclass can have subclasses too) - MediumEditor.util.extend(child, parent); - - // Set the prototype chain to inherit from `parent`, without calling - // `parent`'s constructor function. - var Surrogate = function () { - this.constructor = child; - }; - Surrogate.prototype = parent.prototype; - child.prototype = new Surrogate(); - - if (protoProps) { - MediumEditor.util.extend(child.prototype, protoProps); - } - - // todo: $super? - - return child; - }; - - Extension.prototype = { - /* init: [function] - * - * Called by MediumEditor during initialization. - * The .base property will already have been set to - * current instance of MediumEditor when this is called. - * All helper methods will exist as well - */ - init: function () { - }, - - /* base: [MediumEditor instance] - * - * If not overridden, this will be set to the current instance - * of MediumEditor, before the init method is called - */ - base: undefined, - - /* name: [string] - * - * 'name' of the extension, used for retrieving the extension. - * If not set, MediumEditor will set this to be the key - * used when passing the extension into MediumEditor via the - * 'extensions' option - */ - name: undefined, - - /* checkState: [function (node)] - * - * If implemented, this function will be called one or more times - * the state of the editor & toolbar are updated. - * When the state is updated, the editor does the following: - * - * 1) Find the parent node containing the current selection - * 2) Call checkState on the extension, passing the node as an argument - * 3) Get the parent node of the previous node - * 4) Repeat steps #2 and #3 until we move outside the parent contenteditable - */ - checkState: undefined, - - /* destroy: [function ()] - * - * This method should remove any created html, custom event handlers - * or any other cleanup tasks that should be performed. - * If implemented, this function will be called when MediumEditor's - * destroy method has been called. - */ - destroy: undefined, - - /* As alternatives to checkState, these functions provide a more structured - * path to updating the state of an extension (usually a button) whenever - * the state of the editor & toolbar are updated. - */ - - /* queryCommandState: [function ()] - * - * If implemented, this function will be called once on each extension - * when the state of the editor/toolbar is being updated. - * - * If this function returns a non-null value, the extension will - * be ignored as the code climbs the dom tree. - * - * If this function returns true, and the setActive() function is defined - * setActive() will be called - */ - queryCommandState: undefined, - - /* isActive: [function ()] - * - * If implemented, this function will be called when MediumEditor - * has determined that this extension is 'active' for the current selection. - * This may be called when the editor & toolbar are being updated, - * but only if queryCommandState() or isAlreadyApplied() functions - * are implemented, and when called, return true. - */ - isActive: undefined, - - /* isAlreadyApplied: [function (node)] - * - * If implemented, this function is similar to checkState() in - * that it will be called repeatedly as MediumEditor moves up - * the DOM to update the editor & toolbar after a state change. - * - * NOTE: This function will NOT be called if checkState() has - * been implemented. This function will NOT be called if - * queryCommandState() is implemented and returns a non-null - * value when called - */ - isAlreadyApplied: undefined, - - /* setActive: [function ()] - * - * If implemented, this function is called when MediumEditor knows - * that this extension is currently enabled. Currently, this - * function is called when updating the editor & toolbar, and - * only if queryCommandState() or isAlreadyApplied(node) return - * true when called - */ - setActive: undefined, - - /* setInactive: [function ()] - * - * If implemented, this function is called when MediumEditor knows - * that this extension is currently disabled. Currently, this - * is called at the beginning of each state change for - * the editor & toolbar. After calling this, MediumEditor - * will attempt to update the extension, either via checkState() - * or the combination of queryCommandState(), isAlreadyApplied(node), - * isActive(), and setActive() - */ - setInactive: undefined, - - /* getInteractionElements: [function ()] - * - * If the extension renders any elements that the user can interact with, - * this method should be implemented and return the root element or an array - * containing all of the root elements. MediumEditor will call this function - * during interaction to see if the user clicked on something outside of the editor. - * The elements are used to check if the target element of a click or - * other user event is a descendant of any extension elements. - * This way, the editor can also count user interaction within editor elements as - * interactions with the editor, and thus not trigger 'blur' - */ - getInteractionElements: undefined, - - /************************ Helpers ************************ - * The following are helpers that are either set by MediumEditor - * during initialization, or are helper methods which either - * route calls to the MediumEditor instance or provide common - * functionality for all extensions - *********************************************************/ - - /* window: [Window] - * - * If not overridden, this will be set to the window object - * to be used by MediumEditor and its extensions. This is - * passed via the 'contentWindow' option to MediumEditor - * and is the global 'window' object by default - */ - 'window': undefined, - - /* document: [Document] - * - * If not overridden, this will be set to the document object - * to be used by MediumEditor and its extensions. This is - * passed via the 'ownerDocument' optin to MediumEditor - * and is the global 'document' object by default - */ - 'document': undefined, - - /* getEditorElements: [function ()] - * - * Helper function which returns an array containing - * all the contenteditable elements for this instance - * of MediumEditor - */ - getEditorElements: function () { - return this.base.elements; - }, - - /* getEditorId: [function ()] - * - * Helper function which returns a unique identifier - * for this instance of MediumEditor - */ - getEditorId: function () { - return this.base.id; - }, - - /* getEditorOptions: [function (option)] - * - * Helper function which returns the value of an option - * used to initialize this instance of MediumEditor - */ - getEditorOption: function (option) { - return this.base.options[option]; - } - }; - - /* List of method names to add to the prototype of Extension - * Each of these methods will be defined as helpers that - * just call directly into the MediumEditor instance. - * - * example for 'on' method: - * Extension.prototype.on = function () { - * return this.base.on.apply(this.base, arguments); - * } - */ - [ - // general helpers - 'execAction', - - // event handling - 'on', - 'off', - 'subscribe', - 'trigger' - - ].forEach(function (helper) { - Extension.prototype[helper] = function () { - return this.base[helper].apply(this.base, arguments); - }; - }); - - MediumEditor.Extension = Extension; -})(); - -(function () { - 'use strict'; - - function filterOnlyParentElements (node) { - if (MediumEditor.util.isBlockContainer(node)) { - return NodeFilter.FILTER_ACCEPT; - } else { - return NodeFilter.FILTER_SKIP; - } - } - - var Selection = { - findMatchingSelectionParent: function (testElementFunction, contentWindow) { - var selection = contentWindow.getSelection(), - range, - current; - - if (selection.rangeCount === 0) { - return false; - } - - range = selection.getRangeAt(0); - current = range.commonAncestorContainer; - - return MediumEditor.util.traverseUp(current, testElementFunction); - }, - - getSelectionElement: function (contentWindow) { - return this.findMatchingSelectionParent(function (el) { - return MediumEditor.util.isMediumEditorElement(el); - }, contentWindow); - }, - - // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html - // Tim Down - exportSelection: function (root, doc) { - if (!root) { - return null; - } - - var selectionState = null, - selection = doc.getSelection(); - - if (selection.rangeCount > 0) { - var range = selection.getRangeAt(0), - preSelectionRange = range.cloneRange(), - start; - - preSelectionRange.selectNodeContents(root); - preSelectionRange.setEnd(range.startContainer, range.startOffset); - start = preSelectionRange.toString().length; - - selectionState = { - start: start, - end: start + range.toString().length - }; - - // Check to see if the selection starts with any images - // if so we need to make sure the the beginning of the selection is - // set correctly when importing selection - if (this.doesRangeStartWithImages(range, doc)) { - selectionState.startsWithImage = true; - } - - // Check to see if the selection has any trailing images - // if so, this this means we need to look for them when we import selection - var trailingImageCount = this.getTrailingImageCount(root, selectionState, range.endContainer, range.endOffset); - if (trailingImageCount) { - selectionState.trailingImageCount = trailingImageCount; - } - - // If start = 0 there may still be an empty paragraph before it, but we don't care. - if (start !== 0) { - var emptyBlocksIndex = this.getIndexRelativeToAdjacentEmptyBlocks(doc, root, range.startContainer, range.startOffset); - if (emptyBlocksIndex !== -1) { - selectionState.emptyBlocksIndex = emptyBlocksIndex; - } - } - } - - return selectionState; - }, - - // http://stackoverflow.com/questions/17678843/cant-restore-selection-after-html-modify-even-if-its-the-same-html - // Tim Down - // - // {object} selectionState - the selection to import - // {DOMElement} root - the root element the selection is being restored inside of - // {Document} doc - the document to use for managing selection - // {boolean} [favorLaterSelectionAnchor] - defaults to false. If true, import the cursor immediately - // subsequent to an anchor tag if it would otherwise be placed right at the trailing edge inside the - // anchor. This cursor positioning, even though visually equivalent to the user, can affect behavior - // in MS IE. - importSelection: function (selectionState, root, doc, favorLaterSelectionAnchor) { - if (!selectionState || !root) { - return; - } - - var range = doc.createRange(); - range.setStart(root, 0); - range.collapse(true); - - var node = root, - nodeStack = [], - charIndex = 0, - foundStart = false, - foundEnd = false, - trailingImageCount = 0, - stop = false, - nextCharIndex, - allowRangeToStartAtEndOfNode = false, - lastTextNode = null; - - // When importing selection, the start of the selection may lie at the end of an element - // or at the beginning of an element. Since visually there is no difference between these 2 - // we will try to move the selection to the beginning of an element since this is generally - // what users will expect and it's a more predictable behavior. - // - // However, there are some specific cases when we don't want to do this: - // 1) We're attempting to move the cursor outside of the end of an anchor [favorLaterSelectionAnchor = true] - // 2) The selection starts with an image, which is special since an image doesn't have any 'content' - // as far as selection and ranges are concerned - // 3) The selection starts after a specified number of empty block elements (selectionState.emptyBlocksIndex) - // - // For these cases, we want the selection to start at a very specific location, so we should NOT - // automatically move the cursor to the beginning of the first actual chunk of text - if (favorLaterSelectionAnchor || selectionState.startsWithImage || typeof selectionState.emptyBlocksIndex !== 'undefined') { - allowRangeToStartAtEndOfNode = true; - } - - while (!stop && node) { - // Only iterate over elements and text nodes - if (node.nodeType > 3) { - node = nodeStack.pop(); - continue; - } - - // If we hit a text node, we need to add the amount of characters to the overall count - if (node.nodeType === 3 && !foundEnd) { - nextCharIndex = charIndex + node.length; - // Check if we're at or beyond the start of the selection we're importing - if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) { - // NOTE: We only want to allow a selection to start at the END of an element if - // allowRangeToStartAtEndOfNode is true - if (allowRangeToStartAtEndOfNode || selectionState.start < nextCharIndex) { - range.setStart(node, selectionState.start - charIndex); - foundStart = true; - } else { - // We're at the end of a text node where the selection could start but we shouldn't - // make the selection start here because allowRangeToStartAtEndOfNode is false. - // However, we should keep a reference to this node in case there aren't any more - // text nodes after this, so that we have somewhere to import the selection to - lastTextNode = node; - } - } - // We've found the start of the selection, check if we're at or beyond the end of the selection we're importing - if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) { - if (!selectionState.trailingImageCount) { - range.setEnd(node, selectionState.end - charIndex); - stop = true; - } else { - foundEnd = true; - } - } - charIndex = nextCharIndex; - } else { - if (selectionState.trailingImageCount && foundEnd) { - if (node.nodeName.toLowerCase() === 'img') { - trailingImageCount++; - } - if (trailingImageCount === selectionState.trailingImageCount) { - // Find which index the image is in its parent's children - var endIndex = 0; - while (node.parentNode.childNodes[endIndex] !== node) { - endIndex++; - } - range.setEnd(node.parentNode, endIndex + 1); - stop = true; - } - } - - if (!stop && node.nodeType === 1) { - // this is an element - // add all its children to the stack - var i = node.childNodes.length - 1; - while (i >= 0) { - nodeStack.push(node.childNodes[i]); - i -= 1; - } - } - } - - if (!stop) { - node = nodeStack.pop(); - } - } - - // If we've gone through the entire text but didn't find the beginning of a text node - // to make the selection start at, we should fall back to starting the selection - // at the END of the last text node we found - if (!foundStart && lastTextNode) { - range.setStart(lastTextNode, lastTextNode.length); - range.setEnd(lastTextNode, lastTextNode.length); - } - - if (typeof selectionState.emptyBlocksIndex !== 'undefined') { - range = this.importSelectionMoveCursorPastBlocks(doc, root, selectionState.emptyBlocksIndex, range); - } - - // If the selection is right at the ending edge of a link, put it outside the anchor tag instead of inside. - if (favorLaterSelectionAnchor) { - range = this.importSelectionMoveCursorPastAnchor(selectionState, range); - } - - this.selectRange(doc, range); - }, - - // Utility method called from importSelection only - importSelectionMoveCursorPastAnchor: function (selectionState, range) { - var nodeInsideAnchorTagFunction = function (node) { - return node.nodeName.toLowerCase() === 'a'; - }; - if (selectionState.start === selectionState.end && - range.startContainer.nodeType === 3 && - range.startOffset === range.startContainer.nodeValue.length && - MediumEditor.util.traverseUp(range.startContainer, nodeInsideAnchorTagFunction)) { - var prevNode = range.startContainer, - currentNode = range.startContainer.parentNode; - while (currentNode !== null && currentNode.nodeName.toLowerCase() !== 'a') { - if (currentNode.childNodes[currentNode.childNodes.length - 1] !== prevNode) { - currentNode = null; - } else { - prevNode = currentNode; - currentNode = currentNode.parentNode; - } - } - if (currentNode !== null && currentNode.nodeName.toLowerCase() === 'a') { - var currentNodeIndex = null; - for (var i = 0; currentNodeIndex === null && i < currentNode.parentNode.childNodes.length; i++) { - if (currentNode.parentNode.childNodes[i] === currentNode) { - currentNodeIndex = i; - } - } - range.setStart(currentNode.parentNode, currentNodeIndex + 1); - range.collapse(true); - } - } - return range; - }, - - // Uses the emptyBlocksIndex calculated by getIndexRelativeToAdjacentEmptyBlocks - // to move the cursor back to the start of the correct paragraph - importSelectionMoveCursorPastBlocks: function (doc, root, index, range) { - var treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false), - startContainer = range.startContainer, - startBlock, - targetNode, - currIndex = 0; - index = index || 1; // If index is 0, we still want to move to the next block - - // Chrome counts newlines and spaces that separate block elements as actual elements. - // If the selection is inside one of these text nodes, and it has a previous sibling - // which is a block element, we want the treewalker to start at the previous sibling - // and NOT at the parent of the textnode - if (startContainer.nodeType === 3 && MediumEditor.util.isBlockContainer(startContainer.previousSibling)) { - startBlock = startContainer.previousSibling; - } else { - startBlock = MediumEditor.util.getClosestBlockContainer(startContainer); - } - - // Skip over empty blocks until we hit the block we want the selection to be in - while (treeWalker.nextNode()) { - if (!targetNode) { - // Loop through all blocks until we hit the starting block element - if (startBlock === treeWalker.currentNode) { - targetNode = treeWalker.currentNode; - } - } else { - targetNode = treeWalker.currentNode; - currIndex++; - // We hit the target index, bail - if (currIndex === index) { - break; - } - // If we find a non-empty block, ignore the emptyBlocksIndex and just put selection here - if (targetNode.textContent.length > 0) { - break; - } - } - } - - if (!targetNode) { - targetNode = startBlock; - } - - // We're selecting a high-level block node, so make sure the cursor gets moved into the deepest - // element at the beginning of the block - range.setStart(MediumEditor.util.getFirstSelectableLeafNode(targetNode), 0); - - return range; - }, - - // Returns -1 unless the cursor is at the beginning of a paragraph/block - // If the paragraph/block is preceded by empty paragraphs/block (with no text) - // it will return the number of empty paragraphs before the cursor. - // Otherwise, it will return 0, which indicates the cursor is at the beginning - // of a paragraph/block, and not at the end of the paragraph/block before it - getIndexRelativeToAdjacentEmptyBlocks: function (doc, root, cursorContainer, cursorOffset) { - // If there is text in front of the cursor, that means there isn't only empty blocks before it - if (cursorContainer.textContent.length > 0 && cursorOffset > 0) { - return -1; - } - - // Check if the block that contains the cursor has any other text in front of the cursor - var node = cursorContainer; - if (node.nodeType !== 3) { - node = cursorContainer.childNodes[cursorOffset]; - } - if (node) { - // The element isn't at the beginning of a block, so it has content before it - if (!MediumEditor.util.isElementAtBeginningOfBlock(node)) { - return -1; - } - - var previousSibling = MediumEditor.util.findPreviousSibling(node); - // If there is no previous sibling, this is the first text element in the editor - if (!previousSibling) { - return -1; - } else if (previousSibling.nodeValue) { - // If the previous sibling has text, then there are no empty blocks before this - return -1; - } - } - - // Walk over block elements, counting number of empty blocks between last piece of text - // and the block the cursor is in - var closestBlock = MediumEditor.util.getClosestBlockContainer(cursorContainer), - treeWalker = doc.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, filterOnlyParentElements, false), - emptyBlocksCount = 0; - while (treeWalker.nextNode()) { - var blockIsEmpty = treeWalker.currentNode.textContent === ''; - if (blockIsEmpty || emptyBlocksCount > 0) { - emptyBlocksCount += 1; - } - if (treeWalker.currentNode === closestBlock) { - return emptyBlocksCount; - } - if (!blockIsEmpty) { - emptyBlocksCount = 0; - } - } - - return emptyBlocksCount; - }, - - // Returns true if the selection range begins with an image tag - // Returns false if the range starts with any non empty text nodes - doesRangeStartWithImages: function (range, doc) { - if (range.startOffset !== 0 || range.startContainer.nodeType !== 1) { - return false; - } - - if (range.startContainer.nodeName.toLowerCase() === 'img') { - return true; - } - - var img = range.startContainer.querySelector('img'); - if (!img) { - return false; - } - - var treeWalker = doc.createTreeWalker(range.startContainer, NodeFilter.SHOW_ALL, null, false); - while (treeWalker.nextNode()) { - var next = treeWalker.currentNode; - // If we hit the image, then there isn't any text before the image so - // the image is at the beginning of the range - if (next === img) { - break; - } - // If we haven't hit the image, but found text that contains content - // then the range doesn't start with an image - if (next.nodeValue) { - return false; - } - } - - return true; - }, - - getTrailingImageCount: function (root, selectionState, endContainer, endOffset) { - // If the endOffset of a range is 0, the endContainer doesn't contain images - // If the endContainer is a text node, there are no trailing images - if (endOffset === 0 || endContainer.nodeType !== 1) { - return 0; - } - - // If the endContainer isn't an image, and doesn't have an image descendants - // there are no trailing images - if (endContainer.nodeName.toLowerCase() !== 'img' && !endContainer.querySelector('img')) { - return 0; - } - - var lastNode = endContainer.childNodes[endOffset - 1]; - while (lastNode.hasChildNodes()) { - lastNode = lastNode.lastChild; - } - - var node = root, - nodeStack = [], - charIndex = 0, - foundStart = false, - foundEnd = false, - stop = false, - nextCharIndex, - trailingImages = 0; - - while (!stop && node) { - // Only iterate over elements and text nodes - if (node.nodeType > 3) { - node = nodeStack.pop(); - continue; - } - - if (node.nodeType === 3 && !foundEnd) { - trailingImages = 0; - nextCharIndex = charIndex + node.length; - if (!foundStart && selectionState.start >= charIndex && selectionState.start <= nextCharIndex) { - foundStart = true; - } - if (foundStart && selectionState.end >= charIndex && selectionState.end <= nextCharIndex) { - foundEnd = true; - } - charIndex = nextCharIndex; - } else { - if (node.nodeName.toLowerCase() === 'img') { - trailingImages++; - } - - if (node === lastNode) { - stop = true; - } else if (node.nodeType === 1) { - // this is an element - // add all its children to the stack - var i = node.childNodes.length - 1; - while (i >= 0) { - nodeStack.push(node.childNodes[i]); - i -= 1; - } - } - } - - if (!stop) { - node = nodeStack.pop(); - } - } - - return trailingImages; - }, - - // determine if the current selection contains any 'content' - // content being any non-white space text or an image - selectionContainsContent: function (doc) { - var sel = doc.getSelection(); - - // collapsed selection or selection without range doesn't contain content - if (!sel || sel.isCollapsed || !sel.rangeCount) { - return false; - } - - // if toString() contains any text, the selection contains some content - if (sel.toString().trim() !== '') { - return true; - } - - // if selection contains only image(s), it will return empty for toString() - // so check for an image manually - var selectionNode = this.getSelectedParentElement(sel.getRangeAt(0)); - if (selectionNode) { - if (selectionNode.nodeName.toLowerCase() === 'img' || - (selectionNode.nodeType === 1 && selectionNode.querySelector('img'))) { - return true; - } - } - - return false; - }, - - selectionInContentEditableFalse: function (contentWindow) { - // determine if the current selection is exclusively inside - // a contenteditable="false", though treat the case of an - // explicit contenteditable="true" inside a "false" as false. - var sawtrue, - sawfalse = this.findMatchingSelectionParent(function (el) { - var ce = el && el.getAttribute('contenteditable'); - if (ce === 'true') { - sawtrue = true; - } - return el.nodeName !== '#text' && ce === 'false'; - }, contentWindow); - - return !sawtrue && sawfalse; - }, - - // http://stackoverflow.com/questions/4176923/html-of-selected-text - // by Tim Down - getSelectionHtml: function getSelectionHtml (doc) { - var i, - html = '', - sel = doc.getSelection(), - len, - container; - if (sel.rangeCount) { - container = doc.createElement('div'); - for (i = 0, len = sel.rangeCount; i < len; i += 1) { - container.appendChild(sel.getRangeAt(i).cloneContents()); - } - html = container.innerHTML; - } - return html; - }, - - /** - * Find the caret position within an element irrespective of any inline tags it may contain. - * - * @param {DOMElement} An element containing the cursor to find offsets relative to. - * @param {Range} A Range representing cursor position. Will window.getSelection if none is passed. - * @return {Object} 'left' and 'right' attributes contain offsets from beginning and end of Element - */ - getCaretOffsets: function getCaretOffsets (element, range) { - var preCaretRange, postCaretRange; - - if (!range) { - range = window.getSelection().getRangeAt(0); - } - - preCaretRange = range.cloneRange(); - postCaretRange = range.cloneRange(); - - preCaretRange.selectNodeContents(element); - preCaretRange.setEnd(range.endContainer, range.endOffset); - - postCaretRange.selectNodeContents(element); - postCaretRange.setStart(range.endContainer, range.endOffset); - - return { - left: preCaretRange.toString().length, - right: postCaretRange.toString().length - }; - }, - - // http://stackoverflow.com/questions/15867542/range-object-get-selection-parent-node-chrome-vs-firefox - rangeSelectsSingleNode: function (range) { - var startNode = range.startContainer; - return startNode === range.endContainer && - startNode.hasChildNodes() && - range.endOffset === range.startOffset + 1; - }, - - getSelectedParentElement: function (range) { - if (!range) { - return null; - } - - // Selection encompasses a single element - if (this.rangeSelectsSingleNode(range) && range.startContainer.childNodes[range.startOffset].nodeType !== 3) { - return range.startContainer.childNodes[range.startOffset]; - } - - // Selection range starts inside a text node, so get its parent - if (range.startContainer.nodeType === 3) { - return range.startContainer.parentNode; - } - - // Selection starts inside an element - return range.startContainer; - }, - - getSelectedElements: function (doc) { - var selection = doc.getSelection(), - range, - toRet, - currNode; - - if (!selection.rangeCount || selection.isCollapsed || !selection.getRangeAt(0).commonAncestorContainer) { - return []; - } - - range = selection.getRangeAt(0); - - if (range.commonAncestorContainer.nodeType === 3) { - toRet = []; - currNode = range.commonAncestorContainer; - while (currNode.parentNode && currNode.parentNode.childNodes.length === 1) { - toRet.push(currNode.parentNode); - currNode = currNode.parentNode; - } - - return toRet; - } - - return [].filter.call(range.commonAncestorContainer.getElementsByTagName('*'), function (el) { - return (typeof selection.containsNode === 'function') ? selection.containsNode(el, true) : true; - }); - }, - - selectNode: function (node, doc) { - var range = doc.createRange(); - range.selectNodeContents(node); - this.selectRange(doc, range); - }, - - select: function (doc, startNode, startOffset, endNode, endOffset) { - var range = doc.createRange(); - range.setStart(startNode, startOffset); - if (endNode) { - range.setEnd(endNode, endOffset); - } else { - range.collapse(true); - } - this.selectRange(doc, range); - return range; - }, - - /** - * Clear the current highlighted selection and set the caret to the start or the end of that prior selection, defaults to end. - * - * @param {DomDocument} doc Current document - * @param {boolean} moveCursorToStart A boolean representing whether or not to set the caret to the beginning of the prior selection. - */ - clearSelection: function (doc, moveCursorToStart) { - if (moveCursorToStart) { - doc.getSelection().collapseToStart(); - } else { - doc.getSelection().collapseToEnd(); - } - }, - - /** - * Move cursor to the given node with the given offset. - * - * @param {DomDocument} doc Current document - * @param {DomElement} node Element where to jump - * @param {integer} offset Where in the element should we jump, 0 by default - */ - moveCursor: function (doc, node, offset) { - this.select(doc, node, offset); - }, - - getSelectionRange: function (ownerDocument) { - var selection = ownerDocument.getSelection(); - if (selection.rangeCount === 0) { - return null; - } - return selection.getRangeAt(0); - }, - - selectRange: function (ownerDocument, range) { - var selection = ownerDocument.getSelection(); - - selection.removeAllRanges(); - selection.addRange(range); - }, - - // http://stackoverflow.com/questions/1197401/how-can-i-get-the-element-the-caret-is-in-with-javascript-when-using-contentedi - // by You - getSelectionStart: function (ownerDocument) { - var node = ownerDocument.getSelection().anchorNode, - startNode = (node && node.nodeType === 3 ? node.parentNode : node); - - return startNode; - } - }; - - MediumEditor.selection = Selection; -}()); - -(function () { - 'use strict'; - - function isElementDescendantOfExtension (extensions, element) { - if (!extensions) { - return false; - } - - return extensions.some(function (extension) { - if (typeof extension.getInteractionElements !== 'function') { - return false; - } - - var extensionElements = extension.getInteractionElements(); - if (!extensionElements) { - return false; - } - - if (!Array.isArray(extensionElements)) { - extensionElements = [extensionElements]; - } - return extensionElements.some(function (el) { - return MediumEditor.util.isDescendant(el, element, true); - }); - }); - } - - var Events = function (instance) { - this.base = instance; - this.options = this.base.options; - this.events = []; - this.disabledEvents = {}; - this.customEvents = {}; - this.listeners = {}; - }; - - Events.prototype = { - InputEventOnContenteditableSupported: !MediumEditor.util.isIE && !MediumEditor.util.isEdge, - - // Helpers for event handling - - attachDOMEvent: function (targets, event, listener, useCapture) { - var win = this.base.options.contentWindow, - doc = this.base.options.ownerDocument; - - targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets; - - Array.prototype.forEach.call(targets, function (target) { - target.addEventListener(event, listener, useCapture); - this.events.push([target, event, listener, useCapture]); - }.bind(this)); - }, - - detachDOMEvent: function (targets, event, listener, useCapture) { - var index, e, - win = this.base.options.contentWindow, - doc = this.base.options.ownerDocument; - - if (targets) { - targets = MediumEditor.util.isElement(targets) || [win, doc].indexOf(targets) > -1 ? [targets] : targets; - - Array.prototype.forEach.call(targets, function (target) { - index = this.indexOfListener(target, event, listener, useCapture); - if (index !== -1) { - e = this.events.splice(index, 1)[0]; - e[0].removeEventListener(e[1], e[2], e[3]); - } - }.bind(this)); - } - }, - - indexOfListener: function (target, event, listener, useCapture) { - var i, n, item; - for (i = 0, n = this.events.length; i < n; i = i + 1) { - item = this.events[i]; - if (item[0] === target && item[1] === event && item[2] === listener && item[3] === useCapture) { - return i; - } - } - return -1; - }, - - detachAllDOMEvents: function () { - var e = this.events.pop(); - while (e) { - e[0].removeEventListener(e[1], e[2], e[3]); - e = this.events.pop(); - } - }, - - detachAllEventsFromElement: function (element) { - var filtered = this.events.filter(function (e) { - return e && e[0].getAttribute && e[0].getAttribute('medium-editor-index') === element.getAttribute('medium-editor-index'); - }); - - for (var i = 0, len = filtered.length; i < len; i++) { - var e = filtered[i]; - this.detachDOMEvent(e[0], e[1], e[2], e[3]); - } - }, - - // Attach all existing handlers to a new element - attachAllEventsToElement: function (element) { - if (this.listeners['editableInput']) { - this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML; - } - - if (this.eventsCache) { - this.eventsCache.forEach(function (e) { - this.attachDOMEvent(element, e['name'], e['handler'].bind(this)); - }, this); - } - }, - - enableCustomEvent: function (event) { - if (this.disabledEvents[event] !== undefined) { - delete this.disabledEvents[event]; - } - }, - - disableCustomEvent: function (event) { - this.disabledEvents[event] = true; - }, - - // custom events - attachCustomEvent: function (event, listener) { - this.setupListener(event); - if (!this.customEvents[event]) { - this.customEvents[event] = []; - } - this.customEvents[event].push(listener); - }, - - detachCustomEvent: function (event, listener) { - var index = this.indexOfCustomListener(event, listener); - if (index !== -1) { - this.customEvents[event].splice(index, 1); - // TODO: If array is empty, should detach internal listeners via destroyListener() - } - }, - - indexOfCustomListener: function (event, listener) { - if (!this.customEvents[event] || !this.customEvents[event].length) { - return -1; - } - - return this.customEvents[event].indexOf(listener); - }, - - detachAllCustomEvents: function () { - this.customEvents = {}; - // TODO: Should detach internal listeners here via destroyListener() - }, - - triggerCustomEvent: function (name, data, editable) { - if (this.customEvents[name] && !this.disabledEvents[name]) { - this.customEvents[name].forEach(function (listener) { - listener(data, editable); - }); - } - }, - - // Cleaning up - - destroy: function () { - this.detachAllDOMEvents(); - this.detachAllCustomEvents(); - this.detachExecCommand(); - - if (this.base.elements) { - this.base.elements.forEach(function (element) { - element.removeAttribute('data-medium-focused'); - }); - } - }, - - // Listening to calls to document.execCommand - - // Attach a listener to be notified when document.execCommand is called - attachToExecCommand: function () { - if (this.execCommandListener) { - return; - } - - // Store an instance of the listener so: - // 1) We only attach to execCommand once - // 2) We can remove the listener later - this.execCommandListener = function (execInfo) { - this.handleDocumentExecCommand(execInfo); - }.bind(this); - - // Ensure that execCommand has been wrapped correctly - this.wrapExecCommand(); - - // Add listener to list of execCommand listeners - this.options.ownerDocument.execCommand.listeners.push(this.execCommandListener); - }, - - // Remove our listener for calls to document.execCommand - detachExecCommand: function () { - var doc = this.options.ownerDocument; - if (!this.execCommandListener || !doc.execCommand.listeners) { - return; - } - - // Find the index of this listener in the array of listeners so it can be removed - var index = doc.execCommand.listeners.indexOf(this.execCommandListener); - if (index !== -1) { - doc.execCommand.listeners.splice(index, 1); - } - - // If the list of listeners is now empty, put execCommand back to its original state - if (!doc.execCommand.listeners.length) { - this.unwrapExecCommand(); - } - }, - - // Wrap document.execCommand in a custom method so we can listen to calls to it - wrapExecCommand: function () { - var doc = this.options.ownerDocument; - - // Ensure all instance of MediumEditor only wrap execCommand once - if (doc.execCommand.listeners) { - return; - } - - // Helper method to call all listeners to execCommand - var callListeners = function (args, result) { - if (doc.execCommand.listeners) { - doc.execCommand.listeners.forEach(function (listener) { - listener({ - command: args[0], - value: args[2], - args: args, - result: result - }); - }); - } - }, - - // Create a wrapper method for execCommand which will: - // 1) Call document.execCommand with the correct arguments - // 2) Loop through any listeners and notify them that execCommand was called - // passing extra info on the call - // 3) Return the result - wrapper = function () { - var result = doc.execCommand.orig.apply(this, arguments); - - if (!doc.execCommand.listeners) { - return result; - } - - var args = Array.prototype.slice.call(arguments); - callListeners(args, result); - - return result; - }; - - // Store a reference to the original execCommand - wrapper.orig = doc.execCommand; - - // Attach an array for storing listeners - wrapper.listeners = []; - - // Helper for notifying listeners - wrapper.callListeners = callListeners; - - // Overwrite execCommand - doc.execCommand = wrapper; - }, - - // Revert document.execCommand back to its original self - unwrapExecCommand: function () { - var doc = this.options.ownerDocument; - if (!doc.execCommand.orig) { - return; - } - - // Use the reference to the original execCommand to revert back - doc.execCommand = doc.execCommand.orig; - }, - - // Listening to browser events to emit events medium-editor cares about - setupListener: function (name) { - if (this.listeners[name]) { - return; - } - - switch (name) { - case 'externalInteraction': - // Detecting when user has interacted with elements outside of MediumEditor - this.attachDOMEvent(this.options.ownerDocument.body, 'mousedown', this.handleBodyMousedown.bind(this), true); - this.attachDOMEvent(this.options.ownerDocument.body, 'click', this.handleBodyClick.bind(this), true); - this.attachDOMEvent(this.options.ownerDocument.body, 'focus', this.handleBodyFocus.bind(this), true); - break; - case 'blur': - // Detecting when focus is lost - this.setupListener('externalInteraction'); - break; - case 'focus': - // Detecting when focus moves into some part of MediumEditor - this.setupListener('externalInteraction'); - break; - case 'editableInput': - // setup cache for knowing when the content has changed - this.contentCache = {}; - this.base.elements.forEach(function (element) { - this.contentCache[element.getAttribute('medium-editor-index')] = element.innerHTML; - }, this); - - // Attach to the 'oninput' event, handled correctly by most browsers - if (this.InputEventOnContenteditableSupported) { - this.attachToEachElement('input', this.handleInput); - } - - // For browsers which don't support the input event on contenteditable (IE) - // we'll attach to 'selectionchange' on the document and 'keypress' on the editables - if (!this.InputEventOnContenteditableSupported) { - this.setupListener('editableKeypress'); - this.keypressUpdateInput = true; - this.attachDOMEvent(document, 'selectionchange', this.handleDocumentSelectionChange.bind(this)); - // Listen to calls to execCommand - this.attachToExecCommand(); - } - break; - case 'editableClick': - // Detecting click in the contenteditables - this.attachToEachElement('click', this.handleClick); - break; - case 'editableBlur': - // Detecting blur in the contenteditables - this.attachToEachElement('blur', this.handleBlur); - break; - case 'editableKeypress': - // Detecting keypress in the contenteditables - this.attachToEachElement('keypress', this.handleKeypress); - break; - case 'editableKeyup': - // Detecting keyup in the contenteditables - this.attachToEachElement('keyup', this.handleKeyup); - break; - case 'editableKeydown': - // Detecting keydown on the contenteditables - this.attachToEachElement('keydown', this.handleKeydown); - break; - case 'editableKeydownSpace': - // Detecting keydown for SPACE on the contenteditables - this.setupListener('editableKeydown'); - break; - case 'editableKeydownEnter': - // Detecting keydown for ENTER on the contenteditables - this.setupListener('editableKeydown'); - break; - case 'editableKeydownTab': - // Detecting keydown for TAB on the contenteditable - this.setupListener('editableKeydown'); - break; - case 'editableKeydownDelete': - // Detecting keydown for DELETE/BACKSPACE on the contenteditables - this.setupListener('editableKeydown'); - break; - case 'editableMouseover': - // Detecting mouseover on the contenteditables - this.attachToEachElement('mouseover', this.handleMouseover); - break; - case 'editableDrag': - // Detecting dragover and dragleave on the contenteditables - this.attachToEachElement('dragover', this.handleDragging); - this.attachToEachElement('dragleave', this.handleDragging); - break; - case 'editableDrop': - // Detecting drop on the contenteditables - this.attachToEachElement('drop', this.handleDrop); - break; - // TODO: We need to have a custom 'paste' event separate from 'editablePaste' - // Need to think about the way to introduce this without breaking folks - case 'editablePaste': - // Detecting paste on the contenteditables - this.attachToEachElement('paste', this.handlePaste); - break; - } - this.listeners[name] = true; - }, - - attachToEachElement: function (name, handler) { - // build our internal cache to know which element got already what handler attached - if (!this.eventsCache) { - this.eventsCache = []; - } - - this.base.elements.forEach(function (element) { - this.attachDOMEvent(element, name, handler.bind(this)); - }, this); - - this.eventsCache.push({ 'name': name, 'handler': handler }); - }, - - cleanupElement: function (element) { - var index = element.getAttribute('medium-editor-index'); - if (index) { - this.detachAllEventsFromElement(element); - if (this.contentCache) { - delete this.contentCache[index]; - } - } - }, - - focusElement: function (element) { - element.focus(); - this.updateFocus(element, { target: element, type: 'focus' }); - }, - - updateFocus: function (target, eventObj) { - var hadFocus = this.base.getFocusedElement(), - toFocus; - - // For clicks, we need to know if the mousedown that caused the click happened inside the existing focused element - // or one of the extension elements. If so, we don't want to focus another element - if (hadFocus && - eventObj.type === 'click' && - this.lastMousedownTarget && - (MediumEditor.util.isDescendant(hadFocus, this.lastMousedownTarget, true) || - isElementDescendantOfExtension(this.base.extensions, this.lastMousedownTarget))) { - toFocus = hadFocus; - } - - if (!toFocus) { - this.base.elements.some(function (element) { - // If the target is part of an editor element, this is the element getting focus - if (!toFocus && (MediumEditor.util.isDescendant(element, target, true))) { - toFocus = element; - } - - // bail if we found an element that's getting focus - return !!toFocus; - }, this); - } - - // Check if the target is external (not part of the editor, toolbar, or any other extension) - var externalEvent = !MediumEditor.util.isDescendant(hadFocus, target, true) && - !isElementDescendantOfExtension(this.base.extensions, target); - - if (toFocus !== hadFocus) { - // If element has focus, and focus is going outside of editor - // Don't blur focused element if clicking on editor, toolbar, or anchorpreview - if (hadFocus && externalEvent) { - // Trigger blur on the editable that has lost focus - hadFocus.removeAttribute('data-medium-focused'); - this.triggerCustomEvent('blur', eventObj, hadFocus); - } - - // If focus is going into an editor element - if (toFocus) { - // Trigger focus on the editable that now has focus - toFocus.setAttribute('data-medium-focused', true); - this.triggerCustomEvent('focus', eventObj, toFocus); - } - } - - if (externalEvent) { - this.triggerCustomEvent('externalInteraction', eventObj); - } - }, - - updateInput: function (target, eventObj) { - if (!this.contentCache) { - return; - } - // An event triggered which signifies that the user may have changed something - // Look in our cache of input for the contenteditables to see if something changed - var index = target.getAttribute('medium-editor-index'), - html = target.innerHTML; - - if (html !== this.contentCache[index]) { - // The content has changed since the last time we checked, fire the event - this.triggerCustomEvent('editableInput', eventObj, target); - } - this.contentCache[index] = html; - }, - - handleDocumentSelectionChange: function (event) { - // When selectionchange fires, target and current target are set - // to document, since this is where the event is handled - // However, currentTarget will have an 'activeElement' property - // which will point to whatever element has focus. - if (event.currentTarget && event.currentTarget.activeElement) { - var activeElement = event.currentTarget.activeElement, - currentTarget; - // We can look at the 'activeElement' to determine if the selectionchange has - // happened within a contenteditable owned by this instance of MediumEditor - this.base.elements.some(function (element) { - if (MediumEditor.util.isDescendant(element, activeElement, true)) { - currentTarget = element; - return true; - } - return false; - }, this); - - // We know selectionchange fired within one of our contenteditables - if (currentTarget) { - this.updateInput(currentTarget, { target: activeElement, currentTarget: currentTarget }); - } - } - }, - - handleDocumentExecCommand: function () { - // document.execCommand has been called - // If one of our contenteditables currently has focus, we should - // attempt to trigger the 'editableInput' event - var target = this.base.getFocusedElement(); - if (target) { - this.updateInput(target, { target: target, currentTarget: target }); - } - }, - - handleBodyClick: function (event) { - this.updateFocus(event.target, event); - }, - - handleBodyFocus: function (event) { - this.updateFocus(event.target, event); - }, - - handleBodyMousedown: function (event) { - this.lastMousedownTarget = event.target; - }, - - handleInput: function (event) { - this.updateInput(event.currentTarget, event); - }, - - handleClick: function (event) { - this.triggerCustomEvent('editableClick', event, event.currentTarget); - }, - - handleBlur: function (event) { - this.triggerCustomEvent('editableBlur', event, event.currentTarget); - }, - - handleKeypress: function (event) { - this.triggerCustomEvent('editableKeypress', event, event.currentTarget); - - // If we're doing manual detection of the editableInput event we need - // to check for input changes during 'keypress' - if (this.keypressUpdateInput) { - var eventObj = { target: event.target, currentTarget: event.currentTarget }; - - // In IE, we need to let the rest of the event stack complete before we detect - // changes to input, so using setTimeout here - setTimeout(function () { - this.updateInput(eventObj.currentTarget, eventObj); - }.bind(this), 0); - } - }, - - handleKeyup: function (event) { - this.triggerCustomEvent('editableKeyup', event, event.currentTarget); - }, - - handleMouseover: function (event) { - this.triggerCustomEvent('editableMouseover', event, event.currentTarget); - }, - - handleDragging: function (event) { - this.triggerCustomEvent('editableDrag', event, event.currentTarget); - }, - - handleDrop: function (event) { - this.triggerCustomEvent('editableDrop', event, event.currentTarget); - }, - - handlePaste: function (event) { - this.triggerCustomEvent('editablePaste', event, event.currentTarget); - }, - - handleKeydown: function (event) { - - this.triggerCustomEvent('editableKeydown', event, event.currentTarget); - - if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.SPACE)) { - return this.triggerCustomEvent('editableKeydownSpace', event, event.currentTarget); - } - - if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) || (event.ctrlKey && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.M))) { - return this.triggerCustomEvent('editableKeydownEnter', event, event.currentTarget); - } - - if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.TAB)) { - return this.triggerCustomEvent('editableKeydownTab', event, event.currentTarget); - } - - if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.DELETE, MediumEditor.util.keyCode.BACKSPACE])) { - return this.triggerCustomEvent('editableKeydownDelete', event, event.currentTarget); - } - } - }; - - MediumEditor.Events = Events; -}()); - -(function () { - 'use strict'; - - var Button = MediumEditor.Extension.extend({ - - /* Button Options */ - - /* action: [string] - * The action argument to pass to MediumEditor.execAction() - * when the button is clicked - */ - action: undefined, - - /* aria: [string] - * The value to add as the aria-label attribute of the button - * element displayed in the toolbar. - * This is also used as the tooltip for the button - */ - aria: undefined, - - /* tagNames: [Array] - * NOTE: This is not used if useQueryState is set to true. - * - * Array of element tag names that would indicate that this - * button has already been applied. If this action has already - * been applied, the button will be displayed as 'active' in the toolbar - * - * Example: - * For 'bold', if the text is ever within a or - * tag that indicates the text is already bold. So the array - * of tagNames for bold would be: ['b', 'strong'] - */ - tagNames: undefined, - - /* style: [Object] - * NOTE: This is not used if useQueryState is set to true. - * - * A pair of css property & value(s) that indicate that this - * button has already been applied. If this action has already - * been applied, the button will be displayed as 'active' in the toolbar - * Properties of the object: - * prop [String]: name of the css property - * value [String]: value(s) of the css property - * multiple values can be separated by a '|' - * - * Example: - * For 'bold', if the text is ever within an element with a 'font-weight' - * style property set to '700' or 'bold', that indicates the text - * is already bold. So the style object for bold would be: - * { prop: 'font-weight', value: '700|bold' } - */ - style: undefined, - - /* useQueryState: [boolean] - * Enables/disables whether this button should use the built-in - * document.queryCommandState() method to determine whether - * the action has already been applied. If the action has already - * been applied, the button will be displayed as 'active' in the toolbar - * - * Example: - * For 'bold', if this is set to true, the code will call: - * document.queryCommandState('bold') which will return true if the - * browser thinks the text is already bold, and false otherwise - */ - useQueryState: undefined, - - /* contentDefault: [string] - * Default innerHTML to put inside the button - */ - contentDefault: undefined, - - /* contentFA: [string] - * The innerHTML to use for the content of the button - * if the `buttonLabels` option for MediumEditor is set to 'fontawesome' - */ - contentFA: undefined, - - /* classList: [Array] - * An array of classNames (strings) to be added to the button - */ - classList: undefined, - - /* attrs: [object] - * A set of key-value pairs to add to the button as custom attributes - */ - attrs: undefined, - - // The button constructor can optionally accept the name of a built-in button - // (ie 'bold', 'italic', etc.) - // When the name of a button is passed, it will initialize itself with the - // configuration for that button - constructor: function (options) { - if (Button.isBuiltInButton(options)) { - MediumEditor.Extension.call(this, this.defaults[options]); - } else { - MediumEditor.Extension.call(this, options); - } - }, - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.button = this.createButton(); - this.on(this.button, 'click', this.handleClick.bind(this)); - }, - - /* getButton: [function ()] - * - * If implemented, this function will be called when - * the toolbar is being created. The DOM Element returned - * by this function will be appended to the toolbar along - * with any other buttons. - */ - getButton: function () { - return this.button; - }, - - getAction: function () { - return (typeof this.action === 'function') ? this.action(this.base.options) : this.action; - }, - - getAria: function () { - return (typeof this.aria === 'function') ? this.aria(this.base.options) : this.aria; - }, - - getTagNames: function () { - return (typeof this.tagNames === 'function') ? this.tagNames(this.base.options) : this.tagNames; - }, - - createButton: function () { - var button = this.document.createElement('button'), - content = this.contentDefault, - ariaLabel = this.getAria(), - buttonLabels = this.getEditorOption('buttonLabels'); - // Add class names - button.classList.add('medium-editor-action'); - button.classList.add('medium-editor-action-' + this.name); - if (this.classList) { - this.classList.forEach(function (className) { - button.classList.add(className); - }); - } - - // Add attributes - button.setAttribute('data-action', this.getAction()); - if (ariaLabel) { - button.setAttribute('title', ariaLabel); - button.setAttribute('aria-label', ariaLabel); - } - if (this.attrs) { - Object.keys(this.attrs).forEach(function (attr) { - button.setAttribute(attr, this.attrs[attr]); - }, this); - } - - if (buttonLabels === 'fontawesome' && this.contentFA) { - content = this.contentFA; - } - button.innerHTML = content; - return button; - }, - - handleClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - - var action = this.getAction(); - - if (action) { - this.execAction(action); - } - }, - - isActive: function () { - return this.button.classList.contains(this.getEditorOption('activeButtonClass')); - }, - - setInactive: function () { - this.button.classList.remove(this.getEditorOption('activeButtonClass')); - delete this.knownState; - }, - - setActive: function () { - this.button.classList.add(this.getEditorOption('activeButtonClass')); - delete this.knownState; - }, - - queryCommandState: function () { - var queryState = null; - if (this.useQueryState) { - queryState = this.base.queryCommandState(this.getAction()); - } - return queryState; - }, - - isAlreadyApplied: function (node) { - var isMatch = false, - tagNames = this.getTagNames(), - styleVals, - computedStyle; - - if (this.knownState === false || this.knownState === true) { - return this.knownState; - } - - if (tagNames && tagNames.length > 0) { - isMatch = tagNames.indexOf(node.nodeName.toLowerCase()) !== -1; - } - - if (!isMatch && this.style) { - styleVals = this.style.value.split('|'); - computedStyle = this.window.getComputedStyle(node, null).getPropertyValue(this.style.prop); - styleVals.forEach(function (val) { - if (!this.knownState) { - isMatch = (computedStyle.indexOf(val) !== -1); - // text-decoration is not inherited by default - // so if the computed style for text-decoration doesn't match - // don't write to knownState so we can fallback to other checks - if (isMatch || this.style.prop !== 'text-decoration') { - this.knownState = isMatch; - } - } - }, this); - } - - return isMatch; - } - }); - - Button.isBuiltInButton = function (name) { - return (typeof name === 'string') && MediumEditor.extensions.button.prototype.defaults.hasOwnProperty(name); - }; - - MediumEditor.extensions.button = Button; -}()); - -(function () { - 'use strict'; - - /* MediumEditor.extensions.button.defaults: [Object] - * Set of default config options for all of the built-in MediumEditor buttons - */ - MediumEditor.extensions.button.prototype.defaults = { - 'bold': { - name: 'bold', - action: 'bold', - aria: 'bold', - tagNames: ['b', 'strong'], - style: { - prop: 'font-weight', - value: '700|bold' - }, - useQueryState: true, - contentDefault: 'B', - contentFA: '' - }, - 'italic': { - name: 'italic', - action: 'italic', - aria: 'italic', - tagNames: ['i', 'em'], - style: { - prop: 'font-style', - value: 'italic' - }, - useQueryState: true, - contentDefault: 'I', - contentFA: '' - }, - 'underline': { - name: 'underline', - action: 'underline', - aria: 'underline', - tagNames: ['u'], - style: { - prop: 'text-decoration', - value: 'underline' - }, - useQueryState: true, - contentDefault: 'U', - contentFA: '' - }, - 'strikethrough': { - name: 'strikethrough', - action: 'strikethrough', - aria: 'strike through', - tagNames: ['strike'], - style: { - prop: 'text-decoration', - value: 'line-through' - }, - useQueryState: true, - contentDefault: 'A', - contentFA: '' - }, - 'superscript': { - name: 'superscript', - action: 'superscript', - aria: 'superscript', - tagNames: ['sup'], - /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for superscript - https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */ - // useQueryState: true - contentDefault: 'x1', - contentFA: '' - }, - 'subscript': { - name: 'subscript', - action: 'subscript', - aria: 'subscript', - tagNames: ['sub'], - /* firefox doesn't behave the way we want it to, so we CAN'T use queryCommandState for subscript - https://github.com/guardian/scribe/blob/master/BROWSERINCONSISTENCIES.md#documentquerycommandstate */ - // useQueryState: true - contentDefault: 'x1', - contentFA: '' - }, - 'image': { - name: 'image', - action: 'image', - aria: 'image', - tagNames: ['img'], - contentDefault: 'image', - contentFA: '' - }, - 'html': { - name: 'html', - action: 'html', - aria: 'evaluate html', - tagNames: ['iframe', 'object'], - contentDefault: 'html', - contentFA: '' - }, - 'orderedlist': { - name: 'orderedlist', - action: 'insertorderedlist', - aria: 'ordered list', - tagNames: ['ol'], - useQueryState: true, - contentDefault: '1.', - contentFA: '' - }, - 'unorderedlist': { - name: 'unorderedlist', - action: 'insertunorderedlist', - aria: 'unordered list', - tagNames: ['ul'], - useQueryState: true, - contentDefault: '', - contentFA: '' - }, - 'indent': { - name: 'indent', - action: 'indent', - aria: 'indent', - tagNames: [], - contentDefault: '', - contentFA: '' - }, - 'outdent': { - name: 'outdent', - action: 'outdent', - aria: 'outdent', - tagNames: [], - contentDefault: '', - contentFA: '' - }, - 'justifyCenter': { - name: 'justifyCenter', - action: 'justifyCenter', - aria: 'center justify', - tagNames: [], - style: { - prop: 'text-align', - value: 'center' - }, - contentDefault: 'C', - contentFA: '' - }, - 'justifyFull': { - name: 'justifyFull', - action: 'justifyFull', - aria: 'full justify', - tagNames: [], - style: { - prop: 'text-align', - value: 'justify' - }, - contentDefault: 'J', - contentFA: '' - }, - 'justifyLeft': { - name: 'justifyLeft', - action: 'justifyLeft', - aria: 'left justify', - tagNames: [], - style: { - prop: 'text-align', - value: 'left' - }, - contentDefault: 'L', - contentFA: '' - }, - 'justifyRight': { - name: 'justifyRight', - action: 'justifyRight', - aria: 'right justify', - tagNames: [], - style: { - prop: 'text-align', - value: 'right' - }, - contentDefault: 'R', - contentFA: '' - }, - // Known inline elements that are not removed, or not removed consistantly across browsers: - // , ', - this.getEditorOption('buttonLabels') === 'fontawesome' ? '' : this.formCloseLabel, - ''); - - // both of these options are slightly moot with the ability to - // override the various form buildup/serialize functions. - - if (this.targetCheckbox) { - // fixme: ideally, this targetCheckboxText would be a formLabel too, - // figure out how to deprecate? also consider `fa-` icon default implications. - template.push( - '
      ', - '', - '', - '
      ' - ); - } - - if (this.customClassOption) { - // fixme: expose this `Button` text as a formLabel property, too - // and provide similar access to a `fa-` icon default. - template.push( - '
      ', - '', - '', - '
      ' - ); - } - - return template.join(''); - - }, - - // Used by medium-editor when the default toolbar is to be displayed - isDisplayed: function () { - return MediumEditor.extensions.form.prototype.isDisplayed.apply(this); - }, - - hideForm: function () { - MediumEditor.extensions.form.prototype.hideForm.apply(this); - this.getInput().value = ''; - }, - - showForm: function (opts) { - var input = this.getInput(), - targetCheckbox = this.getAnchorTargetCheckbox(), - buttonCheckbox = this.getAnchorButtonCheckbox(); - - opts = opts || { value: '' }; - // TODO: This is for backwards compatibility - // We don't need to support the 'string' argument in 6.0.0 - if (typeof opts === 'string') { - opts = { - value: opts - }; - } - - this.base.saveSelection(); - this.hideToolbarDefaultActions(); - MediumEditor.extensions.form.prototype.showForm.apply(this); - this.setToolbarPosition(); - - input.value = opts.value; - input.focus(); - - // If we have a target checkbox, we want it to be checked/unchecked - // based on whether the existing link has target=_blank - if (targetCheckbox) { - targetCheckbox.checked = opts.target === '_blank'; - } - - // If we have a custom class checkbox, we want it to be checked/unchecked - // based on whether an existing link already has the class - if (buttonCheckbox) { - var classList = opts.buttonClass ? opts.buttonClass.split(' ') : []; - buttonCheckbox.checked = (classList.indexOf(this.customClassOption) !== -1); - } - }, - - // Called by core when tearing down medium-editor (destroy) - destroy: function () { - if (!this.form) { - return false; - } - - if (this.form.parentNode) { - this.form.parentNode.removeChild(this.form); - } - - delete this.form; - }, - - // core methods - - getFormOpts: function () { - // no notion of private functions? wanted `_getFormOpts` - var targetCheckbox = this.getAnchorTargetCheckbox(), - buttonCheckbox = this.getAnchorButtonCheckbox(), - opts = { - value: this.getInput().value.trim() - }; - - if (this.linkValidation) { - opts.value = this.checkLinkFormat(opts.value); - } - - opts.target = '_self'; - if (targetCheckbox && targetCheckbox.checked) { - opts.target = '_blank'; - } - - if (buttonCheckbox && buttonCheckbox.checked) { - opts.buttonClass = this.customClassOption; - } - - return opts; - }, - - doFormSave: function () { - var opts = this.getFormOpts(); - this.completeFormSave(opts); - }, - - completeFormSave: function (opts) { - this.base.restoreSelection(); - this.execAction(this.action, opts); - this.base.checkSelection(); - }, - - ensureEncodedUri: function (str) { - return str === decodeURI(str) ? encodeURI(str) : str; - }, - - ensureEncodedUriComponent: function (str) { - return str === decodeURIComponent(str) ? encodeURIComponent(str) : str; - }, - - ensureEncodedParam: function (param) { - var split = param.split('='), - key = split[0], - val = split[1]; - - return key + (val === undefined ? '' : '=' + this.ensureEncodedUriComponent(val)); - }, - - ensureEncodedQuery: function (queryString) { - return queryString.split('&').map(this.ensureEncodedParam.bind(this)).join('&'); - }, - - checkLinkFormat: function (value) { - // Matches any alphabetical characters followed by :// - // Matches protocol relative "//" - // Matches common external protocols "mailto:" "tel:" "maps:" - // Matches relative hash link, begins with "#" - var urlSchemeRegex = /^([a-z]+:)?\/\/|^(mailto|tel|maps):|^#/i, - hasScheme = urlSchemeRegex.test(value), - scheme = '', - // telRegex is a regex for checking if the string is a telephone number - telRegex = /^\+?\s?\(?(?:\d\s?-?\)?){3,20}$/, - urlParts = value.match(/^(.*?)(?:\?(.*?))?(?:#(.*))?$/), - path = urlParts[1], - query = urlParts[2], - fragment = urlParts[3]; - - if (telRegex.test(value)) { - return 'tel:' + value; - } - - if (!hasScheme) { - var host = path.split('/')[0]; - // if the host part of the path looks like a hostname - if (host.match(/.+([.:]).+/) || host === 'localhost') { - scheme = 'http://'; - } - } - - return scheme + - // Ensure path is encoded - this.ensureEncodedUri(path) + - // Ensure query is encoded - (query === undefined ? '' : '?' + this.ensureEncodedQuery(query)) + - // Include fragment unencoded as encodeUriComponent is too - // heavy handed for the many characters allowed in a fragment - (fragment === undefined ? '' : '#' + fragment); - }, - - doFormCancel: function () { - this.base.restoreSelection(); - this.base.checkSelection(); - }, - - // form creation and event handling - attachFormEvents: function (form) { - var close = form.querySelector('.medium-editor-toolbar-close'), - save = form.querySelector('.medium-editor-toolbar-save'), - input = form.querySelector('.medium-editor-toolbar-input'); - - // Handle clicks on the form itself - this.on(form, 'click', this.handleFormClick.bind(this)); - - // Handle typing in the textbox - this.on(input, 'keyup', this.handleTextboxKeyup.bind(this)); - - // Handle close button clicks - this.on(close, 'click', this.handleCloseClick.bind(this)); - - // Handle save button clicks (capture) - this.on(save, 'click', this.handleSaveClick.bind(this), true); - - }, - - createForm: function () { - var doc = this.document, - form = doc.createElement('div'); - - // Anchor Form (div) - form.className = 'medium-editor-toolbar-form'; - form.id = 'medium-editor-toolbar-form-anchor-' + this.getEditorId(); - form.innerHTML = this.getTemplate(); - this.attachFormEvents(form); - - return form; - }, - - getInput: function () { - return this.getForm().querySelector('input.medium-editor-toolbar-input'); - }, - - getAnchorTargetCheckbox: function () { - return this.getForm().querySelector('.medium-editor-toolbar-anchor-target'); - }, - - getAnchorButtonCheckbox: function () { - return this.getForm().querySelector('.medium-editor-toolbar-anchor-button'); - }, - - handleTextboxKeyup: function (event) { - // For ENTER -> create the anchor - if (event.keyCode === MediumEditor.util.keyCode.ENTER) { - event.preventDefault(); - this.doFormSave(); - return; - } - - // For ESCAPE -> close the form - if (event.keyCode === MediumEditor.util.keyCode.ESCAPE) { - event.preventDefault(); - this.doFormCancel(); - } - }, - - handleFormClick: function (event) { - // make sure not to hide form when clicking inside the form - event.stopPropagation(); - }, - - handleSaveClick: function (event) { - // Clicking Save -> create the anchor - event.preventDefault(); - this.doFormSave(); - }, - - handleCloseClick: function (event) { - // Click Close -> close the form - event.preventDefault(); - this.doFormCancel(); - } - }); - - MediumEditor.extensions.anchor = AnchorForm; -}()); - -(function () { - 'use strict'; - - var AnchorPreview = MediumEditor.Extension.extend({ - name: 'anchor-preview', - - // Anchor Preview Options - - /* hideDelay: [number] (previously options.anchorPreviewHideDelay) - * time in milliseconds to show the anchor tag preview after the mouse has left the anchor tag. - */ - hideDelay: 500, - - /* previewValueSelector: [string] - * the default selector to locate where to put the activeAnchor value in the preview - */ - previewValueSelector: 'a', - - /* showWhenToolbarIsVisible: [boolean] - * determines whether the anchor tag preview shows up when the toolbar is visible - */ - showWhenToolbarIsVisible: false, - - /* showOnEmptyLinks: [boolean] - * determines whether the anchor tag preview shows up on links with href="" or href="#something" - */ - showOnEmptyLinks: true, - - init: function () { - this.anchorPreview = this.createPreview(); - - this.getEditorOption('elementsContainer').appendChild(this.anchorPreview); - - this.attachToEditables(); - }, - - getInteractionElements: function () { - return this.getPreviewElement(); - }, - - // TODO: Remove this function in 6.0.0 - getPreviewElement: function () { - return this.anchorPreview; - }, - - createPreview: function () { - var el = this.document.createElement('div'); - - el.id = 'medium-editor-anchor-preview-' + this.getEditorId(); - el.className = 'medium-editor-anchor-preview'; - el.innerHTML = this.getTemplate(); - - this.on(el, 'click', this.handleClick.bind(this)); - - return el; - }, - - getTemplate: function () { - return '
      ' + - ' ' + - '
      '; - }, - - destroy: function () { - if (this.anchorPreview) { - if (this.anchorPreview.parentNode) { - this.anchorPreview.parentNode.removeChild(this.anchorPreview); - } - delete this.anchorPreview; - } - }, - - hidePreview: function () { - if (this.anchorPreview) { - this.anchorPreview.classList.remove('medium-editor-anchor-preview-active'); - } - this.activeAnchor = null; - }, - - showPreview: function (anchorEl) { - if (this.anchorPreview.classList.contains('medium-editor-anchor-preview-active') || - anchorEl.getAttribute('data-disable-preview')) { - return true; - } - - if (this.previewValueSelector) { - this.anchorPreview.querySelector(this.previewValueSelector).textContent = anchorEl.attributes.href.value; - this.anchorPreview.querySelector(this.previewValueSelector).href = anchorEl.attributes.href.value; - } - - this.anchorPreview.classList.add('medium-toolbar-arrow-over'); - this.anchorPreview.classList.remove('medium-toolbar-arrow-under'); - - if (!this.anchorPreview.classList.contains('medium-editor-anchor-preview-active')) { - this.anchorPreview.classList.add('medium-editor-anchor-preview-active'); - } - - this.activeAnchor = anchorEl; - - this.positionPreview(); - this.attachPreviewHandlers(); - - return this; - }, - - positionPreview: function (activeAnchor) { - activeAnchor = activeAnchor || this.activeAnchor; - var containerWidth = this.window.innerWidth, - buttonHeight = this.anchorPreview.offsetHeight, - boundary = activeAnchor.getBoundingClientRect(), - diffLeft = this.diffLeft, - diffTop = this.diffTop, - elementsContainer = this.getEditorOption('elementsContainer'), - elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1, - relativeBoundary = {}, - halfOffsetWidth, defaultLeft, middleBoundary, elementsContainerBoundary, top; - - halfOffsetWidth = this.anchorPreview.offsetWidth / 2; - var toolbarExtension = this.base.getExtensionByName('toolbar'); - if (toolbarExtension) { - diffLeft = toolbarExtension.diffLeft; - diffTop = toolbarExtension.diffTop; - } - defaultLeft = diffLeft - halfOffsetWidth; - - // If container element is absolute / fixed, recalculate boundaries to be relative to the container - if (elementsContainerAbsolute) { - elementsContainerBoundary = elementsContainer.getBoundingClientRect(); - ['top', 'left'].forEach(function (key) { - relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key]; - }); - - relativeBoundary.width = boundary.width; - relativeBoundary.height = boundary.height; - boundary = relativeBoundary; - - containerWidth = elementsContainerBoundary.width; - - // Adjust top position according to container scroll position - top = elementsContainer.scrollTop; - } else { - // Adjust top position according to window scroll position - top = this.window.pageYOffset; - } - - middleBoundary = boundary.left + boundary.width / 2; - top += buttonHeight + boundary.top + boundary.height - diffTop - this.anchorPreview.offsetHeight; - - this.anchorPreview.style.top = Math.round(top) + 'px'; - this.anchorPreview.style.right = 'initial'; - if (middleBoundary < halfOffsetWidth) { - this.anchorPreview.style.left = defaultLeft + halfOffsetWidth + 'px'; - this.anchorPreview.style.right = 'initial'; - } else if ((containerWidth - middleBoundary) < halfOffsetWidth) { - this.anchorPreview.style.left = 'auto'; - this.anchorPreview.style.right = 0; - } else { - this.anchorPreview.style.left = defaultLeft + middleBoundary + 'px'; - this.anchorPreview.style.right = 'initial'; - } - }, - - attachToEditables: function () { - this.subscribe('editableMouseover', this.handleEditableMouseover.bind(this)); - this.subscribe('positionedToolbar', this.handlePositionedToolbar.bind(this)); - }, - - handlePositionedToolbar: function () { - // If the toolbar is visible and positioned, we don't need to hide the preview - // when showWhenToolbarIsVisible is true - if (!this.showWhenToolbarIsVisible) { - this.hidePreview(); - } - }, - - handleClick: function (event) { - var anchorExtension = this.base.getExtensionByName('anchor'), - activeAnchor = this.activeAnchor; - - if (anchorExtension && activeAnchor) { - event.preventDefault(); - - this.base.selectElement(this.activeAnchor); - - // Using setTimeout + delay because: - // We may actually be displaying the anchor form, which should be controlled by delay - this.base.delay(function () { - if (activeAnchor) { - var opts = { - value: activeAnchor.attributes.href.value, - target: activeAnchor.getAttribute('target'), - buttonClass: activeAnchor.getAttribute('class') - }; - anchorExtension.showForm(opts); - activeAnchor = null; - } - }.bind(this)); - } - - this.hidePreview(); - }, - - handleAnchorMouseout: function () { - this.anchorToPreview = null; - this.off(this.activeAnchor, 'mouseout', this.instanceHandleAnchorMouseout); - this.instanceHandleAnchorMouseout = null; - }, - - handleEditableMouseover: function (event) { - var target = MediumEditor.util.getClosestTag(event.target, 'a'); - - if (false === target) { - return; - } - - // Detect empty href attributes - // The browser will make href="" or href="#top" - // into absolute urls when accessed as event.target.href, so check the html - if (!this.showOnEmptyLinks && - (!/href=["']\S+["']/.test(target.outerHTML) || /href=["']#\S+["']/.test(target.outerHTML))) { - return true; - } - - // only show when toolbar is not present - var toolbar = this.base.getExtensionByName('toolbar'); - if (!this.showWhenToolbarIsVisible && toolbar && toolbar.isDisplayed && toolbar.isDisplayed()) { - return true; - } - - // detach handler for other anchor in case we hovered multiple anchors quickly - if (this.activeAnchor && this.activeAnchor !== target) { - this.detachPreviewHandlers(); - } - - this.anchorToPreview = target; - - this.instanceHandleAnchorMouseout = this.handleAnchorMouseout.bind(this); - this.on(this.anchorToPreview, 'mouseout', this.instanceHandleAnchorMouseout); - // Using setTimeout + delay because: - // - We're going to show the anchor preview according to the configured delay - // if the mouse has not left the anchor tag in that time - this.base.delay(function () { - if (this.anchorToPreview) { - this.showPreview(this.anchorToPreview); - } - }.bind(this)); - }, - - handlePreviewMouseover: function () { - this.lastOver = (new Date()).getTime(); - this.hovering = true; - }, - - handlePreviewMouseout: function (event) { - if (!event.relatedTarget || !/anchor-preview/.test(event.relatedTarget.className)) { - this.hovering = false; - } - }, - - updatePreview: function () { - if (this.hovering) { - return true; - } - var durr = (new Date()).getTime() - this.lastOver; - if (durr > this.hideDelay) { - // hide the preview 1/2 second after mouse leaves the link - this.detachPreviewHandlers(); - } - }, - - detachPreviewHandlers: function () { - // cleanup - clearInterval(this.intervalTimer); - if (this.instanceHandlePreviewMouseover) { - this.off(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover); - this.off(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout); - if (this.activeAnchor) { - this.off(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover); - this.off(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout); - } - } - - this.hidePreview(); - - this.hovering = this.instanceHandlePreviewMouseover = this.instanceHandlePreviewMouseout = null; - }, - - // TODO: break up method and extract out handlers - attachPreviewHandlers: function () { - this.lastOver = (new Date()).getTime(); - this.hovering = true; - - this.instanceHandlePreviewMouseover = this.handlePreviewMouseover.bind(this); - this.instanceHandlePreviewMouseout = this.handlePreviewMouseout.bind(this); - - this.intervalTimer = setInterval(this.updatePreview.bind(this), 200); - - this.on(this.anchorPreview, 'mouseover', this.instanceHandlePreviewMouseover); - this.on(this.anchorPreview, 'mouseout', this.instanceHandlePreviewMouseout); - this.on(this.activeAnchor, 'mouseover', this.instanceHandlePreviewMouseover); - this.on(this.activeAnchor, 'mouseout', this.instanceHandlePreviewMouseout); - } - }); - - MediumEditor.extensions.anchorPreview = AnchorPreview; -}()); - -(function () { - 'use strict'; - - var WHITESPACE_CHARS, - KNOWN_TLDS_FRAGMENT, - LINK_REGEXP_TEXT, - KNOWN_TLDS_REGEXP, - LINK_REGEXP; - - WHITESPACE_CHARS = [' ', '\t', '\n', '\r', '\u00A0', '\u2000', '\u2001', '\u2002', '\u2003', - '\u2028', '\u2029']; - KNOWN_TLDS_FRAGMENT = 'com|net|org|edu|gov|mil|aero|asia|biz|cat|coop|info|int|jobs|mobi|museum|name|post|pro|tel|travel|' + - 'xxx|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|' + - 'bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cs|cu|cv|cx|cy|cz|dd|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|eu|fi|fj|' + - 'fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|' + - 'is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|' + - 'mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|' + - 'pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|ja|sk|sl|sm|sn|so|sr|ss|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|' + - 'tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|yu|za|zm|zw'; - - LINK_REGEXP_TEXT = - '(' + - // Version of Gruber URL Regexp optimized for JS: http://stackoverflow.com/a/17733640 - '((?:(https?://|ftps?://|nntp://)|www\\d{0,3}[.]|[a-z0-9.\\-]+[.](' + KNOWN_TLDS_FRAGMENT + ')\\\/)\\S+(?:[^\\s`!\\[\\]{};:\'\".,?\u00AB\u00BB\u201C\u201D\u2018\u2019]))' + - // Addition to above Regexp to support bare domains/one level subdomains with common non-i18n TLDs and without www prefix: - ')|(([a-z0-9\\-]+\\.)?[a-z0-9\\-]+\\.(' + KNOWN_TLDS_FRAGMENT + '))'; - - KNOWN_TLDS_REGEXP = new RegExp('^(' + KNOWN_TLDS_FRAGMENT + ')$', 'i'); - - LINK_REGEXP = new RegExp(LINK_REGEXP_TEXT, 'gi'); - - function nodeIsNotInsideAnchorTag (node) { - return !MediumEditor.util.getClosestTag(node, 'a'); - } - - var AutoLink = MediumEditor.Extension.extend({ - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.disableEventHandling = false; - this.subscribe('editableKeypress', this.onKeypress.bind(this)); - this.subscribe('editableBlur', this.onBlur.bind(this)); - // MS IE has it's own auto-URL detect feature but ours is better in some ways. Be consistent. - this.document.execCommand('AutoUrlDetect', false, false); - }, - - isLastInstance: function () { - var activeInstances = 0; - for (var i = 0; i < this.window._mediumEditors.length; i++) { - var editor = this.window._mediumEditors[i]; - if (editor !== null && editor.getExtensionByName('autoLink') !== undefined) { - activeInstances++; - } - } - return activeInstances === 1; - }, - - destroy: function () { - // Turn AutoUrlDetect back on - if (this.document.queryCommandSupported('AutoUrlDetect') && this.isLastInstance()) { - this.document.execCommand('AutoUrlDetect', false, true); - } - }, - - onBlur: function (blurEvent, editable) { - this.performLinking(editable); - }, - - onKeypress: function (keyPressEvent) { - if (this.disableEventHandling) { - return; - } - - if (MediumEditor.util.isKey(keyPressEvent, [MediumEditor.util.keyCode.SPACE, MediumEditor.util.keyCode.ENTER])) { - clearTimeout(this.performLinkingTimeout); - // Saving/restoring the selection in the middle of a keypress doesn't work well... - this.performLinkingTimeout = setTimeout(function () { - try { - var sel = this.base.exportSelection(); - if (this.performLinking(keyPressEvent.target)) { - // pass true for favorLaterSelectionAnchor - this is needed for links at the end of a - // paragraph in MS IE, or MS IE causes the link to be deleted right after adding it. - this.base.importSelection(sel, true); - } - } catch (e) { - if (window.console) { - window.console.error('Failed to perform linking', e); - } - this.disableEventHandling = true; - } - }.bind(this), 0); - } - }, - - performLinking: function (contenteditable) { - /* - Perform linking on blockElement basis, blockElements are HTML elements with text content and without - child element. - - Example: - - HTML content -
      -

      link.

      -

      my

      -
      - - - blockElements - [

      link.

      ,

      my

      ] - - otherwise the detection can wrongly find the end of one paragraph and the beginning of another paragraph - to constitute a link, such as a paragraph ending "link." and the next paragraph beginning with "my" is - interpreted into "link.my" and the code tries to create a link across blockElements - which doesn't work - and is terrible. - (Medium deletes the spaces/returns between P tags so the textContent ends up without paragraph spacing) - */ - var blockElements = MediumEditor.util.splitByBlockElements(contenteditable), - documentModified = false; - if (blockElements.length === 0) { - blockElements = [contenteditable]; - } - for (var i = 0; i < blockElements.length; i++) { - documentModified = this.removeObsoleteAutoLinkSpans(blockElements[i]) || documentModified; - documentModified = this.performLinkingWithinElement(blockElements[i]) || documentModified; - } - this.base.events.updateInput(contenteditable, { target: contenteditable, currentTarget: contenteditable }); - return documentModified; - }, - - removeObsoleteAutoLinkSpans: function (element) { - if (!element || element.nodeType === 3) { - return false; - } - - var spans = element.querySelectorAll('span[data-auto-link="true"]'), - documentModified = false; - - for (var i = 0; i < spans.length; i++) { - var textContent = spans[i].textContent; - if (textContent.indexOf('://') === -1) { - textContent = MediumEditor.util.ensureUrlHasProtocol(textContent); - } - if (spans[i].getAttribute('data-href') !== textContent && nodeIsNotInsideAnchorTag(spans[i])) { - documentModified = true; - var trimmedTextContent = textContent.replace(/\s+$/, ''); - if (spans[i].getAttribute('data-href') === trimmedTextContent) { - var charactersTrimmed = textContent.length - trimmedTextContent.length, - subtree = MediumEditor.util.splitOffDOMTree(spans[i], this.splitTextBeforeEnd(spans[i], charactersTrimmed)); - spans[i].parentNode.insertBefore(subtree, spans[i].nextSibling); - } else { - // Some editing has happened to the span, so just remove it entirely. The user can put it back - // around just the href content if they need to prevent it from linking - MediumEditor.util.unwrap(spans[i], this.document); - } - } - } - return documentModified; - }, - - splitTextBeforeEnd: function (element, characterCount) { - var treeWalker = this.document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false), - lastChildNotExhausted = true; - - // Start the tree walker at the last descendant of the span - while (lastChildNotExhausted) { - lastChildNotExhausted = treeWalker.lastChild() !== null; - } - - var currentNode, - currentNodeValue, - previousNode; - while (characterCount > 0 && previousNode !== null) { - currentNode = treeWalker.currentNode; - currentNodeValue = currentNode.nodeValue; - if (currentNodeValue.length > characterCount) { - previousNode = currentNode.splitText(currentNodeValue.length - characterCount); - characterCount = 0; - } else { - previousNode = treeWalker.previousNode(); - characterCount -= currentNodeValue.length; - } - } - return previousNode; - }, - - performLinkingWithinElement: function (element) { - var matches = this.findLinkableText(element), - linkCreated = false; - - for (var matchIndex = 0; matchIndex < matches.length; matchIndex++) { - var matchingTextNodes = MediumEditor.util.findOrCreateMatchingTextNodes(this.document, element, - matches[matchIndex]); - if (this.shouldNotLink(matchingTextNodes)) { - continue; - } - this.createAutoLink(matchingTextNodes, matches[matchIndex].href); - } - return linkCreated; - }, - - shouldNotLink: function (textNodes) { - var shouldNotLink = false; - for (var i = 0; i < textNodes.length && shouldNotLink === false; i++) { - // Do not link if the text node is either inside an anchor or inside span[data-auto-link] - shouldNotLink = !!MediumEditor.util.traverseUp(textNodes[i], function (node) { - return node.nodeName.toLowerCase() === 'a' || - (node.getAttribute && node.getAttribute('data-auto-link') === 'true'); - }); - } - return shouldNotLink; - }, - - findLinkableText: function (contenteditable) { - var textContent = contenteditable.textContent, - match = null, - matches = []; - - while ((match = LINK_REGEXP.exec(textContent)) !== null) { - var matchOk = true, - matchEnd = match.index + match[0].length; - // If the regexp detected something as a link that has text immediately preceding/following it, bail out. - matchOk = (match.index === 0 || WHITESPACE_CHARS.indexOf(textContent[match.index - 1]) !== -1) && - (matchEnd === textContent.length || WHITESPACE_CHARS.indexOf(textContent[matchEnd]) !== -1); - // If the regexp detected a bare domain that doesn't use one of our expected TLDs, bail out. - matchOk = matchOk && (match[0].indexOf('/') !== -1 || - KNOWN_TLDS_REGEXP.test(match[0].split('.').pop().split('?').shift())); - - if (matchOk) { - matches.push({ - href: match[0], - start: match.index, - end: matchEnd - }); - } - } - return matches; - }, - - createAutoLink: function (textNodes, href) { - href = MediumEditor.util.ensureUrlHasProtocol(href); - var anchor = MediumEditor.util.createLink(this.document, textNodes, href, this.getEditorOption('targetBlank') ? '_blank' : null), - span = this.document.createElement('span'); - span.setAttribute('data-auto-link', 'true'); - span.setAttribute('data-href', href); - anchor.insertBefore(span, anchor.firstChild); - while (anchor.childNodes.length > 1) { - span.appendChild(anchor.childNodes[1]); - } - } - - }); - - MediumEditor.extensions.autoLink = AutoLink; -}()); - -(function () { - 'use strict'; - - var CLASS_DRAG_OVER = 'medium-editor-dragover'; - - function clearClassNames (element) { - var editable = MediumEditor.util.getContainerEditorElement(element), - existing = Array.prototype.slice.call(editable.parentElement.querySelectorAll('.' + CLASS_DRAG_OVER)); - - existing.forEach(function (el) { - el.classList.remove(CLASS_DRAG_OVER); - }); - } - - var FileDragging = MediumEditor.Extension.extend({ - name: 'fileDragging', - - allowedTypes: ['image'], - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.subscribe('editableDrag', this.handleDrag.bind(this)); - this.subscribe('editableDrop', this.handleDrop.bind(this)); - }, - - handleDrag: function (event) { - event.preventDefault(); - event.dataTransfer.dropEffect = 'copy'; - - var target = event.target.classList ? event.target : event.target.parentElement; - - // Ensure the class gets removed from anything that had it before - clearClassNames(target); - - if (event.type === 'dragover') { - target.classList.add(CLASS_DRAG_OVER); - } - }, - - handleDrop: function (event) { - // Prevent file from opening in the current window - event.preventDefault(); - event.stopPropagation(); - // Select the dropping target, and set the selection to the end of the target - // https://github.com/yabwe/medium-editor/issues/980 - this.base.selectElement(event.target); - var selection = this.base.exportSelection(); - selection.start = selection.end; - this.base.importSelection(selection); - // IE9 does not support the File API, so prevent file from opening in the window - // but also don't try to actually get the file - if (event.dataTransfer.files) { - Array.prototype.slice.call(event.dataTransfer.files).forEach(function (file) { - if (this.isAllowedFile(file)) { - if (file.type.match('image')) { - this.insertImageFile(file); - } - } - }, this); - } - - // Make sure we remove our class from everything - clearClassNames(event.target); - }, - - isAllowedFile: function (file) { - return this.allowedTypes.some(function (fileType) { - return !!file.type.match(fileType); - }); - }, - - insertImageFile: function (file) { - if (typeof FileReader !== 'function') { - return; - } - var fileReader = new FileReader(); - fileReader.readAsDataURL(file); - - // attach the onload event handler, makes it easier to listen in with jasmine - fileReader.addEventListener('load', function (e) { - var addImageElement = this.document.createElement('img'); - addImageElement.src = e.target.result; - MediumEditor.util.insertHTMLCommand(this.document, addImageElement.outerHTML); - }.bind(this)); - } - }); - - MediumEditor.extensions.fileDragging = FileDragging; -}()); - -(function () { - 'use strict'; - - var KeyboardCommands = MediumEditor.Extension.extend({ - name: 'keyboard-commands', - - /* KeyboardCommands Options */ - - /* commands: [Array] - * Array of objects describing each command and the combination of keys that will trigger it - * Required for each object: - * command [String] (argument passed to editor.execAction()) - * key [String] (keyboard character that triggers this command) - * meta [boolean] (whether the ctrl/meta key has to be active or inactive) - * shift [boolean] (whether the shift key has to be active or inactive) - * alt [boolean] (whether the alt key has to be active or inactive) - */ - commands: [ - { - command: 'bold', - key: 'B', - meta: true, - shift: false, - alt: false - }, - { - command: 'italic', - key: 'I', - meta: true, - shift: false, - alt: false - }, - { - command: 'underline', - key: 'U', - meta: true, - shift: false, - alt: false - } - ], - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.subscribe('editableKeydown', this.handleKeydown.bind(this)); - this.keys = {}; - this.commands.forEach(function (command) { - var keyCode = command.key.charCodeAt(0); - if (!this.keys[keyCode]) { - this.keys[keyCode] = []; - } - this.keys[keyCode].push(command); - }, this); - }, - - handleKeydown: function (event) { - var keyCode = MediumEditor.util.getKeyCode(event); - if (!this.keys[keyCode]) { - return; - } - - var isMeta = MediumEditor.util.isMetaCtrlKey(event), - isShift = !!event.shiftKey, - isAlt = !!event.altKey; - - this.keys[keyCode].forEach(function (data) { - if (data.meta === isMeta && - data.shift === isShift && - (data.alt === isAlt || - undefined === data.alt)) { // TODO deprecated: remove check for undefined === data.alt when jumping to 6.0.0 - event.preventDefault(); - event.stopPropagation(); - - // command can be a function to execute - if (typeof data.command === 'function') { - data.command.apply(this); - } else if (false !== data.command) { - // command can be false so the shortcut is just disabled - this.execAction(data.command); - } - } - }, this); - } - }); - - MediumEditor.extensions.keyboardCommands = KeyboardCommands; -}()); - -(function () { - 'use strict'; - - var FontNameForm = MediumEditor.extensions.form.extend({ - - name: 'fontname', - action: 'fontName', - aria: 'change font name', - contentDefault: '±', // ± - contentFA: '', - - fonts: ['', 'Arial', 'Verdana', 'Times New Roman'], - - init: function () { - MediumEditor.extensions.form.prototype.init.apply(this, arguments); - }, - - // Called when the button the toolbar is clicked - // Overrides ButtonExtension.handleClick - handleClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - - if (!this.isDisplayed()) { - // Get FontName of current selection (convert to string since IE returns this as number) - var fontName = this.document.queryCommandValue('fontName') + ''; - this.showForm(fontName); - } - - return false; - }, - - // Called by medium-editor to append form to the toolbar - getForm: function () { - if (!this.form) { - this.form = this.createForm(); - } - return this.form; - }, - - // Used by medium-editor when the default toolbar is to be displayed - isDisplayed: function () { - return this.getForm().style.display === 'block'; - }, - - hideForm: function () { - this.getForm().style.display = 'none'; - this.getSelect().value = ''; - }, - - showForm: function (fontName) { - var select = this.getSelect(); - - this.base.saveSelection(); - this.hideToolbarDefaultActions(); - this.getForm().style.display = 'block'; - this.setToolbarPosition(); - - select.value = fontName || ''; - select.focus(); - }, - - // Called by core when tearing down medium-editor (destroy) - destroy: function () { - if (!this.form) { - return false; - } - - if (this.form.parentNode) { - this.form.parentNode.removeChild(this.form); - } - - delete this.form; - }, - - // core methods - - doFormSave: function () { - this.base.restoreSelection(); - this.base.checkSelection(); - }, - - doFormCancel: function () { - this.base.restoreSelection(); - this.clearFontName(); - this.base.checkSelection(); - }, - - // form creation and event handling - createForm: function () { - var doc = this.document, - form = doc.createElement('div'), - select = doc.createElement('select'), - close = doc.createElement('a'), - save = doc.createElement('a'), - option; - - // Font Name Form (div) - form.className = 'medium-editor-toolbar-form'; - form.id = 'medium-editor-toolbar-form-fontname-' + this.getEditorId(); - - // Handle clicks on the form itself - this.on(form, 'click', this.handleFormClick.bind(this)); - - // Add font names - for (var i = 0; i < this.fonts.length; i++) { - option = doc.createElement('option'); - option.innerHTML = this.fonts[i]; - option.value = this.fonts[i]; - select.appendChild(option); - } - - select.className = 'medium-editor-toolbar-select'; - form.appendChild(select); - - // Handle typing in the textbox - this.on(select, 'change', this.handleFontChange.bind(this)); - - // Add save button - save.setAttribute('href', '#'); - save.className = 'medium-editor-toolbar-save'; - save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '✓'; - form.appendChild(save); - - // Handle save button clicks (capture) - this.on(save, 'click', this.handleSaveClick.bind(this), true); - - // Add close button - close.setAttribute('href', '#'); - close.className = 'medium-editor-toolbar-close'; - close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; - form.appendChild(close); - - // Handle close button clicks - this.on(close, 'click', this.handleCloseClick.bind(this)); - - return form; - }, - - getSelect: function () { - return this.getForm().querySelector('select.medium-editor-toolbar-select'); - }, - - clearFontName: function () { - MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) { - if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('face')) { - el.removeAttribute('face'); - } - }); - }, - - handleFontChange: function () { - var font = this.getSelect().value; - if (font === '') { - this.clearFontName(); - } else { - this.execAction('fontName', { value: font }); - } - }, - - handleFormClick: function (event) { - // make sure not to hide form when clicking inside the form - event.stopPropagation(); - }, - - handleSaveClick: function (event) { - // Clicking Save -> create the font size - event.preventDefault(); - this.doFormSave(); - }, - - handleCloseClick: function (event) { - // Click Close -> close the form - event.preventDefault(); - this.doFormCancel(); - } - }); - - MediumEditor.extensions.fontName = FontNameForm; -}()); - -(function () { - 'use strict'; - - var FontSizeForm = MediumEditor.extensions.form.extend({ - - name: 'fontsize', - action: 'fontSize', - aria: 'increase/decrease font size', - contentDefault: '±', // ± - contentFA: '', - - init: function () { - MediumEditor.extensions.form.prototype.init.apply(this, arguments); - }, - - // Called when the button the toolbar is clicked - // Overrides ButtonExtension.handleClick - handleClick: function (event) { - event.preventDefault(); - event.stopPropagation(); - - if (!this.isDisplayed()) { - // Get fontsize of current selection (convert to string since IE returns this as number) - var fontSize = this.document.queryCommandValue('fontSize') + ''; - this.showForm(fontSize); - } - - return false; - }, - - // Called by medium-editor to append form to the toolbar - getForm: function () { - if (!this.form) { - this.form = this.createForm(); - } - return this.form; - }, - - // Used by medium-editor when the default toolbar is to be displayed - isDisplayed: function () { - return this.getForm().style.display === 'block'; - }, - - hideForm: function () { - this.getForm().style.display = 'none'; - this.getInput().value = ''; - }, - - showForm: function (fontSize) { - var input = this.getInput(); - - this.base.saveSelection(); - this.hideToolbarDefaultActions(); - this.getForm().style.display = 'block'; - this.setToolbarPosition(); - - input.value = fontSize || ''; - input.focus(); - }, - - // Called by core when tearing down medium-editor (destroy) - destroy: function () { - if (!this.form) { - return false; - } - - if (this.form.parentNode) { - this.form.parentNode.removeChild(this.form); - } - - delete this.form; - }, - - // core methods - - doFormSave: function () { - this.base.restoreSelection(); - this.base.checkSelection(); - }, - - doFormCancel: function () { - this.base.restoreSelection(); - this.clearFontSize(); - this.base.checkSelection(); - }, - - // form creation and event handling - createForm: function () { - var doc = this.document, - form = doc.createElement('div'), - input = doc.createElement('input'), - close = doc.createElement('a'), - save = doc.createElement('a'); - - // Font Size Form (div) - form.className = 'medium-editor-toolbar-form'; - form.id = 'medium-editor-toolbar-form-fontsize-' + this.getEditorId(); - - // Handle clicks on the form itself - this.on(form, 'click', this.handleFormClick.bind(this)); - - // Add font size slider - input.setAttribute('type', 'range'); - input.setAttribute('min', '1'); - input.setAttribute('max', '7'); - input.className = 'medium-editor-toolbar-input'; - form.appendChild(input); - - // Handle typing in the textbox - this.on(input, 'change', this.handleSliderChange.bind(this)); - - // Add save button - save.setAttribute('href', '#'); - save.className = 'medium-editor-toolbar-save'; - save.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '✓'; - form.appendChild(save); - - // Handle save button clicks (capture) - this.on(save, 'click', this.handleSaveClick.bind(this), true); - - // Add close button - close.setAttribute('href', '#'); - close.className = 'medium-editor-toolbar-close'; - close.innerHTML = this.getEditorOption('buttonLabels') === 'fontawesome' ? - '' : - '×'; - form.appendChild(close); - - // Handle close button clicks - this.on(close, 'click', this.handleCloseClick.bind(this)); - - return form; - }, - - getInput: function () { - return this.getForm().querySelector('input.medium-editor-toolbar-input'); - }, - - clearFontSize: function () { - MediumEditor.selection.getSelectedElements(this.document).forEach(function (el) { - if (el.nodeName.toLowerCase() === 'font' && el.hasAttribute('size')) { - el.removeAttribute('size'); - } - }); - }, - - handleSliderChange: function () { - var size = this.getInput().value; - if (size === '4') { - this.clearFontSize(); - } else { - this.execAction('fontSize', { value: size }); - } - }, - - handleFormClick: function (event) { - // make sure not to hide form when clicking inside the form - event.stopPropagation(); - }, - - handleSaveClick: function (event) { - // Clicking Save -> create the font size - event.preventDefault(); - this.doFormSave(); - }, - - handleCloseClick: function (event) { - // Click Close -> close the form - event.preventDefault(); - this.doFormCancel(); - } - }); - - MediumEditor.extensions.fontSize = FontSizeForm; -}()); -(function () { - 'use strict'; - - /* Helpers and internal variables that don't need to be members of actual paste handler */ - - var pasteBinDefaultContent = '%ME_PASTEBIN%', - lastRange = null, - keyboardPasteEditable = null, - stopProp = function (event) { - event.stopPropagation(); - }; - - /*jslint regexp: true*/ - - // jslint does not allow character negation, because the negation - // will not match any unicode characters. In the regexes in this - // block, negation is used specifically to match the end of an html - // tag, and in fact unicode characters *should* be allowed. - function createReplacements () { - return [ - // Remove anything but the contents within the BODY element - [new RegExp(/^[\s\S]*]*>\s*|\s*<\/body[^>]*>[\s\S]*$/g), ''], - - // cleanup comments added by Chrome when pasting html - [new RegExp(/|/g), ''], - - // Trailing BR elements - [new RegExp(/
      $/i), ''], - - // replace two bogus tags that begin pastes from google docs - [new RegExp(/<[^>]*docs-internal-guid[^>]*>/gi), ''], - [new RegExp(/<\/b>(]*>)?$/gi), ''], - - // un-html spaces and newlines inserted by OS X - [new RegExp(/\s+<\/span>/g), ' '], - [new RegExp(/
      /g), '
      '], - - // replace google docs italics+bold with a span to be replaced once the html is inserted - [new RegExp(/]*(font-style:italic;font-weight:(bold|700)|font-weight:(bold|700);font-style:italic)[^>]*>/gi), ''], - - // replace google docs italics with a span to be replaced once the html is inserted - [new RegExp(/]*font-style:italic[^>]*>/gi), ''], - - //[replace google docs bolds with a span to be replaced once the html is inserted - [new RegExp(/]*font-weight:(bold|700)[^>]*>/gi), ''], - - // replace manually entered b/i/a tags with real ones - [new RegExp(/<(\/?)([iba])>/gi), '<$1$2>'], - - // replace manually a tags with real ones, converting smart-quotes from google docs - [new RegExp(/<a(?:(?!href).)+href=(?:"|”|“|"|“|”)(((?!"|”|“|"|“|”).)*)(?:"|”|“|"|“|”)(?:(?!>).)*>/gi), ''], - - // Newlines between paragraphs in html have no syntactic value, - // but then have a tendency to accidentally become additional paragraphs down the line - [new RegExp(/<\/p>\n+/gi), '

      '], - [new RegExp(/\n+

      - [new RegExp(/<\/?o:[a-z]*>/gi), ''], - - // Microsoft Word adds some special elements around list items - [new RegExp(/(((?!/gi), '$1'] - ]; - } - - /*jslint regexp: false*/ - - /** - * Gets various content types out of the Clipboard API. It will also get the - * plain text using older IE and WebKit API. - * - * @param {event} event Event fired on paste. - * @param {win} reference to window - * @param {doc} reference to document - * @return {Object} Object with mime types and data for those mime types. - */ - function getClipboardContent (event, win, doc) { - var dataTransfer = event.clipboardData || win.clipboardData || doc.dataTransfer, - data = {}; - - if (!dataTransfer) { - return data; - } - - // Use old WebKit/IE API - if (dataTransfer.getData) { - var legacyText = dataTransfer.getData('Text'); - if (legacyText && legacyText.length > 0) { - data['text/plain'] = legacyText; - } - } - - if (dataTransfer.types) { - for (var i = 0; i < dataTransfer.types.length; i++) { - var contentType = dataTransfer.types[i]; - data[contentType] = dataTransfer.getData(contentType); - } - } - - return data; - } - - var PasteHandler = MediumEditor.Extension.extend({ - /* Paste Options */ - - /* forcePlainText: [boolean] - * Forces pasting as plain text. - */ - forcePlainText: true, - - /* cleanPastedHTML: [boolean] - * cleans pasted content from different sources, like google docs etc. - */ - cleanPastedHTML: false, - - /* preCleanReplacements: [Array] - * custom pairs (2 element arrays) of RegExp and replacement text to use during past when - * __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. - * These replacements are executed before any medium editor defined replacements. - */ - preCleanReplacements: [], - - /* cleanReplacements: [Array] - * custom pairs (2 element arrays) of RegExp and replacement text to use during paste when - * __forcePlainText__ or __cleanPastedHTML__ are `true` OR when calling `cleanPaste(text)` helper method. - * These replacements are executed after any medium editor defined replacements. - */ - cleanReplacements: [], - - /* cleanAttrs:: [Array] - * list of element attributes to remove during paste when __cleanPastedHTML__ is `true` or when - * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods. - */ - cleanAttrs: ['class', 'style', 'dir'], - - /* cleanTags: [Array] - * list of element tag names to remove during paste when __cleanPastedHTML__ is `true` or when - * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods. - */ - cleanTags: ['meta'], - - /* unwrapTags: [Array] - * list of element tag names to unwrap (remove the element tag but retain its child elements) - * during paste when __cleanPastedHTML__ is `true` or when - * calling `cleanPaste(text)` or `pasteHTML(html, options)` helper methods. - */ - unwrapTags: [], - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - if (this.forcePlainText || this.cleanPastedHTML) { - this.subscribe('editableKeydown', this.handleKeydown.bind(this)); - // We need access to the full event data in paste - // so we can't use the editablePaste event here - this.getEditorElements().forEach(function (element) { - this.on(element, 'paste', this.handlePaste.bind(this)); - }, this); - this.subscribe('addElement', this.handleAddElement.bind(this)); - } - }, - - handleAddElement: function (event, editable) { - this.on(editable, 'paste', this.handlePaste.bind(this)); - }, - - destroy: function () { - // Make sure pastebin is destroyed in case it's still around for some reason - if (this.forcePlainText || this.cleanPastedHTML) { - this.removePasteBin(); - } - }, - - handlePaste: function (event, editable) { - if (event.defaultPrevented) { - return; - } - - var clipboardContent = getClipboardContent(event, this.window, this.document), - pastedHTML = clipboardContent['text/html'], - pastedPlain = clipboardContent['text/plain']; - - if (this.window.clipboardData && event.clipboardData === undefined && !pastedHTML) { - // If window.clipboardData exists, but event.clipboardData doesn't exist, - // we're probably in IE. IE only has two possibilities for clipboard - // data format: 'Text' and 'URL'. - // - // For IE, we'll fallback to 'Text' for text/html - pastedHTML = pastedPlain; - } - - if (pastedHTML || pastedPlain) { - event.preventDefault(); - - this.doPaste(pastedHTML, pastedPlain, editable); - } - }, - - doPaste: function (pastedHTML, pastedPlain, editable) { - var paragraphs, - html = '', - p; - - if (this.cleanPastedHTML && pastedHTML) { - return this.cleanPaste(pastedHTML); - } - - if (!pastedPlain) { - return; - } - - if (!(this.getEditorOption('disableReturn') || (editable && editable.getAttribute('data-disable-return')))) { - paragraphs = pastedPlain.split(/[\r\n]+/g); - // If there are no \r\n in data, don't wrap in

      - if (paragraphs.length > 1) { - for (p = 0; p < paragraphs.length; p += 1) { - if (paragraphs[p] !== '') { - html += '

      ' + MediumEditor.util.htmlEntities(paragraphs[p]) + '

      '; - } - } - } else { - html = MediumEditor.util.htmlEntities(paragraphs[0]); - } - } else { - html = MediumEditor.util.htmlEntities(pastedPlain); - } - MediumEditor.util.insertHTMLCommand(this.document, html); - }, - - handlePasteBinPaste: function (event) { - if (event.defaultPrevented) { - this.removePasteBin(); - return; - } - - var clipboardContent = getClipboardContent(event, this.window, this.document), - pastedHTML = clipboardContent['text/html'], - pastedPlain = clipboardContent['text/plain'], - editable = keyboardPasteEditable; - - // If we have valid html already, or we're not in cleanPastedHTML mode - // we can ignore the paste bin and just paste now - if (!this.cleanPastedHTML || pastedHTML) { - event.preventDefault(); - this.removePasteBin(); - this.doPaste(pastedHTML, pastedPlain, editable); - - // The event handling code listens for paste on the editable element - // in order to trigger the editablePaste event. Since this paste event - // is happening on the pastebin, the event handling code never knows about it - // So, we have to trigger editablePaste manually - this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable); - return; - } - - // We need to look at the paste bin, so do a setTimeout to let the paste - // fall through into the paste bin - setTimeout(function () { - // Only look for HTML if we're in cleanPastedHTML mode - if (this.cleanPastedHTML) { - // If clipboard didn't have HTML, try the paste bin - pastedHTML = this.getPasteBinHtml(); - } - - // If we needed the paste bin, we're done with it now, remove it - this.removePasteBin(); - - // Handle the paste with the html from the paste bin - this.doPaste(pastedHTML, pastedPlain, editable); - - // The event handling code listens for paste on the editable element - // in order to trigger the editablePaste event. Since this paste event - // is happening on the pastebin, the event handling code never knows about it - // So, we have to trigger editablePaste manually - this.trigger('editablePaste', { currentTarget: editable, target: editable }, editable); - }.bind(this), 0); - }, - - handleKeydown: function (event, editable) { - // if it's not Ctrl+V, do nothing - if (!(MediumEditor.util.isKey(event, MediumEditor.util.keyCode.V) && MediumEditor.util.isMetaCtrlKey(event))) { - return; - } - - event.stopImmediatePropagation(); - - this.removePasteBin(); - this.createPasteBin(editable); - }, - - createPasteBin: function (editable) { - var rects, - range = MediumEditor.selection.getSelectionRange(this.document), - top = this.window.pageYOffset; - - keyboardPasteEditable = editable; - - if (range) { - rects = range.getClientRects(); - - // on empty line, rects is empty so we grab information from the first container of the range - if (rects.length) { - top += rects[0].top; - } else if (range.startContainer.getBoundingClientRect !== undefined) { - top += range.startContainer.getBoundingClientRect().top; - } else { - top += range.getBoundingClientRect().top; - } - } - - lastRange = range; - - var pasteBinElm = this.document.createElement('div'); - pasteBinElm.id = this.pasteBinId = 'medium-editor-pastebin-' + (+Date.now()); - pasteBinElm.setAttribute('style', 'border: 1px red solid; position: absolute; top: ' + top + 'px; width: 10px; height: 10px; overflow: hidden; opacity: 0'); - pasteBinElm.setAttribute('contentEditable', true); - pasteBinElm.innerHTML = pasteBinDefaultContent; - - this.document.body.appendChild(pasteBinElm); - - // avoid .focus() to stop other event (actually the paste event) - this.on(pasteBinElm, 'focus', stopProp); - this.on(pasteBinElm, 'focusin', stopProp); - this.on(pasteBinElm, 'focusout', stopProp); - - pasteBinElm.focus(); - - MediumEditor.selection.selectNode(pasteBinElm, this.document); - - if (!this.boundHandlePaste) { - this.boundHandlePaste = this.handlePasteBinPaste.bind(this); - } - - this.on(pasteBinElm, 'paste', this.boundHandlePaste); - }, - - removePasteBin: function () { - if (null !== lastRange) { - MediumEditor.selection.selectRange(this.document, lastRange); - lastRange = null; - } - - if (null !== keyboardPasteEditable) { - keyboardPasteEditable = null; - } - - var pasteBinElm = this.getPasteBin(); - if (!pasteBinElm) { - return; - } - - if (pasteBinElm) { - this.off(pasteBinElm, 'focus', stopProp); - this.off(pasteBinElm, 'focusin', stopProp); - this.off(pasteBinElm, 'focusout', stopProp); - this.off(pasteBinElm, 'paste', this.boundHandlePaste); - pasteBinElm.parentElement.removeChild(pasteBinElm); - } - }, - - getPasteBin: function () { - return this.document.getElementById(this.pasteBinId); - }, - - getPasteBinHtml: function () { - var pasteBinElm = this.getPasteBin(); - - if (!pasteBinElm) { - return false; - } - - // WebKit has a nice bug where it clones the paste bin if you paste from for example notepad - // so we need to force plain text mode in this case - if (pasteBinElm.firstChild && pasteBinElm.firstChild.id === 'mcepastebin') { - return false; - } - - var pasteBinHtml = pasteBinElm.innerHTML; - - // If paste bin is empty try using plain text mode - // since that is better than nothing right - if (!pasteBinHtml || pasteBinHtml === pasteBinDefaultContent) { - return false; - } - - return pasteBinHtml; - }, - - cleanPaste: function (text) { - var i, elList, tmp, workEl, - multiline = /
      ').join('

      ') + '

      '; - - // block element cleanup - elList = tmp.querySelectorAll('a,p,div,br'); - for (i = 0; i < elList.length; i += 1) { - workEl = elList[i]; - - // Microsoft Word replaces some spaces with newlines. - // While newlines between block elements are meaningless, newlines within - // elements are sometimes actually spaces. - workEl.innerHTML = workEl.innerHTML.replace(/\n/gi, ' '); - - switch (workEl.nodeName.toLowerCase()) { - case 'p': - case 'div': - this.filterCommonBlocks(workEl); - break; - case 'br': - this.filterLineBreak(workEl); - break; - } - } - - this.pasteHTML(tmp.innerHTML); - }, - - pasteHTML: function (html, options) { - options = MediumEditor.util.defaults({}, options, { - cleanAttrs: this.cleanAttrs, - cleanTags: this.cleanTags, - unwrapTags: this.unwrapTags - }); - - var elList, workEl, i, fragmentBody, pasteBlock = this.document.createDocumentFragment(); - - pasteBlock.appendChild(this.document.createElement('body')); - - fragmentBody = pasteBlock.querySelector('body'); - fragmentBody.innerHTML = html; - - this.cleanupSpans(fragmentBody); - - elList = fragmentBody.querySelectorAll('*'); - for (i = 0; i < elList.length; i += 1) { - workEl = elList[i]; - - if ('a' === workEl.nodeName.toLowerCase() && this.getEditorOption('targetBlank')) { - MediumEditor.util.setTargetBlank(workEl); - } - - MediumEditor.util.cleanupAttrs(workEl, options.cleanAttrs); - MediumEditor.util.cleanupTags(workEl, options.cleanTags); - MediumEditor.util.unwrapTags(workEl, options.unwrapTags); - } - - MediumEditor.util.insertHTMLCommand(this.document, fragmentBody.innerHTML.replace(/ /g, ' ')); - }, - - // TODO (6.0): Make this an internal helper instead of member of paste handler - isCommonBlock: function (el) { - return (el && (el.nodeName.toLowerCase() === 'p' || el.nodeName.toLowerCase() === 'div')); - }, - - // TODO (6.0): Make this an internal helper instead of member of paste handler - filterCommonBlocks: function (el) { - if (/^\s*$/.test(el.textContent) && el.parentNode) { - el.parentNode.removeChild(el); - } - }, - - // TODO (6.0): Make this an internal helper instead of member of paste handler - filterLineBreak: function (el) { - if (this.isCommonBlock(el.previousElementSibling)) { - // remove stray br's following common block elements - this.removeWithParent(el); - } else if (this.isCommonBlock(el.parentNode) && (el.parentNode.firstChild === el || el.parentNode.lastChild === el)) { - // remove br's just inside open or close tags of a div/p - this.removeWithParent(el); - } else if (el.parentNode && el.parentNode.childElementCount === 1 && el.parentNode.textContent === '') { - // and br's that are the only child of elements other than div/p - this.removeWithParent(el); - } - }, - - // TODO (6.0): Make this an internal helper instead of member of paste handler - // remove an element, including its parent, if it is the only element within its parent - removeWithParent: function (el) { - if (el && el.parentNode) { - if (el.parentNode.parentNode && el.parentNode.childElementCount === 1) { - el.parentNode.parentNode.removeChild(el.parentNode); - } else { - el.parentNode.removeChild(el); - } - } - }, - - // TODO (6.0): Make this an internal helper instead of member of paste handler - cleanupSpans: function (containerEl) { - var i, - el, - newEl, - spans = containerEl.querySelectorAll('.replace-with'), - isCEF = function (el) { - return (el && el.nodeName !== '#text' && el.getAttribute('contenteditable') === 'false'); - }; - - for (i = 0; i < spans.length; i += 1) { - el = spans[i]; - newEl = this.document.createElement(el.classList.contains('bold') ? 'b' : 'i'); - - if (el.classList.contains('bold') && el.classList.contains('italic')) { - // add an i tag as well if this has both italics and bold - newEl.innerHTML = '' + el.innerHTML + ''; - } else { - newEl.innerHTML = el.innerHTML; - } - el.parentNode.replaceChild(newEl, el); - } - - spans = containerEl.querySelectorAll('span'); - for (i = 0; i < spans.length; i += 1) { - el = spans[i]; - - // bail if span is in contenteditable = false - if (MediumEditor.util.traverseUp(el, isCEF)) { - return false; - } - - // remove empty spans, replace others with their contents - MediumEditor.util.unwrap(el, this.document); - } - } - }); - - MediumEditor.extensions.paste = PasteHandler; -}()); - -(function () { - 'use strict'; - - var Placeholder = MediumEditor.Extension.extend({ - name: 'placeholder', - - /* Placeholder Options */ - - /* text: [string] - * Text to display in the placeholder - */ - text: 'Type your text', - - /* hideOnClick: [boolean] - * Should we hide the placeholder on click (true) or when user starts typing (false) - */ - hideOnClick: true, - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.initPlaceholders(); - this.attachEventHandlers(); - }, - - initPlaceholders: function () { - this.getEditorElements().forEach(this.initElement, this); - }, - - handleAddElement: function (event, editable) { - this.initElement(editable); - }, - - initElement: function (el) { - if (!el.getAttribute('data-placeholder')) { - el.setAttribute('data-placeholder', this.text); - } - this.updatePlaceholder(el); - }, - - destroy: function () { - this.getEditorElements().forEach(this.cleanupElement, this); - }, - - handleRemoveElement: function (event, editable) { - this.cleanupElement(editable); - }, - - cleanupElement: function (el) { - if (el.getAttribute('data-placeholder') === this.text) { - el.removeAttribute('data-placeholder'); - } - }, - - showPlaceholder: function (el) { - if (el) { - // https://github.com/yabwe/medium-editor/issues/234 - // In firefox, styling the placeholder with an absolutely positioned - // pseudo element causes the cursor to appear in a bad location - // when the element is completely empty, so apply a different class to - // style it with a relatively positioned pseudo element - if (MediumEditor.util.isFF && el.childNodes.length === 0) { - el.classList.add('medium-editor-placeholder-relative'); - el.classList.remove('medium-editor-placeholder'); - } else { - el.classList.add('medium-editor-placeholder'); - el.classList.remove('medium-editor-placeholder-relative'); - } - } - }, - - hidePlaceholder: function (el) { - if (el) { - el.classList.remove('medium-editor-placeholder'); - el.classList.remove('medium-editor-placeholder-relative'); - } - }, - - updatePlaceholder: function (el, dontShow) { - // If the element has content, hide the placeholder - if (el.querySelector('img, blockquote, ul, ol, table') || (el.textContent.replace(/^\s+|\s+$/g, '') !== '')) { - return this.hidePlaceholder(el); - } - - if (!dontShow) { - this.showPlaceholder(el); - } - }, - - attachEventHandlers: function () { - if (this.hideOnClick) { - // For the 'hideOnClick' option, the placeholder should always be hidden on focus - this.subscribe('focus', this.handleFocus.bind(this)); - } - - // If the editor has content, it should always hide the placeholder - this.subscribe('editableInput', this.handleInput.bind(this)); - - // When the editor loses focus, check if the placeholder should be visible - this.subscribe('blur', this.handleBlur.bind(this)); - - // Need to know when elements are added/removed from the editor - this.subscribe('addElement', this.handleAddElement.bind(this)); - this.subscribe('removeElement', this.handleRemoveElement.bind(this)); - }, - - handleInput: function (event, element) { - // If the placeholder should be hidden on focus and the - // element has focus, don't show the placeholder - var dontShow = this.hideOnClick && (element === this.base.getFocusedElement()); - - // Editor's content has changed, check if the placeholder should be hidden - this.updatePlaceholder(element, dontShow); - }, - - handleFocus: function (event, element) { - // Editor has focus, hide the placeholder - this.hidePlaceholder(element); - }, - - handleBlur: function (event, element) { - // Editor has lost focus, check if the placeholder should be shown - this.updatePlaceholder(element); - } - }); - - MediumEditor.extensions.placeholder = Placeholder; -}()); - -(function () { - 'use strict'; - - var Toolbar = MediumEditor.Extension.extend({ - name: 'toolbar', - - /* Toolbar Options */ - - /* align: ['left'|'center'|'right'] - * When the __static__ option is true, this aligns the static toolbar - * relative to the medium-editor element. - */ - align: 'center', - - /* allowMultiParagraphSelection: [boolean] - * enables/disables whether the toolbar should be displayed when - * selecting multiple paragraphs/block elements - */ - allowMultiParagraphSelection: true, - - /* buttons: [Array] - * the names of the set of buttons to display on the toolbar. - */ - buttons: ['bold', 'italic', 'underline', 'anchor', 'h2', 'h3', 'quote'], - - /* diffLeft: [Number] - * value in pixels to be added to the X axis positioning of the toolbar. - */ - diffLeft: 0, - - /* diffTop: [Number] - * value in pixels to be added to the Y axis positioning of the toolbar. - */ - diffTop: -10, - - /* firstButtonClass: [string] - * CSS class added to the first button in the toolbar. - */ - firstButtonClass: 'medium-editor-button-first', - - /* lastButtonClass: [string] - * CSS class added to the last button in the toolbar. - */ - lastButtonClass: 'medium-editor-button-last', - - /* standardizeSelectionStart: [boolean] - * enables/disables standardizing how the beginning of a range is decided - * between browsers whenever the selected text is analyzed for updating toolbar buttons status. - */ - standardizeSelectionStart: false, - - /* static: [boolean] - * enable/disable the toolbar always displaying in the same location - * relative to the medium-editor element. - */ - static: false, - - /* sticky: [boolean] - * When the __static__ option is true, this enables/disables the toolbar - * "sticking" to the viewport and staying visible on the screen while - * the page scrolls. - */ - sticky: false, - - /* stickyTopOffset: [Number] - * Value in pixel of the top offset above the toolbar - */ - stickyTopOffset: 0, - - /* updateOnEmptySelection: [boolean] - * When the __static__ option is true, this enables/disables updating - * the state of the toolbar buttons even when the selection is collapsed - * (there is no selection, just a cursor). - */ - updateOnEmptySelection: false, - - /* relativeContainer: [node] - * appending the toolbar to a given node instead of body - */ - relativeContainer: null, - - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.initThrottledMethods(); - - if (!this.relativeContainer) { - this.getEditorOption('elementsContainer').appendChild(this.getToolbarElement()); - } else { - this.relativeContainer.appendChild(this.getToolbarElement()); - } - }, - - // Helper method to execute method for every extension, but ignoring the toolbar extension - forEachExtension: function (iterator, context) { - return this.base.extensions.forEach(function (command) { - if (command === this) { - return; - } - return iterator.apply(context || this, arguments); - }, this); - }, - - // Toolbar creation/deletion - - createToolbar: function () { - var toolbar = this.document.createElement('div'); - - toolbar.id = 'medium-editor-toolbar-' + this.getEditorId(); - toolbar.className = 'medium-editor-toolbar'; - - if (this.static) { - toolbar.className += ' static-toolbar'; - } else if (this.relativeContainer) { - toolbar.className += ' medium-editor-relative-toolbar'; - } else { - toolbar.className += ' medium-editor-stalker-toolbar'; - } - - toolbar.appendChild(this.createToolbarButtons()); - - // Add any forms that extensions may have - this.forEachExtension(function (extension) { - if (extension.hasForm) { - toolbar.appendChild(extension.getForm()); - } - }); - - this.attachEventHandlers(); - - return toolbar; - }, - - createToolbarButtons: function () { - var ul = this.document.createElement('ul'), - li, - btn, - buttons, - extension, - buttonName, - buttonOpts; - - ul.id = 'medium-editor-toolbar-actions' + this.getEditorId(); - ul.className = 'medium-editor-toolbar-actions'; - ul.style.display = 'block'; - - this.buttons.forEach(function (button) { - if (typeof button === 'string') { - buttonName = button; - buttonOpts = null; - } else { - buttonName = button.name; - buttonOpts = button; - } - - // If the button already exists as an extension, it'll be returned - // otherwise it'll create the default built-in button - extension = this.base.addBuiltInExtension(buttonName, buttonOpts); - - if (extension && typeof extension.getButton === 'function') { - btn = extension.getButton(this.base); - li = this.document.createElement('li'); - if (MediumEditor.util.isElement(btn)) { - li.appendChild(btn); - } else { - li.innerHTML = btn; - } - ul.appendChild(li); - } - }, this); - - buttons = ul.querySelectorAll('button'); - if (buttons.length > 0) { - buttons[0].classList.add(this.firstButtonClass); - buttons[buttons.length - 1].classList.add(this.lastButtonClass); - } - - return ul; - }, - - destroy: function () { - if (this.toolbar) { - if (this.toolbar.parentNode) { - this.toolbar.parentNode.removeChild(this.toolbar); - } - delete this.toolbar; - } - }, - - // Toolbar accessors - - getInteractionElements: function () { - return this.getToolbarElement(); - }, - - getToolbarElement: function () { - if (!this.toolbar) { - this.toolbar = this.createToolbar(); - } - - return this.toolbar; - }, - - getToolbarActionsElement: function () { - return this.getToolbarElement().querySelector('.medium-editor-toolbar-actions'); - }, - - // Toolbar event handlers - - initThrottledMethods: function () { - // throttledPositionToolbar is throttled because: - // - It will be called when the browser is resizing, which can fire many times very quickly - // - For some event (like resize) a slight lag in UI responsiveness is OK and provides performance benefits - this.throttledPositionToolbar = MediumEditor.util.throttle(function () { - if (this.base.isActive) { - this.positionToolbarIfShown(); - } - }.bind(this)); - }, - - attachEventHandlers: function () { - // MediumEditor custom events for when user beings and ends interaction with a contenteditable and its elements - this.subscribe('blur', this.handleBlur.bind(this)); - this.subscribe('focus', this.handleFocus.bind(this)); - - // Updating the state of the toolbar as things change - this.subscribe('editableClick', this.handleEditableClick.bind(this)); - this.subscribe('editableKeyup', this.handleEditableKeyup.bind(this)); - - // Handle mouseup on document for updating the selection in the toolbar - this.on(this.document.documentElement, 'mouseup', this.handleDocumentMouseup.bind(this)); - - // Add a scroll event for sticky toolbar - if (this.static && this.sticky) { - // On scroll (capture), re-position the toolbar - this.on(this.window, 'scroll', this.handleWindowScroll.bind(this), true); - } - - // On resize, re-position the toolbar - this.on(this.window, 'resize', this.handleWindowResize.bind(this)); - }, - - handleWindowScroll: function () { - this.positionToolbarIfShown(); - }, - - handleWindowResize: function () { - this.throttledPositionToolbar(); - }, - - handleDocumentMouseup: function (event) { - // Do not trigger checkState when mouseup fires over the toolbar - if (event && - event.target && - MediumEditor.util.isDescendant(this.getToolbarElement(), event.target)) { - return false; - } - this.checkState(); - }, - - handleEditableClick: function () { - // Delay the call to checkState to handle bug where selection is empty - // immediately after clicking inside a pre-existing selection - setTimeout(function () { - this.checkState(); - }.bind(this), 0); - }, - - handleEditableKeyup: function () { - this.checkState(); - }, - - handleBlur: function () { - // Kill any previously delayed calls to hide the toolbar - clearTimeout(this.hideTimeout); - - // Blur may fire even if we have a selection, so we want to prevent any delayed showToolbar - // calls from happening in this specific case - clearTimeout(this.delayShowTimeout); - - // Delay the call to hideToolbar to handle bug with multiple editors on the page at once - this.hideTimeout = setTimeout(function () { - this.hideToolbar(); - }.bind(this), 1); - }, - - handleFocus: function () { - this.checkState(); - }, - - // Hiding/showing toolbar - - isDisplayed: function () { - return this.getToolbarElement().classList.contains('medium-editor-toolbar-active'); - }, - - showToolbar: function () { - clearTimeout(this.hideTimeout); - if (!this.isDisplayed()) { - this.getToolbarElement().classList.add('medium-editor-toolbar-active'); - this.trigger('showToolbar', {}, this.base.getFocusedElement()); - } - }, - - hideToolbar: function () { - if (this.isDisplayed()) { - this.getToolbarElement().classList.remove('medium-editor-toolbar-active'); - this.trigger('hideToolbar', {}, this.base.getFocusedElement()); - } - }, - - isToolbarDefaultActionsDisplayed: function () { - return this.getToolbarActionsElement().style.display === 'block'; - }, - - hideToolbarDefaultActions: function () { - if (this.isToolbarDefaultActionsDisplayed()) { - this.getToolbarActionsElement().style.display = 'none'; - } - }, - - showToolbarDefaultActions: function () { - this.hideExtensionForms(); - - if (!this.isToolbarDefaultActionsDisplayed()) { - this.getToolbarActionsElement().style.display = 'block'; - } - - // Using setTimeout + options.delay because: - // We will actually be displaying the toolbar, which should be controlled by options.delay - this.delayShowTimeout = this.base.delay(function () { - this.showToolbar(); - }.bind(this)); - }, - - hideExtensionForms: function () { - // Hide all extension forms - this.forEachExtension(function (extension) { - if (extension.hasForm && extension.isDisplayed()) { - extension.hideForm(); - } - }); - }, - - // Responding to changes in user selection - - // Checks for existence of multiple block elements in the current selection - multipleBlockElementsSelected: function () { - var regexEmptyHTMLTags = /<[^\/>][^>]*><\/[^>]+>/gim, // http://stackoverflow.com/questions/3129738/remove-empty-tags-using-regex - regexBlockElements = new RegExp('<(' + MediumEditor.util.blockContainerElementNames.join('|') + ')[^>]*>', 'g'), - selectionHTML = MediumEditor.selection.getSelectionHtml(this.document).replace(regexEmptyHTMLTags, ''), // Filter out empty blocks from selection - hasMultiParagraphs = selectionHTML.match(regexBlockElements); // Find how many block elements are within the html - - return !!hasMultiParagraphs && hasMultiParagraphs.length > 1; - }, - - modifySelection: function () { - var selection = this.window.getSelection(), - selectionRange = selection.getRangeAt(0); - - /* - * In firefox, there are cases (ie doubleclick of a word) where the selectionRange start - * will be at the very end of an element. In other browsers, the selectionRange start - * would instead be at the very beginning of an element that actually has content. - * example: - * foobar - * - * If the text 'bar' is selected, most browsers will have the selectionRange start at the beginning - * of the 'bar' span. However, there are cases where firefox will have the selectionRange start - * at the end of the 'foo' span. The contenteditable behavior will be ok, but if there are any - * properties on the 'bar' span, they won't be reflected accurately in the toolbar - * (ie 'Bold' button wouldn't be active) - * - * So, for cases where the selectionRange start is at the end of an element/node, find the next - * adjacent text node that actually has content in it, and move the selectionRange start there. - */ - if (this.standardizeSelectionStart && - selectionRange.startContainer.nodeValue && - (selectionRange.startOffset === selectionRange.startContainer.nodeValue.length)) { - var adjacentNode = MediumEditor.util.findAdjacentTextNodeWithContent(MediumEditor.selection.getSelectionElement(this.window), selectionRange.startContainer, this.document); - if (adjacentNode) { - var offset = 0; - while (adjacentNode.nodeValue.substr(offset, 1).trim().length === 0) { - offset = offset + 1; - } - selectionRange = MediumEditor.selection.select(this.document, adjacentNode, offset, - selectionRange.endContainer, selectionRange.endOffset); - } - } - }, - - checkState: function () { - if (this.base.preventSelectionUpdates) { - return; - } - - // If no editable has focus OR selection is inside contenteditable = false - // hide toolbar - if (!this.base.getFocusedElement() || - MediumEditor.selection.selectionInContentEditableFalse(this.window)) { - return this.hideToolbar(); - } - - // If there's no selection element, selection element doesn't belong to this editor - // or toolbar is disabled for this selection element - // hide toolbar - var selectionElement = MediumEditor.selection.getSelectionElement(this.window); - if (!selectionElement || - this.getEditorElements().indexOf(selectionElement) === -1 || - selectionElement.getAttribute('data-disable-toolbar')) { - return this.hideToolbar(); - } - - // Now we know there's a focused editable with a selection - - // If the updateOnEmptySelection option is true, show the toolbar - if (this.updateOnEmptySelection && this.static) { - return this.showAndUpdateToolbar(); - } - - // If we don't have a 'valid' selection -> hide toolbar - if (!MediumEditor.selection.selectionContainsContent(this.document) || - (this.allowMultiParagraphSelection === false && this.multipleBlockElementsSelected())) { - return this.hideToolbar(); - } - - this.showAndUpdateToolbar(); - }, - - // Updating the toolbar - - showAndUpdateToolbar: function () { - this.modifySelection(); - this.setToolbarButtonStates(); - this.trigger('positionToolbar', {}, this.base.getFocusedElement()); - this.showToolbarDefaultActions(); - this.setToolbarPosition(); - }, - - setToolbarButtonStates: function () { - this.forEachExtension(function (extension) { - if (typeof extension.isActive === 'function' && - typeof extension.setInactive === 'function') { - extension.setInactive(); - } - }); - - this.checkActiveButtons(); - }, - - checkActiveButtons: function () { - var manualStateChecks = [], - queryState = null, - selectionRange = MediumEditor.selection.getSelectionRange(this.document), - parentNode, - updateExtensionState = function (extension) { - if (typeof extension.checkState === 'function') { - extension.checkState(parentNode); - } else if (typeof extension.isActive === 'function' && - typeof extension.isAlreadyApplied === 'function' && - typeof extension.setActive === 'function') { - if (!extension.isActive() && extension.isAlreadyApplied(parentNode)) { - extension.setActive(); - } - } - }; - - if (!selectionRange) { - return; - } - - // Loop through all extensions - this.forEachExtension(function (extension) { - // For those extensions where we can use document.queryCommandState(), do so - if (typeof extension.queryCommandState === 'function') { - queryState = extension.queryCommandState(); - // If queryCommandState returns a valid value, we can trust the browser - // and don't need to do our manual checks - if (queryState !== null) { - if (queryState && typeof extension.setActive === 'function') { - extension.setActive(); - } - return; - } - } - // We can't use queryCommandState for this extension, so add to manualStateChecks - manualStateChecks.push(extension); - }); - - parentNode = MediumEditor.selection.getSelectedParentElement(selectionRange); - - // Make sure the selection parent isn't outside of the contenteditable - if (!this.getEditorElements().some(function (element) { - return MediumEditor.util.isDescendant(element, parentNode, true); - })) { - return; - } - - // Climb up the DOM and do manual checks for whether a certain extension is currently enabled for this node - while (parentNode) { - manualStateChecks.forEach(updateExtensionState); - - // we can abort the search upwards if we leave the contentEditable element - if (MediumEditor.util.isMediumEditorElement(parentNode)) { - break; - } - parentNode = parentNode.parentNode; - } - }, - - // Positioning toolbar - - positionToolbarIfShown: function () { - if (this.isDisplayed()) { - this.setToolbarPosition(); - } - }, - - setToolbarPosition: function () { - var container = this.base.getFocusedElement(), - selection = this.window.getSelection(); - - // If there isn't a valid selection, bail - if (!container) { - return this; - } - - if (this.static || !selection.isCollapsed) { - this.showToolbar(); - - // we don't need any absolute positioning if relativeContainer is set - if (!this.relativeContainer) { - if (this.static) { - this.positionStaticToolbar(container); - } else { - this.positionToolbar(selection); - } - } - - this.trigger('positionedToolbar', {}, this.base.getFocusedElement()); - } - }, - - positionStaticToolbar: function (container) { - // position the toolbar at left 0, so we can get the real width of the toolbar - this.getToolbarElement().style.left = '0'; - - // document.documentElement for IE 9 - var scrollTop = (this.document.documentElement && this.document.documentElement.scrollTop) || this.document.body.scrollTop, - windowWidth = this.window.innerWidth, - toolbarElement = this.getToolbarElement(), - containerRect = container.getBoundingClientRect(), - containerTop = containerRect.top + scrollTop, - containerCenter = (containerRect.left + (containerRect.width / 2)), - toolbarHeight = toolbarElement.offsetHeight, - toolbarWidth = toolbarElement.offsetWidth, - halfOffsetWidth = toolbarWidth / 2, - targetLeft; - - if (this.sticky) { - // If it's beyond the height of the editor, position it at the bottom of the editor - if (scrollTop > (containerTop + container.offsetHeight - toolbarHeight - this.stickyTopOffset)) { - toolbarElement.style.top = (containerTop + container.offsetHeight - toolbarHeight) + 'px'; - toolbarElement.classList.remove('medium-editor-sticky-toolbar'); - // Stick the toolbar to the top of the window - } else if (scrollTop > (containerTop - toolbarHeight - this.stickyTopOffset)) { - toolbarElement.classList.add('medium-editor-sticky-toolbar'); - toolbarElement.style.top = this.stickyTopOffset + 'px'; - // Normal static toolbar position - } else { - toolbarElement.classList.remove('medium-editor-sticky-toolbar'); - toolbarElement.style.top = containerTop - toolbarHeight + 'px'; - } - } else { - toolbarElement.style.top = containerTop - toolbarHeight + 'px'; - } - - switch (this.align) { - case 'left': - targetLeft = containerRect.left; - break; - - case 'right': - targetLeft = containerRect.right - toolbarWidth; - break; - - case 'center': - targetLeft = containerCenter - halfOffsetWidth; - break; - } - - if (targetLeft < 0) { - targetLeft = 0; - } else if ((targetLeft + toolbarWidth) > windowWidth) { - targetLeft = (windowWidth - Math.ceil(toolbarWidth) - 1); - } - - toolbarElement.style.left = targetLeft + 'px'; - }, - - positionToolbar: function (selection) { - // position the toolbar at left 0, so we can get the real width of the toolbar - this.getToolbarElement().style.left = '0'; - this.getToolbarElement().style.right = 'initial'; - - var range = selection.getRangeAt(0), - boundary = range.getBoundingClientRect(); - - // Handle selections with just images - if (!boundary || ((boundary.height === 0 && boundary.width === 0) && range.startContainer === range.endContainer)) { - // If there's a nested image, use that for the bounding rectangle - if (range.startContainer.nodeType === 1 && range.startContainer.querySelector('img')) { - boundary = range.startContainer.querySelector('img').getBoundingClientRect(); - } else { - boundary = range.startContainer.getBoundingClientRect(); - } - } - - var containerWidth = this.window.innerWidth, - toolbarElement = this.getToolbarElement(), - toolbarHeight = toolbarElement.offsetHeight, - toolbarWidth = toolbarElement.offsetWidth, - halfOffsetWidth = toolbarWidth / 2, - buttonHeight = 50, - defaultLeft = this.diffLeft - halfOffsetWidth, - elementsContainer = this.getEditorOption('elementsContainer'), - elementsContainerAbsolute = ['absolute', 'fixed'].indexOf(window.getComputedStyle(elementsContainer).getPropertyValue('position')) > -1, - positions = {}, - relativeBoundary = {}, - middleBoundary, elementsContainerBoundary; - - // If container element is absolute / fixed, recalculate boundaries to be relative to the container - if (elementsContainerAbsolute) { - elementsContainerBoundary = elementsContainer.getBoundingClientRect(); - ['top', 'left'].forEach(function (key) { - relativeBoundary[key] = boundary[key] - elementsContainerBoundary[key]; - }); - - relativeBoundary.width = boundary.width; - relativeBoundary.height = boundary.height; - boundary = relativeBoundary; - - containerWidth = elementsContainerBoundary.width; - - // Adjust top position according to container scroll position - positions.top = elementsContainer.scrollTop; - } else { - // Adjust top position according to window scroll position - positions.top = this.window.pageYOffset; - } - - middleBoundary = boundary.left + boundary.width / 2; - positions.top += boundary.top - toolbarHeight; - - if (boundary.top < buttonHeight) { - toolbarElement.classList.add('medium-toolbar-arrow-over'); - toolbarElement.classList.remove('medium-toolbar-arrow-under'); - positions.top += buttonHeight + boundary.height - this.diffTop; - } else { - toolbarElement.classList.add('medium-toolbar-arrow-under'); - toolbarElement.classList.remove('medium-toolbar-arrow-over'); - positions.top += this.diffTop; - } - - if (middleBoundary < halfOffsetWidth) { - positions.left = defaultLeft + halfOffsetWidth; - positions.right = 'initial'; - } else if ((containerWidth - middleBoundary) < halfOffsetWidth) { - positions.left = 'auto'; - positions.right = 0; - } else { - positions.left = defaultLeft + middleBoundary; - positions.right = 'initial'; - } - - ['top', 'left', 'right'].forEach(function (key) { - toolbarElement.style[key] = positions[key] + (isNaN(positions[key]) ? '' : 'px'); - }); - } - }); - - MediumEditor.extensions.toolbar = Toolbar; -}()); - -(function () { - 'use strict'; - - var ImageDragging = MediumEditor.Extension.extend({ - init: function () { - MediumEditor.Extension.prototype.init.apply(this, arguments); - - this.subscribe('editableDrag', this.handleDrag.bind(this)); - this.subscribe('editableDrop', this.handleDrop.bind(this)); - }, - - handleDrag: function (event) { - var className = 'medium-editor-dragover'; - event.preventDefault(); - event.dataTransfer.dropEffect = 'copy'; - - if (event.type === 'dragover') { - event.target.classList.add(className); - } else if (event.type === 'dragleave') { - event.target.classList.remove(className); - } - }, - - handleDrop: function (event) { - var className = 'medium-editor-dragover', - files; - event.preventDefault(); - event.stopPropagation(); - - // IE9 does not support the File API, so prevent file from opening in a new window - // but also don't try to actually get the file - if (event.dataTransfer.files) { - files = Array.prototype.slice.call(event.dataTransfer.files, 0); - files.some(function (file) { - if (file.type.match('image')) { - var fileReader, id; - fileReader = new FileReader(); - fileReader.readAsDataURL(file); - - id = 'medium-img-' + (+new Date()); - MediumEditor.util.insertHTMLCommand(this.document, ''); - - fileReader.onload = function () { - var img = this.document.getElementById(id); - if (img) { - img.removeAttribute('id'); - img.removeAttribute('class'); - img.src = fileReader.result; - } - }.bind(this); - } - }.bind(this)); - } - event.target.classList.remove(className); - } - }); - - MediumEditor.extensions.imageDragging = ImageDragging; -}()); - -(function () { - 'use strict'; - - // Event handlers that shouldn't be exposed externally - - function handleDisableExtraSpaces (event) { - var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), - textContent = node.textContent, - caretPositions = MediumEditor.selection.getCaretOffsets(node); - - if ((textContent[caretPositions.left - 1] === undefined) || (textContent[caretPositions.left - 1].trim() === '') || (textContent[caretPositions.left] !== undefined && textContent[caretPositions.left].trim() === '')) { - event.preventDefault(); - } - } - - function handleDisabledEnterKeydown (event, element) { - if (this.options.disableReturn || element.getAttribute('data-disable-return')) { - event.preventDefault(); - } else if (this.options.disableDoubleReturn || element.getAttribute('data-disable-double-return')) { - var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument); - - // if current text selection is empty OR previous sibling text is empty OR it is not a list - if ((node && node.textContent.trim() === '' && node.nodeName.toLowerCase() !== 'li') || - (node.previousElementSibling && node.previousElementSibling.nodeName.toLowerCase() !== 'br' && - node.previousElementSibling.textContent.trim() === '')) { - event.preventDefault(); - } - } - } - - function handleTabKeydown (event) { - // Override tab only for pre nodes - var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), - tag = node && node.nodeName.toLowerCase(); - - if (tag === 'pre') { - event.preventDefault(); - MediumEditor.util.insertHTMLCommand(this.options.ownerDocument, ' '); - } - - // Tab to indent list structures! - if (MediumEditor.util.isListItem(node)) { - event.preventDefault(); - - // If Shift is down, outdent, otherwise indent - if (event.shiftKey) { - this.options.ownerDocument.execCommand('outdent', false, null); - } else { - this.options.ownerDocument.execCommand('indent', false, null); - } - } - } - - function handleBlockDeleteKeydowns (event) { - var p, node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), - tagName = node.nodeName.toLowerCase(), - isEmpty = /^(\s+|)?$/i, - isHeader = /h\d/i; - - if (MediumEditor.util.isKey(event, [MediumEditor.util.keyCode.BACKSPACE, MediumEditor.util.keyCode.ENTER]) && - // has a preceding sibling - node.previousElementSibling && - // in a header - isHeader.test(tagName) && - // at the very end of the block - MediumEditor.selection.getCaretOffsets(node).left === 0) { - if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && isEmpty.test(node.previousElementSibling.innerHTML)) { - // backspacing the beginning of a header into an empty previous element will - // change the tagName of the current node to prevent one - // instead delete previous node and cancel the event. - node.previousElementSibling.parentNode.removeChild(node.previousElementSibling); - event.preventDefault(); - } else if (!this.options.disableDoubleReturn && MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER)) { - // hitting return in the beginning of a header will create empty header elements before the current one - // instead, make "


      " element, which are what happens if you hit return in an empty paragraph - p = this.options.ownerDocument.createElement('p'); - p.innerHTML = '
      '; - node.previousElementSibling.parentNode.insertBefore(p, node); - event.preventDefault(); - } - } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.DELETE) && - // between two sibling elements - node.nextElementSibling && - node.previousElementSibling && - // not in a header - !isHeader.test(tagName) && - // in an empty tag - isEmpty.test(node.innerHTML) && - // when the next tag *is* a header - isHeader.test(node.nextElementSibling.nodeName.toLowerCase())) { - // hitting delete in an empty element preceding a header, ex: - //

      [CURSOR]

      Header

      - // Will cause the h1 to become a paragraph. - // Instead, delete the paragraph node and move the cursor to the beginning of the h1 - - // remove node and move cursor to start of header - MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextElementSibling); - - node.previousElementSibling.parentNode.removeChild(node); - - event.preventDefault(); - } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - tagName === 'li' && - // hitting backspace inside an empty li - isEmpty.test(node.innerHTML) && - // is first element (no preceding siblings) - !node.previousElementSibling && - // parent also does not have a sibling - !node.parentElement.previousElementSibling && - // is not the only li in a list - node.nextElementSibling && - node.nextElementSibling.nodeName.toLowerCase() === 'li') { - // backspacing in an empty first list element in the first list (with more elements) ex: - //
      • [CURSOR]
      • List Item 2
      - // will remove the first
    2. but add some extra element before (varies based on browser) - // Instead, this will: - // 1) remove the list element - // 2) create a paragraph before the list - // 3) move the cursor into the paragraph - - // create a paragraph before the list - p = this.options.ownerDocument.createElement('p'); - p.innerHTML = '
      '; - node.parentElement.parentElement.insertBefore(p, node.parentElement); - - // move the cursor into the new paragraph - MediumEditor.selection.moveCursor(this.options.ownerDocument, p); - - // remove the list element - node.parentElement.removeChild(node); - - event.preventDefault(); - } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).left === 0) { - - // when cursor is at the beginning of the element and the element is
      - // then pressing backspace key should change the
      to a

      tag - event.preventDefault(); - MediumEditor.util.execFormatBlock(this.options.ownerDocument, 'p'); - } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && - (MediumEditor.util.getClosestTag(node, 'blockquote') !== false) && - MediumEditor.selection.getCaretOffsets(node).right === 0) { - - // when cursor is at the end of

      , - // then pressing enter key should create

      tag, not

      - p = this.options.ownerDocument.createElement('p'); - p.innerHTML = '
      '; - node.parentElement.insertBefore(p, node.nextSibling); - - // move the cursor into the new paragraph - MediumEditor.selection.moveCursor(this.options.ownerDocument, p); - - event.preventDefault(); - } else if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.BACKSPACE) && - MediumEditor.util.isMediumEditorElement(node.parentElement) && - !node.previousElementSibling && - node.nextElementSibling && - isEmpty.test(node.innerHTML)) { - - // when cursor is in the first element, it's empty and user presses backspace, - // do delete action instead to get rid of the first element and move caret to 2nd - event.preventDefault(); - MediumEditor.selection.moveCursor(this.options.ownerDocument, node.nextSibling); - node.parentElement.removeChild(node); - } - } - - function handleKeyup (event) { - var node = MediumEditor.selection.getSelectionStart(this.options.ownerDocument), - tagName; - - if (!node) { - return; - } - - // https://github.com/yabwe/medium-editor/issues/994 - // Firefox thrown an error when calling `formatBlock` on an empty editable blockContainer that's not a
      - if (MediumEditor.util.isMediumEditorElement(node) && node.children.length === 0 && !MediumEditor.util.isBlockContainer(node)) { - this.options.ownerDocument.execCommand('formatBlock', false, 'p'); - } - - // https://github.com/yabwe/medium-editor/issues/1455 - // if somehow we have the BR as the selected element, typing does nothing, so move the cursor - if (node.nodeName === 'BR') { - MediumEditor.selection.moveCursor(this.options.ownerDocument, node.parentElement); - } - - // https://github.com/yabwe/medium-editor/issues/834 - // https://github.com/yabwe/medium-editor/pull/382 - // Don't call format block if this is a block element (ie h1, figCaption, etc.) - if (MediumEditor.util.isKey(event, MediumEditor.util.keyCode.ENTER) && - !MediumEditor.util.isListItem(node) && - !MediumEditor.util.isBlockContainer(node)) { - - tagName = node.nodeName.toLowerCase(); - // For anchor tags, unlink - if (tagName === 'a') { - this.options.ownerDocument.execCommand('unlink', false, null); - } else if (!event.shiftKey && !event.ctrlKey) { - this.options.ownerDocument.execCommand('formatBlock', false, 'p'); - // https://github.com/yabwe/medium-editor/issues/1455 - // firefox puts the focus on the br - so we need to move the cursor to the newly created p - if (MediumEditor.util.isFF) { - var newParagraph = node.querySelector('p'); - if (newParagraph) { - MediumEditor.selection.moveCursor(this.options.ownerDocument, newParagraph); - } - } - } - } - } - - function handleEditableInput (event, editable) { - var textarea = editable.parentNode.querySelector('textarea[medium-editor-textarea-id="' + editable.getAttribute('medium-editor-textarea-id') + '"]'); - if (textarea) { - textarea.value = editable.innerHTML.trim(); - } - } - - // Internal helper methods which shouldn't be exposed externally - - function addToEditors (win) { - if (!win._mediumEditors) { - // To avoid breaking users who are assuming that the unique id on - // medium-editor elements will start at 1, inserting a 'null' in the - // array so the unique-id can always map to the index of the editor instance - win._mediumEditors = [null]; - } - - // If this already has a unique id, re-use it - if (!this.id) { - this.id = win._mediumEditors.length; - } - - win._mediumEditors[this.id] = this; - } - - function removeFromEditors (win) { - if (!win._mediumEditors || !win._mediumEditors[this.id]) { - return; - } - - /* Setting the instance to null in the array instead of deleting it allows: - * 1) Each instance to preserve its own unique-id, even after being destroyed - * and initialized again - * 2) The unique-id to always correspond to an index in the array of medium-editor - * instances. Thus, we will be able to look at a contenteditable, and determine - * which instance it belongs to, by indexing into the global array. - */ - win._mediumEditors[this.id] = null; - } - - function createElementsArray (selector, doc, filterEditorElements) { - var elements = []; - - if (!selector) { - selector = []; - } - // If string, use as query selector - if (typeof selector === 'string') { - selector = doc.querySelectorAll(selector); - } - // If element, put into array - if (MediumEditor.util.isElement(selector)) { - selector = [selector]; - } - - if (filterEditorElements) { - // Remove elements that have already been initialized by the editor - // selecotr might not be an array (ie NodeList) so use for loop - for (var i = 0; i < selector.length; i++) { - var el = selector[i]; - if (MediumEditor.util.isElement(el) && - !el.getAttribute('data-medium-editor-element') && - !el.getAttribute('medium-editor-textarea-id')) { - elements.push(el); - } - } - } else { - // Convert NodeList (or other array like object) into an array - elements = Array.prototype.slice.apply(selector); - } - - return elements; - } - - function cleanupTextareaElement (element) { - var textarea = element.parentNode.querySelector('textarea[medium-editor-textarea-id="' + element.getAttribute('medium-editor-textarea-id') + '"]'); - if (textarea) { - // Un-hide the textarea - textarea.classList.remove('medium-editor-hidden'); - textarea.removeAttribute('medium-editor-textarea-id'); - } - if (element.parentNode) { - element.parentNode.removeChild(element); - } - } - - function setExtensionDefaults (extension, defaults) { - Object.keys(defaults).forEach(function (prop) { - if (extension[prop] === undefined) { - extension[prop] = defaults[prop]; - } - }); - return extension; - } - - function initExtension (extension, name, instance) { - var extensionDefaults = { - 'window': instance.options.contentWindow, - 'document': instance.options.ownerDocument, - 'base': instance - }; - - // Add default options into the extension - extension = setExtensionDefaults(extension, extensionDefaults); - - // Call init on the extension - if (typeof extension.init === 'function') { - extension.init(); - } - - // Set extension name (if not already set) - if (!extension.name) { - extension.name = name; - } - return extension; - } - - function isToolbarEnabled () { - // If any of the elements don't have the toolbar disabled - // We need a toolbar - if (this.elements.every(function (element) { - return !!element.getAttribute('data-disable-toolbar'); - })) { - return false; - } - - return this.options.toolbar !== false; - } - - function isAnchorPreviewEnabled () { - // If toolbar is disabled, don't add - if (!isToolbarEnabled.call(this)) { - return false; - } - - return this.options.anchorPreview !== false; - } - - function isPlaceholderEnabled () { - return this.options.placeholder !== false; - } - - function isAutoLinkEnabled () { - return this.options.autoLink !== false; - } - - function isImageDraggingEnabled () { - return this.options.imageDragging !== false; - } - - function isKeyboardCommandsEnabled () { - return this.options.keyboardCommands !== false; - } - - function shouldUseFileDraggingExtension () { - // Since the file-dragging extension replaces the image-dragging extension, - // we need to check if the user passed an overridden image-dragging extension. - // If they have, to avoid breaking users, we won't use file-dragging extension. - return !this.options.extensions['imageDragging']; - } - - function createContentEditable (textarea) { - var div = this.options.ownerDocument.createElement('div'), - now = Date.now(), - uniqueId = 'medium-editor-' + now, - attributes = textarea.attributes; - - // Some browsers can move pretty fast, since we're using a timestamp - // to make a unique-id, ensure that the id is actually unique on the page - while (this.options.ownerDocument.getElementById(uniqueId)) { - now++; - uniqueId = 'medium-editor-' + now; - } - - div.className = textarea.className; - div.id = uniqueId; - div.innerHTML = textarea.value; - - textarea.setAttribute('medium-editor-textarea-id', uniqueId); - - // re-create all attributes from the textarea to the new created div - for (var i = 0, n = attributes.length; i < n; i++) { - // do not re-create existing attributes - if (!div.hasAttribute(attributes[i].nodeName)) { - div.setAttribute(attributes[i].nodeName, attributes[i].value); - } - } - - // If textarea has a form, listen for reset on the form to clear - // the content of the created div - if (textarea.form) { - this.on(textarea.form, 'reset', function (event) { - if (!event.defaultPrevented) { - this.resetContent(this.options.ownerDocument.getElementById(uniqueId)); - } - }.bind(this)); - } - - textarea.classList.add('medium-editor-hidden'); - textarea.parentNode.insertBefore( - div, - textarea - ); - - return div; - } - - var initialContent = {}; - - function initElement (element, editorId) { - if (!element.getAttribute('data-medium-editor-element')) { - if (element.nodeName.toLowerCase() === 'textarea') { - element = createContentEditable.call(this, element); - - // Make sure we only attach to editableInput once for
  • "===t)return e.execCommand("outdent",!1,t);if((f.isFF||f.isEdge)&&"p"===t)return Array.prototype.slice.call(n.childNodes).some(function(e){return!f.isBlockContainer(e)})&&e.execCommand("formatBlock",!1,t),e.execCommand("outdent",!1,t)}return e.execCommand("formatBlock",!1,t)},setTargetBlank:function(e,t){var n,i=t||!1;if("a"===e.nodeName.toLowerCase())e.target="_blank",e.rel="noopener noreferrer";else for(e=e.getElementsByTagName("a"),n=0;n=l&&e.start<=s&&(m||e.start=l&&e.end<=s&&(e.trailingImageCount?d=!0:(o.setEnd(r,e.end-l),h=!0)),l=s;h||(r=a.pop())}!c&&f&&(o.setStart(f,f.length),o.setEnd(f,f.length)),void 0!==e.emptyBlocksIndex&&(o=this.importSelectionMoveCursorPastBlocks(n,t,e.emptyBlocksIndex,o)),i&&(o=this.importSelectionMoveCursorPastAnchor(e,o)),this.selectRange(n,o)}},importSelectionMoveCursorPastAnchor:function(e,t){if(e.start===e.end&&3===t.startContainer.nodeType&&t.startOffset===t.startContainer.nodeValue.length&&b.util.traverseUp(t.startContainer,function(e){return"a"===e.nodeName.toLowerCase()})){for(var n=t.startContainer,i=t.startContainer.parentNode;null!==i&&"a"!==i.nodeName.toLowerCase();)i=i.childNodes[i.childNodes.length-1]!==n?null:(n=i).parentNode;if(null!==i&&"a"===i.nodeName.toLowerCase()){for(var o=null,s=0;null===o&&s=l&&t.start<=s&&(c=!0),c&&t.end>=l&&t.end<=s&&(d=!0),l=s;u||(r=a.pop())}return h},selectionContainsContent:function(e){var t=e.getSelection();if(!t||t.isCollapsed||!t.rangeCount)return!1;if(""!==t.toString().trim())return!0;var n=this.getSelectedParentElement(t.getRangeAt(0));return!(!n||!("img"===n.nodeName.toLowerCase()||1===n.nodeType&&n.querySelector("img")))},selectionInContentEditableFalse:function(e){var n,t=this.findMatchingSelectionParent(function(e){var t=e&&e.getAttribute("contenteditable");return"true"===t&&(n=!0),"#text"!==e.nodeName&&"false"===t},e);return!n&&t},getSelectionHtml:function(e){var t,n,i,o="",s=e.getSelection();if(s.rangeCount){for(i=e.createElement("div"),t=0,n=s.rangeCount;tB",contentFA:''},italic:{name:"italic",action:"italic",aria:"italic",tagNames:["i","em"],style:{prop:"font-style",value:"italic"},useQueryState:!0,contentDefault:"I",contentFA:''},underline:{name:"underline",action:"underline",aria:"underline",tagNames:["u"],style:{prop:"text-decoration",value:"underline"},useQueryState:!0,contentDefault:"U",contentFA:''},strikethrough:{name:"strikethrough",action:"strikethrough",aria:"strike through",tagNames:["strike"],style:{prop:"text-decoration",value:"line-through"},useQueryState:!0,contentDefault:"A",contentFA:''},superscript:{name:"superscript",action:"superscript",aria:"superscript",tagNames:["sup"],contentDefault:"x1",contentFA:''},subscript:{name:"subscript",action:"subscript",aria:"subscript",tagNames:["sub"],contentDefault:"x1",contentFA:''},image:{name:"image",action:"image",aria:"image",tagNames:["img"],contentDefault:"image",contentFA:''},html:{name:"html",action:"html",aria:"evaluate html",tagNames:["iframe","object"],contentDefault:"html",contentFA:''},orderedlist:{name:"orderedlist",action:"insertorderedlist",aria:"ordered list",tagNames:["ol"],useQueryState:!0,contentDefault:"1.",contentFA:''},unorderedlist:{name:"unorderedlist",action:"insertunorderedlist",aria:"unordered list",tagNames:["ul"],useQueryState:!0,contentDefault:"",contentFA:''},indent:{name:"indent",action:"indent",aria:"indent",tagNames:[],contentDefault:"",contentFA:''},outdent:{name:"outdent",action:"outdent",aria:"outdent",tagNames:[],contentDefault:"",contentFA:''},justifyCenter:{name:"justifyCenter",action:"justifyCenter",aria:"center justify",tagNames:[],style:{prop:"text-align",value:"center"},contentDefault:"C",contentFA:''},justifyFull:{name:"justifyFull",action:"justifyFull",aria:"full justify",tagNames:[],style:{prop:"text-align",value:"justify"},contentDefault:"J",contentFA:''},justifyLeft:{name:"justifyLeft",action:"justifyLeft",aria:"left justify",tagNames:[],style:{prop:"text-align",value:"left"},contentDefault:"L",contentFA:''},justifyRight:{name:"justifyRight",action:"justifyRight",aria:"right justify",tagNames:[],style:{prop:"text-align",value:"right"},contentDefault:"R",contentFA:''},removeFormat:{name:"removeFormat",aria:"remove formatting",action:"removeFormat",contentDefault:"X",contentFA:''},quote:{name:"quote",action:"append-blockquote",aria:"blockquote",tagNames:["blockquote"],contentDefault:"",contentFA:''},pre:{name:"pre",action:"append-pre",aria:"preformatted text",tagNames:["pre"],contentDefault:"0101",contentFA:''},h1:{name:"h1",action:"append-h1",aria:"header type one",tagNames:["h1"],contentDefault:"H1",contentFA:'1'},h2:{name:"h2",action:"append-h2",aria:"header type two",tagNames:["h2"],contentDefault:"H2",contentFA:'2'},h3:{name:"h3",action:"append-h3",aria:"header type three",tagNames:["h3"],contentDefault:"H3",contentFA:'3'},h4:{name:"h4",action:"append-h4",aria:"header type four",tagNames:["h4"],contentDefault:"H4",contentFA:'4'},h5:{name:"h5",action:"append-h5",aria:"header type five",tagNames:["h5"],contentDefault:"H5",contentFA:'5'},h6:{name:"h6",action:"append-h6",aria:"header type six",tagNames:["h6"],contentDefault:"H6",contentFA:'6'}},i=b.extensions.button.extend({init:function(){b.extensions.button.prototype.init.apply(this,arguments)},formSaveLabel:"✓",formCloseLabel:"×",activeClass:"medium-editor-toolbar-form-active",hasForm:!0,getForm:function(){},isDisplayed:function(){return!!this.hasForm&&this.getForm().classList.contains(this.activeClass)},showForm:function(){this.hasForm&&this.getForm().classList.add(this.activeClass)},hideForm:function(){this.hasForm&&this.getForm().classList.remove(this.activeClass)},showToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.showToolbarDefaultActions()},hideToolbarDefaultActions:function(){var e=this.base.getExtensionByName("toolbar");e&&e.hideToolbarDefaultActions()},setToolbarPosition:function(){var e=this.base.getExtensionByName("toolbar");e&&e.setToolbarPosition()}}),b.extensions.form=i,o=b.extensions.form.extend({customClassOption:null,customClassOptionText:"Button",linkValidation:!1,placeholderText:"Paste or type a link",targetCheckbox:!1,targetCheckboxText:"Open in new window",name:"anchor",action:"createLink",aria:"link",tagNames:["a"],contentDefault:"#",contentFA:'',init:function(){b.extensions.form.prototype.init.apply(this,arguments),this.subscribe("editableKeydown",this.handleKeydown.bind(this))},handleClick:function(e){e.preventDefault(),e.stopPropagation();var t=b.selection.getSelectionRange(this.document);return"a"===t.startContainer.nodeName.toLowerCase()||"a"===t.endContainer.nodeName.toLowerCase()||b.util.getClosestTag(b.selection.getSelectedParentElement(t),"a")?this.execAction("unlink"):(this.isDisplayed()||this.showForm(),!1)},handleKeydown:function(e){b.util.isKey(e,b.util.keyCode.K)&&b.util.isMetaCtrlKey(e)&&!e.shiftKey&&this.handleClick(e)},getForm:function(){return this.form||(this.form=this.createForm()),this.form},getTemplate:function(){var e=[''];return e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formSaveLabel,""),e.push('',"fontawesome"===this.getEditorOption("buttonLabels")?'':this.formCloseLabel,""),this.targetCheckbox&&e.push('