This is an automated email from the ASF dual-hosted git repository. pinal pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/atlas.git
The following commit(s) were added to refs/heads/master by this push: new c0f318f ATLAS-4471 : UI: Add text editor for long description fields c0f318f is described below commit c0f318f18ff621a4fe88fd49f3d2fad8468d2de4 Author: Farhan Khan <farhan.k...@cloudera.com> AuthorDate: Fri Jan 7 18:20:08 2022 +0530 ATLAS-4471 : UI: Add text editor for long description fields Signed-off-by: Pinal Shah <pinal.s...@freestoneinfotech.com> --- dashboardv2/public/css/scss/common.scss | 3 +- dashboardv2/public/css/scss/override.scss | 26 + dashboardv2/public/css/scss/style.scss | 4 +- dashboardv2/public/css/scss/texteditor.scss | 76 ++ dashboardv2/public/css/scss/trumbowyg.scss | 889 +++++++++++++++++++++ .../public/js/external_lib/dompurify/purify.min.js | 3 + .../public/js/external_lib/trumbowyg/trumbowyg.js | 12 + .../public/js/external_lib/trumbowyg/ui/icons.svg | 1 + dashboardv2/public/js/main.js | 11 +- .../glossary/GlossaryDetailLayoutView_tmpl.html | 14 +- dashboardv2/public/js/utils/CommonViewFunction.js | 45 +- dashboardv2/public/js/utils/Utils.js | 12 +- .../js/views/glossary/GlossaryDetailLayoutView.js | 16 +- dashboardv3/public/css/scss/common.scss | 3 +- dashboardv3/public/css/scss/override.scss | 27 + dashboardv3/public/css/scss/style.scss | 4 +- dashboardv3/public/css/scss/texteditor.scss | 76 ++ dashboardv3/public/css/scss/trumbowyg.scss | 889 +++++++++++++++++++++ .../public/js/external_lib/dompurify/purify.min.js | 3 + .../public/js/external_lib/trumbowyg/trumbowyg.js | 12 + .../public/js/external_lib/trumbowyg/ui/icons.svg | 1 + dashboardv3/public/js/main.js | 11 +- .../glossary/GlossaryDetailLayoutView_tmpl.html | 16 +- dashboardv3/public/js/utils/CommonViewFunction.js | 44 +- dashboardv3/public/js/utils/Utils.js | 12 +- .../js/views/glossary/GlossaryDetailLayoutView.js | 17 +- 26 files changed, 2199 insertions(+), 28 deletions(-) diff --git a/dashboardv2/public/css/scss/common.scss b/dashboardv2/public/css/scss/common.scss index 5bf8b61..5e7ffef 100644 --- a/dashboardv2/public/css/scss/common.scss +++ b/dashboardv2/public/css/scss/common.scss @@ -341,7 +341,8 @@ pre { } .long-description { - width: 85%; + width: 100%; + overflow-wrap: break-word; cursor: default !important; background-color: transparent !important; } \ No newline at end of file diff --git a/dashboardv2/public/css/scss/override.scss b/dashboardv2/public/css/scss/override.scss index 94548ff..f98c7fc 100644 --- a/dashboardv2/public/css/scss/override.scss +++ b/dashboardv2/public/css/scss/override.scss @@ -569,6 +569,32 @@ div.columnmanager-dropdown-container { padding-top: 20px; } +.long-description-container { + margin-right: 0px !important; +} + +.glossary-longdescription-wrapper { + display: inline-block; + height: 100px; + background-color: #fff !important; + overflow: auto; + margin-top: 8px; + width: 100%; + + ul { + list-style: disc !important; + } +} + +.isTextTypeBtn-wrapper { + display: inline-block; + line-height: 22px; + + .switch { + margin-bottom: 0px; + } +} + .select2-container--default .select2-search--inline .select2-search__field { width: 100% !important; } diff --git a/dashboardv2/public/css/scss/style.scss b/dashboardv2/public/css/scss/style.scss index 9f4482c..a56c0af 100644 --- a/dashboardv2/public/css/scss/style.scss +++ b/dashboardv2/public/css/scss/style.scss @@ -36,4 +36,6 @@ @import "wizard.scss"; @import "business-metadata.scss"; @import "stats.scss"; -@import "override.scss"; \ No newline at end of file +@import "override.scss"; +@import "trumbowyg.scss"; +@import "texteditor.scss"; \ No newline at end of file diff --git a/dashboardv2/public/css/scss/texteditor.scss b/dashboardv2/public/css/scss/texteditor.scss new file mode 100644 index 0000000..275bdcc --- /dev/null +++ b/dashboardv2/public/css/scss/texteditor.scss @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* texteditor */ + +.trumbowyg { + border-radius: 4px !important; + min-height: 150px !important; + + .trumbowyg-button-pane { + background-color: #f6f7fb !important; + } + + .trumbowyg-editor { + min-height: 150px !important; + overflow-wrap: break-word; + } + + ul { + list-style: disc !important; + } + + .trumbowyg-textarea { + min-height: 150px !important; + } +} + +.trumbowyg-modal-submit, +.trumbowyg-modal-reset { + border: 1px #37bb9b solid !important; + color: #37bb9b !important; + border-radius: 4px !important; + font-size: 14px !important; + background-color: transparent !important; +} + +.trumbowyg-modal-submit:hover, +.trumbowyg-modal-reset:hover { + background-color: #37bb9b !important; + color: #fff !important; +} + +.trumbowyg-modal-box label .trumbowyg-input-infos span { + color: #686868 !important; +} + +.trumbowyg-viewHTML-button { + width: 101px !important; +} + +.trumbowyg-h1-dropdown-button{ + font-size: 36px !important; +} +.trumbowyg-h2-dropdown-button{ + font-size: 30px !important; +} +.trumbowyg-h3-dropdown-button{ + font-size: 24px !important; +} +.trumbowyg-h4-dropdown-button{ + font-size: 18px !important; +} \ No newline at end of file diff --git a/dashboardv2/public/css/scss/trumbowyg.scss b/dashboardv2/public/css/scss/trumbowyg.scss new file mode 100644 index 0000000..a5463ea --- /dev/null +++ b/dashboardv2/public/css/scss/trumbowyg.scss @@ -0,0 +1,889 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* trumbowyg */ + +/** + * Trumbowyg v2.24.0 - A lightweight WYSIWYG editor + * Default stylesheet for Trumbowyg editor + * ------------------------ + * @link http://alex-d.github.io/Trumbowyg + * @license MIT + * @author Alexandre Demode (Alex-D) + * Twitter : @AlexandreDemode + * Website : alex-d.fr + */ + +$light-color: #ecf0f1 !default; +$dark-color: #222 !default; + +$modal-submit-color: #2ecc71 !default; +$modal-reset-color: #EEE !default; + +$transition-duration: 150ms !default; +$slow-transition-duration: 300ms !default; + +#trumbowyg-icons { + overflow: hidden; + visibility: hidden; + height: 0; + width: 0; + + svg { + height: 0; + width: 0; + } +} + +.trumbowyg-box, +.trumbowyg-modal { + + *, + *::before, + *::after { + box-sizing: border-box; + } + + svg { + width: 17px; + height: 100%; + fill: $dark-color; + } +} + +.trumbowyg-box, +.trumbowyg-editor { + display: block; + position: relative; + border: 1px solid #DDD; + width: 100%; + min-height: 300px; +} + +.trumbowyg-box .trumbowyg-editor { + margin: 0 auto; +} + +.trumbowyg-box.trumbowyg-fullscreen { + background: #FEFEFE; + border: none !important; +} + +.trumbowyg-editor, +.trumbowyg-textarea { + position: relative; + box-sizing: border-box; + padding: 20px; + min-height: 300px; + width: 100%; + border-style: none; + resize: none; + outline: none; + overflow: auto; + user-select: text; // Avoid issues on iOS + + &.trumbowyg-autogrow-on-enter { + transition: height $slow-transition-duration ease-out; + } +} + +.trumbowyg-box-blur .trumbowyg-editor { + + *, + &::before { + color: transparent !important; + text-shadow: 0 0 7px #333; + + @media screen and (min-width: 0 \0) { + color: rgba(200, 200, 200, 0.6) !important; + } + + @supports (-ms-accelerator:true) { + color: rgba(200, 200, 200, 0.6) !important; + } + } + + img, + hr { + opacity: 0.2; + } +} + +.trumbowyg-textarea { + position: relative; + display: block; + overflow: auto; + border: none; + font-size: 14px; + font-family: "Inconsolata", "Consolas", "Courier", "Courier New", sans-serif; + line-height: 18px; +} + +.trumbowyg-box.trumbowyg-editor-visible { + .trumbowyg-textarea { + height: 1px !important; + width: 25%; + min-height: 0 !important; + padding: 0 !important; + background: none; + opacity: 0 !important; + } +} + +.trumbowyg-box.trumbowyg-editor-hidden { + .trumbowyg-textarea { + display: block; + margin-bottom: 1px; + } + + .trumbowyg-editor { + display: none; + } +} + +.trumbowyg-box.trumbowyg-disabled { + .trumbowyg-textarea { + opacity: 0.8; + background: none; + } +} + +.trumbowyg-editor[contenteditable=true]:empty:not(:focus)::before { + content: attr(placeholder); + color: #999; + pointer-events: none; + white-space: break-spaces; +} + +.trumbowyg-button-pane { + width: 100%; + min-height: 36px; + background: $light-color; + border-bottom: 1px solid darken($light-color, 7%); + margin: 0; + padding: 0 5px; + position: relative; + list-style-type: none; + line-height: 10px; + backface-visibility: hidden; + z-index: 11; + + &::after { + content: " "; + display: block; + position: absolute; + top: 36px; + left: 0; + right: 0; + width: 100%; + height: 1px; + background: darken($light-color, 7%); + } + + .trumbowyg-button-group { + display: inline-block; + + .trumbowyg-fullscreen-button svg { + color: transparent; + } + + &::after { + content: " "; + display: inline-block; + width: 1px; + background: darken($light-color, 7%); + margin: 0 5px; + height: 35px; + vertical-align: top; + } + + &:last-child::after { + content: none; + } + } + + button { + display: inline-block; + position: relative; + width: 35px; + height: 35px; + padding: 1px 6px !important; + margin-bottom: 1px; + overflow: hidden; + border: none; + cursor: pointer; + background: none; + vertical-align: middle; + transition: background-color $transition-duration, opacity $transition-duration; + + &.trumbowyg-textual-button { + width: auto; + line-height: 35px; + user-select: none; + } + } + + &.trumbowyg-disable button:not(.trumbowyg-not-disable):not(.trumbowyg-active), + button.trumbowyg-disable, + .trumbowyg-disabled & button:not(.trumbowyg-not-disable):not(.trumbowyg-viewHTML-button) { + opacity: 0.2; + cursor: default; + pointer-events: none; + } + + &.trumbowyg-disable, + .trumbowyg-disabled & { + .trumbowyg-button-group::before { + background: darken($light-color, 3%); + } + } + + button:not(.trumbowyg-disable):hover, + button:not(.trumbowyg-disable):focus, + button.trumbowyg-active { + background-color: #FFF; + outline: none; + } + + .trumbowyg-open-dropdown { + &::after { + display: block; + content: " "; + position: absolute; + top: 25px; + right: 3px; + height: 0; + width: 0; + border: 3px solid transparent; + border-top-color: #555; + } + + &.trumbowyg-textual-button { + padding-left: 10px !important; + padding-right: 18px !important; + + &::after { + top: 17px; + right: 7px; + } + } + } + + .trumbowyg-right { + float: right; + } +} + +.trumbowyg-dropdown { + max-width: 300px; + max-height: 250px; + overflow-y: auto; + overflow-x: hidden; + white-space: nowrap; + border: 1px solid $light-color; + padding: 5px 0; + border-top: none; + background: #FFF; + margin-left: -1px; + box-shadow: rgba(0, 0, 0, .1) 0 2px 3px; + z-index: 12; + + button { + display: block; + width: 100%; + height: 35px; + line-height: 35px; + text-decoration: none; + background: #FFF; + padding: 0 20px 0 10px; + color: #333 !important; + border: none; + cursor: pointer; + text-align: left; + font-size: 15px; + transition: all $transition-duration; + + &:hover, + &:focus { + background: $light-color; + } + + svg { + float: left; + margin-right: 14px; + } + } +} + +/* Modal box */ +.trumbowyg-modal { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + max-width: 520px; + width: 100%; + height: 350px; + z-index: 12; + overflow: hidden; + backface-visibility: hidden; +} + +.trumbowyg-modal-box { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + max-width: 500px; + width: calc(100% - 20px); + padding-bottom: 45px; + z-index: 1; + background-color: #FFF; + text-align: center; + font-size: 14px; + box-shadow: rgba(0, 0, 0, .2) 0 2px 3px; + backface-visibility: hidden; + + .trumbowyg-modal-title { + font-size: 24px; + font-weight: bold; + margin: 0 0 20px; + padding: 15px 0 13px; + display: block; + border-bottom: 1px solid #EEE; + color: #333; + background: lighten($light-color, 5%); + } + + .trumbowyg-progress { + width: 100%; + height: 3px; + position: absolute; + top: 58px; + + .trumbowyg-progress-bar { + background: #2BC06A; + width: 0; + height: 100%; + transition: width $transition-duration linear; + } + } + + label { + display: block; + position: relative; + margin: 15px 12px; + height: 29px; + line-height: 29px; + overflow: hidden; + + .trumbowyg-input-infos { + display: block; + text-align: left; + height: 25px; + line-height: 25px; + transition: all 150ms; + + span { + display: block; + color: darken($light-color, 45%); + background-color: lighten($light-color, 5%); + border: 1px solid #DEDEDE; + padding: 0 7px; + width: 150px; + } + + span.trumbowyg-msg-error { + color: #e74c3c; + } + } + + &.trumbowyg-input-error { + + input, + textarea { + border: 1px solid #e74c3c; + } + + .trumbowyg-input-infos { + margin-top: -27px; + } + } + + input { + position: absolute; + top: 0; + right: 0; + height: 27px; + line-height: 27px; + border: 1px solid #DEDEDE; + background: #fff; + font-size: 14px; + max-width: 330px; + width: 70%; + padding: 0 7px; + transition: all $transition-duration; + + &:hover, + &:focus { + outline: none; + border: 1px solid #95a5a6; + } + + &:focus { + background: lighten($light-color, 5%); + } + } + + input[type="checkbox"] { + left: 6px; + top: 6px; + right: auto; + height: 16px; + width: 16px; + + +.trumbowyg-input-infos span { + width: auto; + padding-left: 25px; + } + } + } + + .error { + margin-top: 25px; + display: block; + color: red; + } + + .trumbowyg-modal-button { + position: absolute; + bottom: 10px; + right: 0; + text-decoration: none; + color: #FFF; + display: block; + width: 100px; + height: 35px; + line-height: 33px; + margin: 0 10px; + background-color: #333; + border: none; + cursor: pointer; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif; + font-size: 16px; + transition: all $transition-duration; + + &.trumbowyg-modal-submit { + right: 110px; + background: darken($modal-submit-color, 3%); + + &:hover, + &:focus { + background: lighten($modal-submit-color, 5%); + outline: none; + } + + &:active { + background: darken($modal-submit-color, 10%); + } + } + + &.trumbowyg-modal-reset { + color: #555; + background: darken($modal-reset-color, 3%); + + &:hover, + &:focus { + background: lighten($modal-reset-color, 5%); + outline: none; + } + + &:active { + background: darken($modal-reset-color, 10%); + } + } + } +} + +.trumbowyg-overlay { + position: absolute; + background-color: rgba(255, 255, 255, 0.5); + height: 100%; + width: 100%; + left: 0; + display: none; + top: 0; + z-index: 10; +} + +/** + * Fullscreen + */ +body.trumbowyg-body-fullscreen { + overflow: hidden; +} + +.trumbowyg-fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + z-index: 99999; + + &.trumbowyg-box, + .trumbowyg-editor { + border: none; + } + + .trumbowyg-editor, + .trumbowyg-textarea { + height: calc(100% - 37px) !important; + overflow: auto; + } + + .trumbowyg-overlay { + height: 100% !important; + } + + .trumbowyg-button-group .trumbowyg-fullscreen-button svg { + color: $dark-color; + fill: transparent; + } +} + +.trumbowyg-editor { + + object, + embed, + video, + img { + max-width: 100%; + } + + video, + img { + height: auto; + } + + img { + cursor: move; + } + + canvas:focus { + outline: none; + } + + /* + * lset for resetCss option + */ + &.trumbowyg-reset-css { + background: #FEFEFE !important; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif !important; + font-size: 14px !important; + line-height: 1.45em !important; + color: #333; + + a { + color: #15c !important; + text-decoration: underline !important; + } + + div, + p, + ul, + ol, + blockquote { + box-shadow: none !important; + background: none !important; + margin: 0 !important; + margin-bottom: 15px !important; + line-height: 1.4em !important; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif !important; + font-size: 14px !important; + border: none; + } + + iframe, + object, + hr { + margin-bottom: 15px !important; + } + + blockquote { + margin-left: 32px !important; + font-style: italic !important; + color: #555; + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + ul, + ol { + padding-left: 20px !important; + } + + ul ul, + ol ol, + ul ol, + ol ul { + border: none; + margin: 2px !important; + padding: 0 !important; + padding-left: 24px !important; + } + + hr { + display: block; + height: 1px; + border: none; + border-top: 1px solid #CCC; + } + + h1, + h2, + h3, + h4 { + color: #111; + background: none; + margin: 0 !important; + padding: 0 !important; + font-weight: bold; + } + + h1 { + font-size: 32px !important; + line-height: 38px !important; + margin-bottom: 20px !important; + } + + h2 { + font-size: 26px !important; + line-height: 34px !important; + margin-bottom: 15px !important; + } + + h3 { + font-size: 22px !important; + line-height: 28px !important; + margin-bottom: 7px !important; + } + + h4 { + font-size: 16px !important; + line-height: 22px !important; + margin-bottom: 7px !important; + } + } +} + +.trumbowyg-dropdown button svg { + display: none; +} + +/* + * Dark theme + */ +.trumbowyg-dark { + .trumbowyg-textarea { + background: #111; + color: #ddd; + } + + .trumbowyg-box { + border: 1px solid lighten($dark-color, 7%); + + &.trumbowyg-fullscreen { + background: #111; + } + + &.trumbowyg-box-blur .trumbowyg-editor { + + *, + &::before { + text-shadow: 0 0 7px #ccc; + + @media screen and (min-width: 0 \0) { + color: rgba(20, 20, 20, 0.6) !important; + } + + @supports (-ms-accelerator:true) { + color: rgba(20, 20, 20, 0.6) !important; + } + } + } + + svg { + fill: $light-color; + color: $light-color; + } + } + + .trumbowyg-button-pane { + background-color: $dark-color; + border-bottom-color: lighten($dark-color, 7%); + + &::after { + background: lighten($dark-color, 7%); + } + + .trumbowyg-button-group:not(:empty) { + &::after { + background-color: lighten($dark-color, 7%); + } + + .trumbowyg-fullscreen-button svg { + color: transparent; + } + } + + &.trumbowyg-disable { + .trumbowyg-button-group::after { + background-color: lighten($dark-color, 3%); + } + } + + button:not(.trumbowyg-disable):hover, + button:not(.trumbowyg-disable):focus, + button.trumbowyg-active { + background-color: #333; + } + + .trumbowyg-open-dropdown::after { + border-top-color: #fff; + } + } + + .trumbowyg-fullscreen { + .trumbowyg-button-pane .trumbowyg-button-group:not(:empty) .trumbowyg-fullscreen-button svg { + color: $light-color; + fill: transparent; + } + } + + .trumbowyg-dropdown { + border-color: $dark-color; + background: #333; + box-shadow: rgba(0, 0, 0, .3) 0 2px 3px; + + button { + background: #333; + color: #fff !important; + + &:hover, + &:focus { + background: $dark-color; + } + } + } + + // Modal box + .trumbowyg-modal-box { + background-color: $dark-color; + + .trumbowyg-modal-title { + border-bottom: 1px solid #555; + color: #fff; + background: lighten($dark-color, 10%); + } + + label { + display: block; + position: relative; + margin: 15px 12px; + height: 27px; + line-height: 27px; + overflow: hidden; + + .trumbowyg-input-infos { + span { + color: #eee; + background-color: lighten($dark-color, 5%); + border-color: $dark-color; + } + + span.trumbowyg-msg-error { + color: #e74c3c; + } + } + + &.trumbowyg-input-error { + + input, + textarea { + border-color: #e74c3c; + } + } + + input { + border-color: $dark-color; + color: #eee; + background: #333; + + &:hover, + &:focus { + border-color: lighten($dark-color, 25%); + } + + &:focus { + background-color: lighten($dark-color, 5%); + } + } + } + + .trumbowyg-modal-button { + &.trumbowyg-modal-submit { + background: darken($modal-submit-color, 20%); + + &:hover, + &:focus { + background: darken($modal-submit-color, 10%); + } + + &:active { + background: darken($modal-submit-color, 25%); + } + } + + &.trumbowyg-modal-reset { + background: #333; + color: #ccc; + + &:hover, + &:focus { + background: #444; + } + + &:active { + background: #111; + } + } + } + } + + .trumbowyg-overlay { + background-color: rgba(15, 15, 15, 0.6); + } +} \ No newline at end of file diff --git a/dashboardv2/public/js/external_lib/dompurify/purify.min.js b/dashboardv2/public/js/external_lib/dompurify/purify.min.js new file mode 100644 index 0000000..602e48e --- /dev/null +++ b/dashboardv2/public/js/external_lib/dompurify/purify.min.js @@ -0,0 +1,3 @@ +/*! @license DOMPurify 2.3.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.4/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,(function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){r [...] +//# sourceMappingURL=purify.min.js.map diff --git a/dashboardv2/public/js/external_lib/trumbowyg/trumbowyg.js b/dashboardv2/public/js/external_lib/trumbowyg/trumbowyg.js new file mode 100644 index 0000000..7ef6d17 --- /dev/null +++ b/dashboardv2/public/js/external_lib/trumbowyg/trumbowyg.js @@ -0,0 +1,12 @@ +/** Trumbowyg v2.24.0 - A lightweight WYSIWYG editor - alex-d.github.io/Trumbowyg - License MIT - Author : Alexandre Demode (Alex-D) / alex-d.fr */ +/** + * Trumbowyg v2.24.0 - A lightweight WYSIWYG editor + * Trumbowyg core file + * ------------------------ + * @link http://alex-d.github.io/Trumbowyg + * @license MIT + * @author Alexandre Demode (Alex-D) + * Twitter : @AlexandreDemode + * Website : alex-d.fr + */ +jQuery.trumbowyg={langs:{en:{viewHTML:"Plain Text",undo:"Undo",redo:"Redo",formatting:"Formatting",p:"Paragraph",blockquote:"Quote",code:"Code",header:"Header",bold:"Bold",italic:"Italic",strikethrough:"Strikethrough",underline:"Underline",strong:"Strong",em:"Emphasis",del:"Deleted",superscript:"Superscript",subscript:"Subscript",unorderedList:"Unordered list",orderedList:"Ordered list",insertImage:"Insert Image",link:"Link",createLink:"Insert link",unlink:"Remove link",justifyLeft:"Alig [...] \ No newline at end of file diff --git a/dashboardv2/public/js/external_lib/trumbowyg/ui/icons.svg b/dashboardv2/public/js/external_lib/trumbowyg/ui/icons.svg new file mode 100644 index 0000000..e7d0dfc --- /dev/null +++ b/dashboardv2/public/js/external_lib/trumbowyg/ui/icons.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol id="trumbowyg-blockquote" viewBox="0 0 72 72"><path d="M21.3 31.9h-.6c.8-1.2 1.9-2.2 3.4-3.2 2.1-1.4 5-2.7 9.2-3.3l-1.4-8.9c-4.7.7-8.5 2.1-11.7 4-2.4 1.4-4.3 3.1-5.8 4.9-2.3 2.7-3.7 5.7-4.5 8.5-.8 2.8-1 5.4-1 7.5 0 2.3.3 4 .4 4.8 0 .1.1.3.1.4 1.2 5.4 6.1 9.5 11.9 9.5 6.7 0 12.2-5.4 12.2-12.2s-5.5-12-12.2-12zM49.5 31.9h-.6c.8-1.2 1.9-2.2 3.4-3.2 2.1-1.4 5-2.7 9.2-3.3l-1.4-8.9c-4.7.7-8.5 2.1-11.7 4-2 [...] \ No newline at end of file diff --git a/dashboardv2/public/js/main.js b/dashboardv2/public/js/main.js index f2ea1d8..603e5ec 100644 --- a/dashboardv2/public/js/main.js +++ b/dashboardv2/public/js/main.js @@ -133,6 +133,13 @@ require.config({ }, 'jquery-steps': { 'deps': ['jquery'] + }, + 'DOMPurify': { + 'exports': 'DOMPurify' + }, + 'trumbowyg': { + 'deps': ['jquery'], + 'exports': 'trumbowyg' } }, @@ -179,7 +186,9 @@ require.config({ 'jquery-steps': 'libs/jquery-steps/jquery.steps.min', 'dropzone': 'libs/dropzone/js/dropzone-amd-module', 'lossless-json': 'libs/lossless-json/lossless-json', - 'store': 'external_lib/idealTimeout/store.min' + 'store': 'external_lib/idealTimeout/store.min', + 'DOMPurify': 'external_lib/dompurify/purify.min', + 'trumbowyg': 'external_lib/trumbowyg/trumbowyg' }, /** diff --git a/dashboardv2/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html b/dashboardv2/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html index b81f749..c8b55f7 100644 --- a/dashboardv2/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html +++ b/dashboardv2/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html @@ -27,10 +27,18 @@ <p data-id="shortDescription"></p> </div> </div> - <div class="form-group clearfix"> + <div class="long-description-container form-group clearfix"> <span class="pull-left text-muted">Long Description: </span> - <div> - <textarea class="form-control long-description" name="longDescription" data-id="longDescription" disabled></textarea> + <div class="isTextTypeBtn-wrapper pull-right"> + <span class="text-muted pull-left">Formatted</span> + <label class="switch pull-left"> + <input type="checkbox" class="switch-input" name="textType" value="text"> + <span class="switch-slider"></span> + </label> + <span class="text-muted">Plain</span> + </div> + <div class="glossary-longdescription-wrapper form-control col-sm-12"> + <div class="long-description" name="longDescription" data-id="longDescription" disabled></div> </div> </div> {{#if isTermView}} diff --git a/dashboardv2/public/js/utils/CommonViewFunction.js b/dashboardv2/public/js/utils/CommonViewFunction.js index f7400c2..eb3a515 100644 --- a/dashboardv2/public/js/utils/CommonViewFunction.js +++ b/dashboardv2/public/js/utils/CommonViewFunction.js @@ -16,7 +16,7 @@ * limitations under the License. */ -define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enums', 'moment', 'utils/Globals', 'moment-timezone'], function(require, Utils, Modal, Messages, Enums, moment, Globals) { +define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enums', 'moment', 'utils/Globals', 'moment-timezone'], function(require, Utils, Modal, Messages, Enums, moment, Globals, momentTimezone) { 'use strict'; var CommonViewFunction = {}; @@ -691,12 +691,23 @@ define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enum var model = options.model, isTermView = options.isTermView, isGlossaryView = options.isGlossaryView, - collection = options.collection + collection = options.collection; + //Below condition is added for sanitizing the longDescription text against XSS attack. + if (model) { + var longDescriptionContent = isGlossaryView ? model.get('longDescription') : model.longDescription, + sanitizeLongDescriptionContent; + if (longDescriptionContent) { + sanitizeLongDescriptionContent = Utils.sanitizeHtmlContent(longDescriptionContent); + isGlossaryView ? model.set("longDescription", sanitizeLongDescriptionContent) : model.longDescription = sanitizeLongDescriptionContent; + } + } + //End } require([ 'views/glossary/CreateEditCategoryTermLayoutView', 'views/glossary/CreateEditGlossaryLayoutView', - 'modules/Modal' + 'modules/Modal', + 'trumbowyg' ], function(CreateEditCategoryTermLayoutView, CreateEditGlossaryLayoutView, Modal) { var view = null, title = null; @@ -714,14 +725,40 @@ define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enum "cancelText": "Cancel", "okCloses": false, "okText": model ? "Update" : "Create", - "allowCancel": true + "allowCancel": true, + "width": "765px" }).open(); modal.$el.find('input[data-id=shortDescription]').on('input keydown', function(e) { $(this).val($(this).val().replace(/\s+/g, ' ')); }); modal.$el.find('button.ok').attr("disabled", "true"); + var longDescriptionEditor = modal.$el.find('textarea[data-id=longDescription]'), + okBtn = modal.$el.find('button.ok'); + longDescriptionEditor.trumbowyg({ + btns: [ + ['formatting'], + ['strong', 'em', 'underline', 'del'], + ['link'], + ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], + ['unorderedList', 'orderedList'], + ['viewHTML'] + ], + removeformatPasted: true, + urlProtocol: true, + defaultLinkTarget: '_blank' + }).on('tbwchange', function() { + okBtn.removeAttr("disabled"); + }); modal.on('ok', function() { modal.$el.find('button.ok').showButtonLoader(); + //Below condition is added for sanitizing the longDescription text against XSS attack. + var editorContent, cleanContent; + editorContent = longDescriptionEditor.trumbowyg('html'); + if (editorContent !== "") { + cleanContent = Utils.sanitizeHtmlContent(editorContent); + longDescriptionEditor.trumbowyg('html', cleanContent); + } + //End CommonViewFunction.createEditGlossaryCategoryTermSubmit(_.extend({ "ref": view, "modal": modal }, options)); }); modal.on('closeModal', function() { diff --git a/dashboardv2/public/js/utils/Utils.js b/dashboardv2/public/js/utils/Utils.js index f2498d8..f3126db 100644 --- a/dashboardv2/public/js/utils/Utils.js +++ b/dashboardv2/public/js/utils/Utils.js @@ -16,7 +16,7 @@ * limitations under the License. */ -define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', 'moment', 'store', 'modules/Modal', 'moment-timezone', 'pnotify.buttons', 'pnotify.confirm'], function(require, Globals, pnotify, Messages, Enums, moment, store, Modal) { +define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', 'moment', 'store', 'modules/Modal', 'DOMPurify', 'moment-timezone', 'pnotify.buttons', 'pnotify.confirm'], function(require, Globals, pnotify, Messages, Enums, moment, store, Modal, DOMPurify) { 'use strict'; var Utils = {}; @@ -952,6 +952,14 @@ define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', } return dateValue; } + //-----------------------------------------DOMPurify-------------------------------------- + //This below function expects string that needs to be sanitize against XSS attack. + Utils.sanitizeHtmlContent = function(string) { + if (string) { + return DOMPurify.sanitize(string, { FORBID_TAGS: ['img', 'script', 'iframe', 'embed', 'svg', 'meta'], ALLOWED_ATTR: ['target', 'href'] }); + } + } + //---------------------------------------------------------------------------------------- //------------------------------------------------idleTimeout----------------------------- $.fn.idleTimeout = function(userRuntimeConfig) { @@ -1222,4 +1230,4 @@ define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', //------------------------------------------------ return Utils; -}); \ No newline at end of file +}); diff --git a/dashboardv2/public/js/views/glossary/GlossaryDetailLayoutView.js b/dashboardv2/public/js/views/glossary/GlossaryDetailLayoutView.js index 0a13162..b070c55 100644 --- a/dashboardv2/public/js/views/glossary/GlossaryDetailLayoutView.js +++ b/dashboardv2/public/js/views/glossary/GlossaryDetailLayoutView.js @@ -70,6 +70,7 @@ define(['require', removeTag: '[data-id="removeTagTerm"]', tagClick: '[data-id="tagClickTerm"]', addTag: '[data-id="addTagTerm"]', + textType: '[name="textType"]' }, /** ui events hash */ events: function() { @@ -168,6 +169,10 @@ define(['require', events["click " + this.ui.addTerm] = 'onClickAddTermBtn'; events["click " + this.ui.addCategory] = 'onClickAddTermBtn'; events["click " + this.ui.addTag] = 'onClickAddTagBtn'; + events["change " + this.ui.textType] = function(e) { + this.isTextTypeChecked = !this.isTextTypeChecked; + this.renderDetails(this.data); + }; return events; }, /** @@ -186,6 +191,7 @@ define(['require', } } this.selectedTermAttribute = null; + this.isTextTypeChecked = false; }, onRender: function() { this.$('.fontLoader-relative').show(); @@ -297,10 +303,18 @@ define(['require', }, renderDetails: function(data) { Utils.hideTitleLoader(this.$('.fontLoader'), this.ui.details); + //Below condition is added for sanitizing the longDescription text against XSS attack. + var longDescriptionContent = (data && data.longDescription) ? data.longDescription : "", + sanitizeLongDescriptionContent = ""; + if (longDescriptionContent !== "") { + sanitizeLongDescriptionContent = Utils.sanitizeHtmlContent(longDescriptionContent); + } + //End if (data) { + var longDescriptionValue = longDescriptionContent ? sanitizeLongDescriptionContent : ""; this.ui.title.text(data.name || data.displayText || data.qualifiedName); this.ui.shortDescription.text(data.shortDescription ? data.shortDescription : ""); - this.ui.longDescription.text(data.longDescription ? data.longDescription : ""); + this.isTextTypeChecked ? this.ui.longDescription.text(longDescriptionValue) : this.ui.longDescription.html(longDescriptionValue); this.generateCategories(data.categories); this.generateTerm(data.terms); this.generateTag(data.classifications); diff --git a/dashboardv3/public/css/scss/common.scss b/dashboardv3/public/css/scss/common.scss index 9310e20..c13c47c 100644 --- a/dashboardv3/public/css/scss/common.scss +++ b/dashboardv3/public/css/scss/common.scss @@ -340,7 +340,8 @@ pre { } .long-description { - width: 85%; + width: 100%; + overflow-wrap: break-word; cursor: default !important; background-color: transparent !important; } \ No newline at end of file diff --git a/dashboardv3/public/css/scss/override.scss b/dashboardv3/public/css/scss/override.scss index fb3604d..fec5f6f 100644 --- a/dashboardv3/public/css/scss/override.scss +++ b/dashboardv3/public/css/scss/override.scss @@ -562,6 +562,33 @@ div.columnmanager-dropdown-container { padding-top: 20px; } +.long-description-container, +.short-description-container { + margin-right: 0px !important; +} + +.glossary-longdescription-wrapper { + display: inline-block; + height: 100px; + background-color: #fff !important; + overflow: auto; + margin-top: 8px; + width: 100%; + + ul { + list-style: disc !important; + } +} + +.isTextTypeBtn-wrapper { + display: inline-block; + line-height: 22px; + + .switch { + margin-bottom: 0px; + } +} + .select2-container--default .select2-search--inline .select2-search__field { width: 100% !important; } diff --git a/dashboardv3/public/css/scss/style.scss b/dashboardv3/public/css/scss/style.scss index 37d5458..53e8385 100644 --- a/dashboardv3/public/css/scss/style.scss +++ b/dashboardv3/public/css/scss/style.scss @@ -37,4 +37,6 @@ @import "business-metadata.scss"; @import "stats.scss"; @import "override.scss"; -@import "leftsidebar.scss"; \ No newline at end of file +@import "leftsidebar.scss"; +@import "trumbowyg.scss"; +@import "texteditor.scss"; \ No newline at end of file diff --git a/dashboardv3/public/css/scss/texteditor.scss b/dashboardv3/public/css/scss/texteditor.scss new file mode 100644 index 0000000..275bdcc --- /dev/null +++ b/dashboardv3/public/css/scss/texteditor.scss @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* texteditor */ + +.trumbowyg { + border-radius: 4px !important; + min-height: 150px !important; + + .trumbowyg-button-pane { + background-color: #f6f7fb !important; + } + + .trumbowyg-editor { + min-height: 150px !important; + overflow-wrap: break-word; + } + + ul { + list-style: disc !important; + } + + .trumbowyg-textarea { + min-height: 150px !important; + } +} + +.trumbowyg-modal-submit, +.trumbowyg-modal-reset { + border: 1px #37bb9b solid !important; + color: #37bb9b !important; + border-radius: 4px !important; + font-size: 14px !important; + background-color: transparent !important; +} + +.trumbowyg-modal-submit:hover, +.trumbowyg-modal-reset:hover { + background-color: #37bb9b !important; + color: #fff !important; +} + +.trumbowyg-modal-box label .trumbowyg-input-infos span { + color: #686868 !important; +} + +.trumbowyg-viewHTML-button { + width: 101px !important; +} + +.trumbowyg-h1-dropdown-button{ + font-size: 36px !important; +} +.trumbowyg-h2-dropdown-button{ + font-size: 30px !important; +} +.trumbowyg-h3-dropdown-button{ + font-size: 24px !important; +} +.trumbowyg-h4-dropdown-button{ + font-size: 18px !important; +} \ No newline at end of file diff --git a/dashboardv3/public/css/scss/trumbowyg.scss b/dashboardv3/public/css/scss/trumbowyg.scss new file mode 100644 index 0000000..a5463ea --- /dev/null +++ b/dashboardv3/public/css/scss/trumbowyg.scss @@ -0,0 +1,889 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +/* trumbowyg */ + +/** + * Trumbowyg v2.24.0 - A lightweight WYSIWYG editor + * Default stylesheet for Trumbowyg editor + * ------------------------ + * @link http://alex-d.github.io/Trumbowyg + * @license MIT + * @author Alexandre Demode (Alex-D) + * Twitter : @AlexandreDemode + * Website : alex-d.fr + */ + +$light-color: #ecf0f1 !default; +$dark-color: #222 !default; + +$modal-submit-color: #2ecc71 !default; +$modal-reset-color: #EEE !default; + +$transition-duration: 150ms !default; +$slow-transition-duration: 300ms !default; + +#trumbowyg-icons { + overflow: hidden; + visibility: hidden; + height: 0; + width: 0; + + svg { + height: 0; + width: 0; + } +} + +.trumbowyg-box, +.trumbowyg-modal { + + *, + *::before, + *::after { + box-sizing: border-box; + } + + svg { + width: 17px; + height: 100%; + fill: $dark-color; + } +} + +.trumbowyg-box, +.trumbowyg-editor { + display: block; + position: relative; + border: 1px solid #DDD; + width: 100%; + min-height: 300px; +} + +.trumbowyg-box .trumbowyg-editor { + margin: 0 auto; +} + +.trumbowyg-box.trumbowyg-fullscreen { + background: #FEFEFE; + border: none !important; +} + +.trumbowyg-editor, +.trumbowyg-textarea { + position: relative; + box-sizing: border-box; + padding: 20px; + min-height: 300px; + width: 100%; + border-style: none; + resize: none; + outline: none; + overflow: auto; + user-select: text; // Avoid issues on iOS + + &.trumbowyg-autogrow-on-enter { + transition: height $slow-transition-duration ease-out; + } +} + +.trumbowyg-box-blur .trumbowyg-editor { + + *, + &::before { + color: transparent !important; + text-shadow: 0 0 7px #333; + + @media screen and (min-width: 0 \0) { + color: rgba(200, 200, 200, 0.6) !important; + } + + @supports (-ms-accelerator:true) { + color: rgba(200, 200, 200, 0.6) !important; + } + } + + img, + hr { + opacity: 0.2; + } +} + +.trumbowyg-textarea { + position: relative; + display: block; + overflow: auto; + border: none; + font-size: 14px; + font-family: "Inconsolata", "Consolas", "Courier", "Courier New", sans-serif; + line-height: 18px; +} + +.trumbowyg-box.trumbowyg-editor-visible { + .trumbowyg-textarea { + height: 1px !important; + width: 25%; + min-height: 0 !important; + padding: 0 !important; + background: none; + opacity: 0 !important; + } +} + +.trumbowyg-box.trumbowyg-editor-hidden { + .trumbowyg-textarea { + display: block; + margin-bottom: 1px; + } + + .trumbowyg-editor { + display: none; + } +} + +.trumbowyg-box.trumbowyg-disabled { + .trumbowyg-textarea { + opacity: 0.8; + background: none; + } +} + +.trumbowyg-editor[contenteditable=true]:empty:not(:focus)::before { + content: attr(placeholder); + color: #999; + pointer-events: none; + white-space: break-spaces; +} + +.trumbowyg-button-pane { + width: 100%; + min-height: 36px; + background: $light-color; + border-bottom: 1px solid darken($light-color, 7%); + margin: 0; + padding: 0 5px; + position: relative; + list-style-type: none; + line-height: 10px; + backface-visibility: hidden; + z-index: 11; + + &::after { + content: " "; + display: block; + position: absolute; + top: 36px; + left: 0; + right: 0; + width: 100%; + height: 1px; + background: darken($light-color, 7%); + } + + .trumbowyg-button-group { + display: inline-block; + + .trumbowyg-fullscreen-button svg { + color: transparent; + } + + &::after { + content: " "; + display: inline-block; + width: 1px; + background: darken($light-color, 7%); + margin: 0 5px; + height: 35px; + vertical-align: top; + } + + &:last-child::after { + content: none; + } + } + + button { + display: inline-block; + position: relative; + width: 35px; + height: 35px; + padding: 1px 6px !important; + margin-bottom: 1px; + overflow: hidden; + border: none; + cursor: pointer; + background: none; + vertical-align: middle; + transition: background-color $transition-duration, opacity $transition-duration; + + &.trumbowyg-textual-button { + width: auto; + line-height: 35px; + user-select: none; + } + } + + &.trumbowyg-disable button:not(.trumbowyg-not-disable):not(.trumbowyg-active), + button.trumbowyg-disable, + .trumbowyg-disabled & button:not(.trumbowyg-not-disable):not(.trumbowyg-viewHTML-button) { + opacity: 0.2; + cursor: default; + pointer-events: none; + } + + &.trumbowyg-disable, + .trumbowyg-disabled & { + .trumbowyg-button-group::before { + background: darken($light-color, 3%); + } + } + + button:not(.trumbowyg-disable):hover, + button:not(.trumbowyg-disable):focus, + button.trumbowyg-active { + background-color: #FFF; + outline: none; + } + + .trumbowyg-open-dropdown { + &::after { + display: block; + content: " "; + position: absolute; + top: 25px; + right: 3px; + height: 0; + width: 0; + border: 3px solid transparent; + border-top-color: #555; + } + + &.trumbowyg-textual-button { + padding-left: 10px !important; + padding-right: 18px !important; + + &::after { + top: 17px; + right: 7px; + } + } + } + + .trumbowyg-right { + float: right; + } +} + +.trumbowyg-dropdown { + max-width: 300px; + max-height: 250px; + overflow-y: auto; + overflow-x: hidden; + white-space: nowrap; + border: 1px solid $light-color; + padding: 5px 0; + border-top: none; + background: #FFF; + margin-left: -1px; + box-shadow: rgba(0, 0, 0, .1) 0 2px 3px; + z-index: 12; + + button { + display: block; + width: 100%; + height: 35px; + line-height: 35px; + text-decoration: none; + background: #FFF; + padding: 0 20px 0 10px; + color: #333 !important; + border: none; + cursor: pointer; + text-align: left; + font-size: 15px; + transition: all $transition-duration; + + &:hover, + &:focus { + background: $light-color; + } + + svg { + float: left; + margin-right: 14px; + } + } +} + +/* Modal box */ +.trumbowyg-modal { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + max-width: 520px; + width: 100%; + height: 350px; + z-index: 12; + overflow: hidden; + backface-visibility: hidden; +} + +.trumbowyg-modal-box { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + max-width: 500px; + width: calc(100% - 20px); + padding-bottom: 45px; + z-index: 1; + background-color: #FFF; + text-align: center; + font-size: 14px; + box-shadow: rgba(0, 0, 0, .2) 0 2px 3px; + backface-visibility: hidden; + + .trumbowyg-modal-title { + font-size: 24px; + font-weight: bold; + margin: 0 0 20px; + padding: 15px 0 13px; + display: block; + border-bottom: 1px solid #EEE; + color: #333; + background: lighten($light-color, 5%); + } + + .trumbowyg-progress { + width: 100%; + height: 3px; + position: absolute; + top: 58px; + + .trumbowyg-progress-bar { + background: #2BC06A; + width: 0; + height: 100%; + transition: width $transition-duration linear; + } + } + + label { + display: block; + position: relative; + margin: 15px 12px; + height: 29px; + line-height: 29px; + overflow: hidden; + + .trumbowyg-input-infos { + display: block; + text-align: left; + height: 25px; + line-height: 25px; + transition: all 150ms; + + span { + display: block; + color: darken($light-color, 45%); + background-color: lighten($light-color, 5%); + border: 1px solid #DEDEDE; + padding: 0 7px; + width: 150px; + } + + span.trumbowyg-msg-error { + color: #e74c3c; + } + } + + &.trumbowyg-input-error { + + input, + textarea { + border: 1px solid #e74c3c; + } + + .trumbowyg-input-infos { + margin-top: -27px; + } + } + + input { + position: absolute; + top: 0; + right: 0; + height: 27px; + line-height: 27px; + border: 1px solid #DEDEDE; + background: #fff; + font-size: 14px; + max-width: 330px; + width: 70%; + padding: 0 7px; + transition: all $transition-duration; + + &:hover, + &:focus { + outline: none; + border: 1px solid #95a5a6; + } + + &:focus { + background: lighten($light-color, 5%); + } + } + + input[type="checkbox"] { + left: 6px; + top: 6px; + right: auto; + height: 16px; + width: 16px; + + +.trumbowyg-input-infos span { + width: auto; + padding-left: 25px; + } + } + } + + .error { + margin-top: 25px; + display: block; + color: red; + } + + .trumbowyg-modal-button { + position: absolute; + bottom: 10px; + right: 0; + text-decoration: none; + color: #FFF; + display: block; + width: 100px; + height: 35px; + line-height: 33px; + margin: 0 10px; + background-color: #333; + border: none; + cursor: pointer; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif; + font-size: 16px; + transition: all $transition-duration; + + &.trumbowyg-modal-submit { + right: 110px; + background: darken($modal-submit-color, 3%); + + &:hover, + &:focus { + background: lighten($modal-submit-color, 5%); + outline: none; + } + + &:active { + background: darken($modal-submit-color, 10%); + } + } + + &.trumbowyg-modal-reset { + color: #555; + background: darken($modal-reset-color, 3%); + + &:hover, + &:focus { + background: lighten($modal-reset-color, 5%); + outline: none; + } + + &:active { + background: darken($modal-reset-color, 10%); + } + } + } +} + +.trumbowyg-overlay { + position: absolute; + background-color: rgba(255, 255, 255, 0.5); + height: 100%; + width: 100%; + left: 0; + display: none; + top: 0; + z-index: 10; +} + +/** + * Fullscreen + */ +body.trumbowyg-body-fullscreen { + overflow: hidden; +} + +.trumbowyg-fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + z-index: 99999; + + &.trumbowyg-box, + .trumbowyg-editor { + border: none; + } + + .trumbowyg-editor, + .trumbowyg-textarea { + height: calc(100% - 37px) !important; + overflow: auto; + } + + .trumbowyg-overlay { + height: 100% !important; + } + + .trumbowyg-button-group .trumbowyg-fullscreen-button svg { + color: $dark-color; + fill: transparent; + } +} + +.trumbowyg-editor { + + object, + embed, + video, + img { + max-width: 100%; + } + + video, + img { + height: auto; + } + + img { + cursor: move; + } + + canvas:focus { + outline: none; + } + + /* + * lset for resetCss option + */ + &.trumbowyg-reset-css { + background: #FEFEFE !important; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif !important; + font-size: 14px !important; + line-height: 1.45em !important; + color: #333; + + a { + color: #15c !important; + text-decoration: underline !important; + } + + div, + p, + ul, + ol, + blockquote { + box-shadow: none !important; + background: none !important; + margin: 0 !important; + margin-bottom: 15px !important; + line-height: 1.4em !important; + font-family: "Trebuchet MS", Helvetica, Verdana, sans-serif !important; + font-size: 14px !important; + border: none; + } + + iframe, + object, + hr { + margin-bottom: 15px !important; + } + + blockquote { + margin-left: 32px !important; + font-style: italic !important; + color: #555; + } + + ul { + list-style: disc; + } + + ol { + list-style: decimal; + } + + ul, + ol { + padding-left: 20px !important; + } + + ul ul, + ol ol, + ul ol, + ol ul { + border: none; + margin: 2px !important; + padding: 0 !important; + padding-left: 24px !important; + } + + hr { + display: block; + height: 1px; + border: none; + border-top: 1px solid #CCC; + } + + h1, + h2, + h3, + h4 { + color: #111; + background: none; + margin: 0 !important; + padding: 0 !important; + font-weight: bold; + } + + h1 { + font-size: 32px !important; + line-height: 38px !important; + margin-bottom: 20px !important; + } + + h2 { + font-size: 26px !important; + line-height: 34px !important; + margin-bottom: 15px !important; + } + + h3 { + font-size: 22px !important; + line-height: 28px !important; + margin-bottom: 7px !important; + } + + h4 { + font-size: 16px !important; + line-height: 22px !important; + margin-bottom: 7px !important; + } + } +} + +.trumbowyg-dropdown button svg { + display: none; +} + +/* + * Dark theme + */ +.trumbowyg-dark { + .trumbowyg-textarea { + background: #111; + color: #ddd; + } + + .trumbowyg-box { + border: 1px solid lighten($dark-color, 7%); + + &.trumbowyg-fullscreen { + background: #111; + } + + &.trumbowyg-box-blur .trumbowyg-editor { + + *, + &::before { + text-shadow: 0 0 7px #ccc; + + @media screen and (min-width: 0 \0) { + color: rgba(20, 20, 20, 0.6) !important; + } + + @supports (-ms-accelerator:true) { + color: rgba(20, 20, 20, 0.6) !important; + } + } + } + + svg { + fill: $light-color; + color: $light-color; + } + } + + .trumbowyg-button-pane { + background-color: $dark-color; + border-bottom-color: lighten($dark-color, 7%); + + &::after { + background: lighten($dark-color, 7%); + } + + .trumbowyg-button-group:not(:empty) { + &::after { + background-color: lighten($dark-color, 7%); + } + + .trumbowyg-fullscreen-button svg { + color: transparent; + } + } + + &.trumbowyg-disable { + .trumbowyg-button-group::after { + background-color: lighten($dark-color, 3%); + } + } + + button:not(.trumbowyg-disable):hover, + button:not(.trumbowyg-disable):focus, + button.trumbowyg-active { + background-color: #333; + } + + .trumbowyg-open-dropdown::after { + border-top-color: #fff; + } + } + + .trumbowyg-fullscreen { + .trumbowyg-button-pane .trumbowyg-button-group:not(:empty) .trumbowyg-fullscreen-button svg { + color: $light-color; + fill: transparent; + } + } + + .trumbowyg-dropdown { + border-color: $dark-color; + background: #333; + box-shadow: rgba(0, 0, 0, .3) 0 2px 3px; + + button { + background: #333; + color: #fff !important; + + &:hover, + &:focus { + background: $dark-color; + } + } + } + + // Modal box + .trumbowyg-modal-box { + background-color: $dark-color; + + .trumbowyg-modal-title { + border-bottom: 1px solid #555; + color: #fff; + background: lighten($dark-color, 10%); + } + + label { + display: block; + position: relative; + margin: 15px 12px; + height: 27px; + line-height: 27px; + overflow: hidden; + + .trumbowyg-input-infos { + span { + color: #eee; + background-color: lighten($dark-color, 5%); + border-color: $dark-color; + } + + span.trumbowyg-msg-error { + color: #e74c3c; + } + } + + &.trumbowyg-input-error { + + input, + textarea { + border-color: #e74c3c; + } + } + + input { + border-color: $dark-color; + color: #eee; + background: #333; + + &:hover, + &:focus { + border-color: lighten($dark-color, 25%); + } + + &:focus { + background-color: lighten($dark-color, 5%); + } + } + } + + .trumbowyg-modal-button { + &.trumbowyg-modal-submit { + background: darken($modal-submit-color, 20%); + + &:hover, + &:focus { + background: darken($modal-submit-color, 10%); + } + + &:active { + background: darken($modal-submit-color, 25%); + } + } + + &.trumbowyg-modal-reset { + background: #333; + color: #ccc; + + &:hover, + &:focus { + background: #444; + } + + &:active { + background: #111; + } + } + } + } + + .trumbowyg-overlay { + background-color: rgba(15, 15, 15, 0.6); + } +} \ No newline at end of file diff --git a/dashboardv3/public/js/external_lib/dompurify/purify.min.js b/dashboardv3/public/js/external_lib/dompurify/purify.min.js new file mode 100644 index 0000000..602e48e --- /dev/null +++ b/dashboardv3/public/js/external_lib/dompurify/purify.min.js @@ -0,0 +1,3 @@ +/*! @license DOMPurify 2.3.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.4/LICENSE */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).DOMPurify=t()}(this,(function(){"use strict";var e=Object.hasOwnProperty,t=Object.setPrototypeOf,n=Object.isFrozen,r=Object.getPrototypeOf,o=Object.getOwnPropertyDescriptor,i=Object.freeze,a=Object.seal,l=Object.create,c="undefined"!=typeof Reflect&&Reflect,s=c.apply,u=c.construct;s||(s=function(e,t,n){return e.apply(t,n)}),i||(i=function(e){r [...] +//# sourceMappingURL=purify.min.js.map diff --git a/dashboardv3/public/js/external_lib/trumbowyg/trumbowyg.js b/dashboardv3/public/js/external_lib/trumbowyg/trumbowyg.js new file mode 100644 index 0000000..7ef6d17 --- /dev/null +++ b/dashboardv3/public/js/external_lib/trumbowyg/trumbowyg.js @@ -0,0 +1,12 @@ +/** Trumbowyg v2.24.0 - A lightweight WYSIWYG editor - alex-d.github.io/Trumbowyg - License MIT - Author : Alexandre Demode (Alex-D) / alex-d.fr */ +/** + * Trumbowyg v2.24.0 - A lightweight WYSIWYG editor + * Trumbowyg core file + * ------------------------ + * @link http://alex-d.github.io/Trumbowyg + * @license MIT + * @author Alexandre Demode (Alex-D) + * Twitter : @AlexandreDemode + * Website : alex-d.fr + */ +jQuery.trumbowyg={langs:{en:{viewHTML:"Plain Text",undo:"Undo",redo:"Redo",formatting:"Formatting",p:"Paragraph",blockquote:"Quote",code:"Code",header:"Header",bold:"Bold",italic:"Italic",strikethrough:"Strikethrough",underline:"Underline",strong:"Strong",em:"Emphasis",del:"Deleted",superscript:"Superscript",subscript:"Subscript",unorderedList:"Unordered list",orderedList:"Ordered list",insertImage:"Insert Image",link:"Link",createLink:"Insert link",unlink:"Remove link",justifyLeft:"Alig [...] \ No newline at end of file diff --git a/dashboardv3/public/js/external_lib/trumbowyg/ui/icons.svg b/dashboardv3/public/js/external_lib/trumbowyg/ui/icons.svg new file mode 100644 index 0000000..e7d0dfc --- /dev/null +++ b/dashboardv3/public/js/external_lib/trumbowyg/ui/icons.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol id="trumbowyg-blockquote" viewBox="0 0 72 72"><path d="M21.3 31.9h-.6c.8-1.2 1.9-2.2 3.4-3.2 2.1-1.4 5-2.7 9.2-3.3l-1.4-8.9c-4.7.7-8.5 2.1-11.7 4-2.4 1.4-4.3 3.1-5.8 4.9-2.3 2.7-3.7 5.7-4.5 8.5-.8 2.8-1 5.4-1 7.5 0 2.3.3 4 .4 4.8 0 .1.1.3.1.4 1.2 5.4 6.1 9.5 11.9 9.5 6.7 0 12.2-5.4 12.2-12.2s-5.5-12-12.2-12zM49.5 31.9h-.6c.8-1.2 1.9-2.2 3.4-3.2 2.1-1.4 5-2.7 9.2-3.3l-1.4-8.9c-4.7.7-8.5 2.1-11.7 4-2 [...] \ No newline at end of file diff --git a/dashboardv3/public/js/main.js b/dashboardv3/public/js/main.js index 97e177c..008fbb2 100644 --- a/dashboardv3/public/js/main.js +++ b/dashboardv3/public/js/main.js @@ -162,6 +162,13 @@ require.config({ }, 'jquery-steps': { 'deps': ['jquery'] + }, + 'DOMPurify': { + 'exports': 'DOMPurify' + }, + 'trumbowyg': { + 'deps': ['jquery'], + 'exports': 'trumbowyg' } }, @@ -208,7 +215,9 @@ require.config({ 'jquery-steps': 'libs/jquery-steps/jquery.steps.min', 'dropzone': 'libs/dropzone/js/dropzone-amd-module', 'lossless-json': 'libs/lossless-json/lossless-json', - 'store': 'external_lib/idealTimeout/store.min' + 'store': 'external_lib/idealTimeout/store.min', + 'DOMPurify': 'external_lib/dompurify/purify.min', + 'trumbowyg': 'external_lib/trumbowyg/trumbowyg' }, /** diff --git a/dashboardv3/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html b/dashboardv3/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html index 23a5d84..64e6031 100644 --- a/dashboardv3/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html +++ b/dashboardv3/public/js/templates/glossary/GlossaryDetailLayoutView_tmpl.html @@ -24,16 +24,24 @@ <div data-id="details" class="clearfix form-horizontal col-sm-12"> <h1 class="title"><span data-id="title"></span></h1> <button type="button" data-id="editButton" class="btn btn-sm btn-action pull-right"><i class="fa fa-pencil"></i></button> - <div class="form-group col-sm-12"> + <div class="form-group short-description-container"> <span class="pull-left text-muted">Short Description: </span> <div class="pull-left"> <p data-id="shortDescription"></p> </div> </div> - <div class="form-group col-sm-12"> + <div class="form-group long-description-container"> <span class="pull-left text-muted">Long Description: </span> - <div> - <textarea class="form-control long-description" name="longDescription" data-id="longDescription" disabled></textarea> + <div class="isTextTypeBtn-wrapper pull-right"> + <span class="text-muted pull-left">Formatted</span> + <label class="switch pull-left"> + <input type="checkbox" class="switch-input" name="textType" value="text"> + <span class="switch-slider"></span> + </label> + <span class="text-muted">Plain</span> + </div> + <div class="glossary-longdescription-wrapper form-control"> + <div class="long-description" name="longDescription" data-id="longDescription" disabled></div> </div> </div> {{#if isTermView}} diff --git a/dashboardv3/public/js/utils/CommonViewFunction.js b/dashboardv3/public/js/utils/CommonViewFunction.js index 07954c0..418bc7f 100644 --- a/dashboardv3/public/js/utils/CommonViewFunction.js +++ b/dashboardv3/public/js/utils/CommonViewFunction.js @@ -16,7 +16,7 @@ * limitations under the License. */ -define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enums', 'moment', 'utils/Globals', 'moment-timezone'], function(require, Utils, Modal, Messages, Enums, moment, Globals) { +define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enums', 'moment', 'utils/Globals', 'moment-timezone'], function(require, Utils, Modal, Messages, Enums, moment, Globals, momentTimezone) { 'use strict'; var CommonViewFunction = {}; @@ -711,12 +711,22 @@ define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enum var model = options.model, isTermView = options.isTermView, isGlossaryView = options.isGlossaryView, - collection = options.collection + collection = options.collection; + //Below condition is added for sanitizing the longDescription text against XSS attack. + if (model) { + var longDescriptionContent = isGlossaryView ? model.get('longDescription') : model.longDescription, + sanitizeLongDescriptionContent; + if (longDescriptionContent) { + sanitizeLongDescriptionContent = Utils.sanitizeHtmlContent(longDescriptionContent) + isGlossaryView ? model.set("longDescription", sanitizeLongDescriptionContent) : model.longDescription = sanitizeLongDescriptionContent; + } + } } require([ 'views/glossary/CreateEditCategoryTermLayoutView', 'views/glossary/CreateEditGlossaryLayoutView', - 'modules/Modal' + 'modules/Modal', + 'trumbowyg' ], function(CreateEditCategoryTermLayoutView, CreateEditGlossaryLayoutView, Modal) { var view = null, title = null; @@ -734,14 +744,40 @@ define(['require', 'utils/Utils', 'modules/Modal', 'utils/Messages', 'utils/Enum "cancelText": "Cancel", "okCloses": false, "okText": model ? "Update" : "Create", - "allowCancel": true + "allowCancel": true, + "width": "765px" }).open(); modal.$el.find('input[data-id=shortDescription]').on('input keydown', function(e) { $(this).val($(this).val().replace(/\s+/g, ' ')); }); modal.$el.find('button.ok').attr("disabled", "true"); + var longDescriptionEditor = modal.$el.find('textarea[data-id=longDescription]'), + okBtn = modal.$el.find('button.ok'); + longDescriptionEditor.trumbowyg({ + btns: [ + ['formatting'], + ['strong', 'em', 'underline', 'del'], + ['link'], + ['justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull'], + ['unorderedList', 'orderedList'], + ['viewHTML'] + ], + removeformatPasted: true, + urlProtocol: true, + defaultLinkTarget: '_blank' + }).on('tbwchange', function() { + okBtn.removeAttr("disabled"); + }); modal.on('ok', function() { modal.$el.find('button.ok').showButtonLoader(); + //Below condition is added for sanitizing the longDescription text against XSS attack. + var editorContent, cleanContent; + editorContent = longDescriptionEditor.trumbowyg('html'); + if (editorContent !== "") { + cleanContent = Utils.sanitizeHtmlContent(editorContent); + longDescriptionEditor.trumbowyg('html', cleanContent); + } + //End CommonViewFunction.createEditGlossaryCategoryTermSubmit(_.extend({ "ref": view, "modal": modal }, options)); }); modal.on('closeModal', function() { diff --git a/dashboardv3/public/js/utils/Utils.js b/dashboardv3/public/js/utils/Utils.js index c3122fc..d4db9f4 100644 --- a/dashboardv3/public/js/utils/Utils.js +++ b/dashboardv3/public/js/utils/Utils.js @@ -16,7 +16,7 @@ * limitations under the License. */ -define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', 'moment', 'store', 'modules/Modal', 'moment-timezone', 'pnotify.buttons', 'pnotify.confirm'], function(require, Globals, pnotify, Messages, Enums, moment, store, Modal) { +define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', 'moment', 'store', 'modules/Modal', 'DOMPurify', 'moment-timezone', 'pnotify.buttons', 'pnotify.confirm'], function(require, Globals, pnotify, Messages, Enums, moment, store, Modal, DOMPurify) { 'use strict'; var Utils = {}; @@ -964,6 +964,14 @@ define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', } return dateValue; } + //-----------------------------------------DOMPurify-------------------------------------- + //This below function expects string that needs to be sanitize against XSS attack. + Utils.sanitizeHtmlContent = function(string) { + if (string) { + return DOMPurify.sanitize(string, { FORBID_TAGS: ['img', 'script', 'iframe', 'embed', 'svg', 'meta'], ALLOWED_ATTR: ['target', 'href'] }); + } + } + //---------------------------------------------------------------------------------------- //------------------------------------------------idleTimeout----------------------------- $.fn.idleTimeout = function(userRuntimeConfig) { @@ -1230,4 +1238,4 @@ define(['require', 'utils/Globals', 'pnotify', 'utils/Messages', 'utils/Enums', //------------------------------------------------ return Utils; -}); \ No newline at end of file +}); diff --git a/dashboardv3/public/js/views/glossary/GlossaryDetailLayoutView.js b/dashboardv3/public/js/views/glossary/GlossaryDetailLayoutView.js index 79622dc..845ace1 100644 --- a/dashboardv3/public/js/views/glossary/GlossaryDetailLayoutView.js +++ b/dashboardv3/public/js/views/glossary/GlossaryDetailLayoutView.js @@ -70,7 +70,8 @@ define(['require', removeTag: '[data-id="removeTagTerm"]', tagClick: '[data-id="tagClickTerm"]', addTag: '[data-id="addTagTerm"]', - backButton: '[data-id="backButton"]' + backButton: '[data-id="backButton"]', + textType: '[name="textType"]' }, /** ui events hash */ events: function() { @@ -174,6 +175,10 @@ define(['require', events["click " + this.ui.addTerm] = 'onClickAddTermBtn'; events["click " + this.ui.addCategory] = 'onClickAddTermBtn'; events["click " + this.ui.addTag] = 'onClickAddTagBtn'; + events["change " + this.ui.textType] = function(e) { + this.isTextTypeChecked = !this.isTextTypeChecked; + this.renderDetails(this.data); + }; return events; }, /** @@ -312,10 +317,18 @@ define(['require', }, renderDetails: function(data) { Utils.hideTitleLoader(this.$('.fontLoader'), this.ui.details); + //Below condition is added for sanitizing the longDescription text against XSS attack. + var longDescriptionContent = (data && data.longDescription) ? data.longDescription : "", + sanitizeLongDescriptionContent = ""; + if (longDescriptionContent !== "") { + sanitizeLongDescriptionContent = Utils.sanitizeHtmlContent(longDescriptionContent); + } + //End if (data) { + var longDescriptionValue = longDescriptionContent ? sanitizeLongDescriptionContent : ""; this.ui.title.text(data.name || data.displayText || data.qualifiedName); this.ui.shortDescription.text(data.shortDescription ? data.shortDescription : ""); - this.ui.longDescription.text(data.longDescription ? data.longDescription : ""); + this.isTextTypeChecked ? this.ui.longDescription.text(longDescriptionValue) : this.ui.longDescription.html(longDescriptionValue); this.generateCategories(data.categories); this.generateTerm(data.terms); this.generateTag(data.classifications);