From 97893d40b58728c5a7c3fb72aa7620daf553b915 Mon Sep 17 00:00:00 2001 From: ppetkow Date: Thu, 22 Aug 2019 16:56:38 +0300 Subject: [PATCH] Replace css with sass #760 (#765) * update contextmenu, menu, searchbox remove necessary div. before each class name * move from css to scss compilation for project and set colors and font to variables * remove override from css * update move color and clean styles * #760 update base on last RP review * remove some selectors from status bar --- dist/jsoneditor.css | 1095 +- dist/jsoneditor.js | 33872 ++++++++-------- dist/jsoneditor.min.css | 2 +- gulpfile.js | 42 +- package-lock.json | 530 +- package.json | 1 + src/css/autocomplete.css | 32 - src/css/contextmenu.css | 518 - src/css/jsoneditor.css | 646 - src/css/menu.css | 127 - src/css/navigationbar.css | 17 - src/css/reset.css | 25 - src/css/searchbox.css | 78 - src/css/statusbar.css | 50 - src/css/treepath.css | 51 - .../selectr/{selectr.css => selectr.scss} | 29 +- src/js/showSortModal.js | 4 + src/js/showTransformModal.js | 4 + src/scss/autocomplete.scss | 34 + src/scss/contextmenu.scss | 441 + src/{css => scss}/img/description.txt | 0 src/{css => scss}/img/jsoneditor-icons.svg | 0 src/scss/jsoneditor.scss | 566 + src/scss/menu.scss | 128 + src/scss/navigationbar.scss | 19 + src/scss/reset.scss | 24 + src/scss/searchbox.scss | 63 + src/scss/statusbar.scss | 51 + src/scss/styles.scss | 39 + src/scss/treepath.scss | 50 + 30 files changed, 19519 insertions(+), 19019 deletions(-) delete mode 100644 src/css/autocomplete.css delete mode 100644 src/css/contextmenu.css delete mode 100644 src/css/jsoneditor.css delete mode 100644 src/css/menu.css delete mode 100644 src/css/navigationbar.css delete mode 100644 src/css/reset.css delete mode 100644 src/css/searchbox.css delete mode 100644 src/css/statusbar.css delete mode 100644 src/css/treepath.css rename src/js/assets/selectr/{selectr.css => selectr.scss} (94%) create mode 100644 src/scss/autocomplete.scss create mode 100644 src/scss/contextmenu.scss rename src/{css => scss}/img/description.txt (100%) rename src/{css => scss}/img/jsoneditor-icons.svg (100%) create mode 100644 src/scss/jsoneditor.scss create mode 100644 src/scss/menu.scss create mode 100644 src/scss/navigationbar.scss create mode 100644 src/scss/reset.scss create mode 100644 src/scss/searchbox.scss create mode 100644 src/scss/statusbar.scss create mode 100644 src/scss/styles.scss create mode 100644 src/scss/treepath.scss diff --git a/dist/jsoneditor.css b/dist/jsoneditor.css index 95190c5..0eaf1d8 100644 --- a/dist/jsoneditor.css +++ b/dist/jsoneditor.css @@ -1,29 +1,35 @@ -/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */ - -div.jsoneditor .jsoneditor-search input { +.jsoneditor .jsoneditor-search input { height: auto; border: inherit; + border: none; + box-shadow: none; } -div.jsoneditor .jsoneditor-search input:focus { - border: none !important; - box-shadow: none !important; -} - -div.jsoneditor table { +.jsoneditor table { border-collapse: collapse; width: auto; } -div.jsoneditor td, -div.jsoneditor th { +.jsoneditor td, +.jsoneditor th { padding: 0; display: table-cell; text-align: left; vertical-align: inherit; border-radius: inherit; } - +.jsoneditor { + color: #1a1a1a; + border: thin solid #3883fa; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + width: 100%; + height: 100%; + position: relative; + padding: 0; + line-height: 100%; +} div.jsoneditor-field, div.jsoneditor-value, @@ -38,8 +44,6 @@ div.jsoneditor-default { float: left; } -/* adjust margin of p elements inside editable divs, needed for Opera, IE */ - div.jsoneditor-field p, div.jsoneditor-value p { margin: 0; @@ -49,6 +53,30 @@ div.jsoneditor-value { word-break: break-word; } +div.jsoneditor-value.jsoneditor-empty::after { + content: "value"; +} + +div.jsoneditor-value.jsoneditor-string { + color: #006000; +} + +div.jsoneditor-value.jsoneditor-number { + color: #ee422e; +} + +div.jsoneditor-value.jsoneditor-boolean { + color: #ff8c00; +} + +div.jsoneditor-value.jsoneditor-null { + color: #004ed0; +} + +div.jsoneditor-value.jsoneditor-invalid { + color: #000000; +} + div.jsoneditor-readonly { min-width: 16px; color: #808080; @@ -60,36 +88,12 @@ div.jsoneditor-empty { border-radius: 2px; } -div.jsoneditor-field.jsoneditor-empty::after, -div.jsoneditor-value.jsoneditor-empty::after { - pointer-events: none; - color: #d3d3d3; - font-size: 8pt; -} - div.jsoneditor-field.jsoneditor-empty::after { content: "field"; } -div.jsoneditor-value.jsoneditor-empty::after { - content: "value"; -} - -div.jsoneditor-value.jsoneditor-url, -a.jsoneditor-value.jsoneditor-url { - color: green; - text-decoration: underline; -} - -a.jsoneditor-value.jsoneditor-url { - display: inline-block; - padding: 2px; - margin: 2px; -} - -a.jsoneditor-value.jsoneditor-url:hover, -a.jsoneditor-value.jsoneditor-url:focus { - color: #ee422e; +div.jsoneditor td { + vertical-align: top; } div.jsoneditor td.jsoneditor-separator { @@ -98,51 +102,37 @@ div.jsoneditor td.jsoneditor-separator { color: #808080; } -div.jsoneditor-field[contenteditable=true]:focus, -div.jsoneditor-field[contenteditable=true]:hover, -div.jsoneditor-value[contenteditable=true]:focus, -div.jsoneditor-value[contenteditable=true]:hover, -div.jsoneditor-field.jsoneditor-highlight, -div.jsoneditor-value.jsoneditor-highlight { - background-color: #FFFFAB; - border: 1px solid yellow; - border-radius: 2px; +div.jsoneditor td.jsoneditor-tree { + vertical-align: top; } -div.jsoneditor-field.jsoneditor-highlight-active, -div.jsoneditor-field.jsoneditor-highlight-active:focus, -div.jsoneditor-field.jsoneditor-highlight-active:hover, -div.jsoneditor-value.jsoneditor-highlight-active, -div.jsoneditor-value.jsoneditor-highlight-active:focus, -div.jsoneditor-value.jsoneditor-highlight-active:hover { - background-color: #ffee00; - border: 1px solid #ffc700; - border-radius: 2px; +div.jsoneditor div.jsoneditor-anchor .picker_wrapper.popup.popup_bottom { + top: 28px; + left: -10px; } -div.jsoneditor-value.jsoneditor-string { - color: #006000; +div.jsoneditor.busy pre.jsoneditor-preview { + background: #f5f5f5; + color: #808080; } -div.jsoneditor-value.jsoneditor-object, -div.jsoneditor-value.jsoneditor-array { - min-width: 16px; +div.jsoneditor.busy div.jsoneditor-busy { + display: inherit; } -div.jsoneditor-value.jsoneditor-number { - color: #ee422e; +div.jsoneditor code.jsoneditor-preview { + background: none; } -div.jsoneditor-value.jsoneditor-boolean { - color: #ff8c00; -} - -div.jsoneditor-value.jsoneditor-null { - color: #004ED0; -} - -div.jsoneditor-value.jsoneditor-invalid { - color: #000000; +div.jsoneditor.jsoneditor-mode-preview pre.jsoneditor-preview { + width: 100%; + height: 100%; + box-sizing: border-box; + overflow: auto; + padding: 2px; + margin: 0; + white-space: pre-wrap; + word-break: break-all; } div.jsoneditor-default { @@ -150,6 +140,13 @@ div.jsoneditor-default { padding-left: 10px; } +div.jsoneditor-tree { + width: 100%; + height: 100%; + position: relative; + overflow: auto; +} + div.jsoneditor-tree button.jsoneditor-button { width: 24px; height: 24px; @@ -160,9 +157,9 @@ div.jsoneditor-tree button.jsoneditor-button { background: transparent url("img/jsoneditor-icons.svg"); } -div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree, -div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree { - cursor: pointer; +div.jsoneditor-tree button.jsoneditor-button:focus { + background-color: #f5f5f5; + outline: #e5e5e5 solid 1px; } div.jsoneditor-tree button.jsoneditor-collapsed { @@ -177,31 +174,20 @@ div.jsoneditor-tree button.jsoneditor-contextmenu { background-position: -48px -72px; } -div.jsoneditor-tree button.jsoneditor-contextmenu:hover, -div.jsoneditor-tree button.jsoneditor-contextmenu:focus, -div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected, -tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { - background-position: -48px -48px; +div.jsoneditor-tree button.jsoneditor-invisible { + visibility: hidden; + background: none; +} + +div.jsoneditor-tree button.jsoneditor-dragarea { + background: url("img/jsoneditor-icons.svg") -72px -72px; + cursor: move; } div.jsoneditor-tree *:focus { outline: none; } -div.jsoneditor-tree button.jsoneditor-button:focus { - /* TODO: nice outline for buttons with focus - outline: #97B0F8 solid 2px; - box-shadow: 0 0 8px #97B0F8; - */ - background-color: #f5f5f5; - outline: #e5e5e5 solid 1px; -} - -div.jsoneditor-tree button.jsoneditor-invisible { - visibility: hidden; - background: none; -} - div.jsoneditor-tree div.jsoneditor-show-more { display: inline-block; padding: 3px 4px; @@ -218,11 +204,6 @@ div.jsoneditor-tree div.jsoneditor-show-more a { color: #808080; } -div.jsoneditor-tree div.jsoneditor-show-more a:hover, -div.jsoneditor-tree div.jsoneditor-show-more a:focus { - color: #ee422e; -} - div.jsoneditor-tree div.jsoneditor-color { display: inline-block; width: 12px; @@ -232,14 +213,9 @@ div.jsoneditor-tree div.jsoneditor-color { cursor: pointer; } -div.jsoneditor div.jsoneditor-anchor .picker_wrapper.popup.popup_bottom { - top: 28px; - left: -10px; -} - div.jsoneditor-tree div.jsoneditor-date { background: #a1a1a1; - color: white; + color: #ffffff; font-family: arial, sans-serif; border-radius: 3px; display: inline-block; @@ -247,26 +223,20 @@ div.jsoneditor-tree div.jsoneditor-date { margin: 0 3px; } -div.jsoneditor { - color: #1A1A1A; - border: thin solid #3883fa; - /* we use thin and not 1px to work around an issue in Chrome/IE, see #637 */ - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; - width: 100%; - height: 100%; - position: relative; - padding: 0; - line-height: 100%; -} - div.jsoneditor-tree table.jsoneditor-tree { border-collapse: collapse; border-spacing: 0; width: 100%; } +div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") -168px -48px; +} + div.jsoneditor-outer { position: static; width: 100%; @@ -283,6 +253,11 @@ div.jsoneditor-outer.has-nav-bar { padding-top: 26px; } +div.jsoneditor-outer.has-nav-bar.has-main-menu-bar { + margin-top: -61px; + padding-top: 61px; +} + div.jsoneditor-outer.has-status-bar { margin-bottom: -26px; padding-bottom: 26px; @@ -293,33 +268,6 @@ div.jsoneditor-outer.has-main-menu-bar { padding-top: 35px; } -div.jsoneditor-outer.has-nav-bar.has-main-menu-bar { - margin-top: -61px; - padding-top: 61px; -} - - - -div.jsoneditor.busy pre.jsoneditor-preview { - background: #f5f5f5; - color: #808080; -} - -div.jsoneditor code.jsoneditor-preview { - background: none; -} - -div.jsoneditor.jsoneditor-mode-preview pre.jsoneditor-preview { - width: 100%; - height: 100%; - box-sizing: border-box; - overflow: auto; - padding: 2px; - margin: 0; - white-space: pre-wrap; - word-break: break-all; -} - div.jsoneditor-busy { position: absolute; top: 15%; @@ -331,15 +279,79 @@ div.jsoneditor-busy { } div.jsoneditor-busy span { - background-color: #FFFFAB; - border: 1px solid yellow; + background-color: #ffffab; + border: 1px solid #ffee00; border-radius: 3px; padding: 5px 15px; - box-shadow: 0 0 5px rgba(0,0,0,0.4); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); } -div.jsoneditor.busy div.jsoneditor-busy { - display: inherit; +div.jsoneditor-field.jsoneditor-empty::after, +div.jsoneditor-value.jsoneditor-empty::after { + pointer-events: none; + color: #d3d3d3; + font-size: 8pt; +} + +div.jsoneditor-value.jsoneditor-url, +a.jsoneditor-value.jsoneditor-url { + color: #006000; + text-decoration: underline; +} + +a.jsoneditor-value.jsoneditor-url { + display: inline-block; + padding: 2px; + margin: 2px; +} + +a.jsoneditor-value.jsoneditor-url:hover, +a.jsoneditor-value.jsoneditor-url:focus { + color: #ee422e; +} + +div.jsoneditor-field[contenteditable="true"]:focus, +div.jsoneditor-field[contenteditable="true"]:hover, +div.jsoneditor-value[contenteditable="true"]:focus, +div.jsoneditor-value[contenteditable="true"]:hover, +div.jsoneditor-field.jsoneditor-highlight, +div.jsoneditor-value.jsoneditor-highlight { + background-color: #ffffab; + border: 1px solid #ffee00; + border-radius: 2px; +} + +div.jsoneditor-field.jsoneditor-highlight-active, +div.jsoneditor-field.jsoneditor-highlight-active:focus, +div.jsoneditor-field.jsoneditor-highlight-active:hover, +div.jsoneditor-value.jsoneditor-highlight-active, +div.jsoneditor-value.jsoneditor-highlight-active:focus, +div.jsoneditor-value.jsoneditor-highlight-active:hover { + background-color: #ffee00; + border: 1px solid #ffc700; + border-radius: 2px; +} + +div.jsoneditor-value.jsoneditor-object, +div.jsoneditor-value.jsoneditor-array { + min-width: 16px; +} + +div.jsoneditor-mode-view tr.jsoneditor-expandable td.jsoneditor-tree, +div.jsoneditor-mode-form tr.jsoneditor-expandable td.jsoneditor-tree { + cursor: pointer; +} + +div.jsoneditor-tree button.jsoneditor-contextmenu:hover, +div.jsoneditor-tree button.jsoneditor-contextmenu:focus, +div.jsoneditor-tree button.jsoneditor-contextmenu.jsoneditor-selected, +tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { + background-position: -48px -48px; +} + +div.jsoneditor-tree div.jsoneditor-show-more a:hover, +div.jsoneditor-tree div.jsoneditor-show-more a:focus { + color: #ee422e; } textarea.jsoneditor-text, @@ -347,13 +359,6 @@ textarea.jsoneditor-text, min-height: 150px; } -div.jsoneditor-tree { - width: 100%; - height: 100%; - position: relative; - overflow: auto; -} - textarea.jsoneditor-text { width: 100%; height: 100%; @@ -363,7 +368,7 @@ textarea.jsoneditor-text { box-sizing: border-box; outline-width: 0; border: none; - background-color: white; + background-color: #ffffff; resize: none; } @@ -382,11 +387,6 @@ tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu { visibility: visible; } -div.jsoneditor-tree button.jsoneditor-dragarea { - background: url("img/jsoneditor-icons.svg") -72px -72px; - cursor: move; -} - div.jsoneditor-tree button.jsoneditor-dragarea:hover, div.jsoneditor-tree button.jsoneditor-dragarea:focus, tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea { @@ -400,14 +400,6 @@ div.jsoneditor td { margin: 0; } -div.jsoneditor td { - vertical-align: top; -} - -div.jsoneditor td.jsoneditor-tree { - vertical-align: top; -} - div.jsoneditor-field, div.jsoneditor-value, div.jsoneditor td, @@ -417,15 +409,12 @@ div.jsoneditor pre.jsoneditor-preview, div.jsoneditor .jsoneditor-schema-error { font-family: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif; font-size: 10pt; - color: #1A1A1A; + color: #1a1a1a; } -/* popover */ - .jsoneditor-schema-error { cursor: default; display: inline-block; - /*font-family: arial, sans-serif;*/ height: 24px; line-height: 24px; position: relative; @@ -433,24 +422,11 @@ div.jsoneditor .jsoneditor-schema-error { width: 24px; } -div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { - width: 24px; - height: 24px; - padding: 0; - margin: 0 4px 0 0; - background: url("img/jsoneditor-icons.svg") -168px -48px; -} - -.jsoneditor-text-errors tr.jump-to-line:hover { - text-decoration: underline; - cursor: pointer; -} - .jsoneditor-schema-error .jsoneditor-popover { background-color: #4c4c4c; border-radius: 3px; - box-shadow: 0 0 5px rgba(0,0,0,0.4); - color: #fff; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); + color: #ffffff; display: none; padding: 7px 10px; position: absolute; @@ -463,46 +439,31 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { left: -98px; } -.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below { - top: 32px; - left: -98px; -} - -.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left { - top: -7px; - right: 32px; -} - -.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right { - top: -7px; - left: 32px; -} - -.jsoneditor-schema-error .jsoneditor-popover:before { - border-right: 7px solid transparent; - border-left: 7px solid transparent; - content: ''; - display: block; - left: 50%; - margin-left: -7px; - position: absolute; -} - .jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above:before { border-top: 7px solid #4c4c4c; bottom: -7px; } +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below { + top: 32px; + left: -98px; +} + .jsoneditor-schema-error .jsoneditor-popover.jsoneditor-below:before { border-bottom: 7px solid #4c4c4c; top: -7px; } +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left { + top: -7px; + right: 32px; +} + .jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before { border-left: 7px solid #4c4c4c; border-top: 7px solid transparent; border-bottom: 7px solid transparent; - content: ''; + content: ""; top: 19px; right: -14px; left: inherit; @@ -511,11 +472,16 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { position: absolute; } +.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right { + top: -7px; + left: 32px; +} + .jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before { border-right: 7px solid #4c4c4c; border-top: 7px solid transparent; border-bottom: 7px solid transparent; - content: ''; + content: ""; top: 19px; left: -14px; margin-left: inherit; @@ -523,15 +489,28 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { position: absolute; } +.jsoneditor-schema-error .jsoneditor-popover:before { + border-right: 7px solid transparent; + border-left: 7px solid transparent; + content: ""; + display: block; + left: 50%; + margin-left: -7px; + position: absolute; +} + +.jsoneditor-text-errors tr.jump-to-line:hover { + text-decoration: underline; + cursor: pointer; +} + .jsoneditor-schema-error:hover .jsoneditor-popover, .jsoneditor-schema-error:focus .jsoneditor-popover { display: block; - -webkit-animation: fade-in .3s linear 1, move-up .3s linear 1; - -moz-animation: fade-in .3s linear 1, move-up .3s linear 1; - -ms-animation: fade-in .3s linear 1, move-up .3s linear 1; + animation: fade-in 0.3s linear 1, move-up 0.3s linear 1; } -@-webkit-keyframes fade-in { +@keyframes fade-in { from { opacity: 0; } @@ -541,50 +520,6 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { } } -@-moz-keyframes fade-in { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@-ms-keyframes fade-in { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -/*@-webkit-keyframes move-up {*/ - -/*from { bottom: 24px; }*/ - -/*to { bottom: 32px; }*/ - -/*}*/ - -/*@-moz-keyframes move-up {*/ - -/*from { bottom: 24px; }*/ - -/*to { bottom: 32px; }*/ - -/*}*/ - -/*@-ms-keyframes move-up {*/ - -/*from { bottom: 24px; }*/ - -/*to { bottom: 32px; }*/ - -/*}*/ - /* JSON schema errors displayed at the bottom of the editor in mode text and code */ .jsoneditor .jsoneditor-validation-errors-container { @@ -623,7 +558,7 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { .jsoneditor .jsoneditor-text-errors { width: 100%; border-collapse: collapse; - border-top: 1px solid #ffd700; + border-top: 1px solid #ffc700; } .jsoneditor .jsoneditor-text-errors td { @@ -637,7 +572,7 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { } .jsoneditor .jsoneditor-text-errors tr { - background-color: #ffef8b; + background-color: #ffffab; } .jsoneditor .jsoneditor-text-errors tr.parse-error { @@ -654,38 +589,18 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { } .jsoneditor-text-errors tr .jsoneditor-schema-error { - background: url("img/jsoneditor-icons.svg") -168px -48px; + background: url("img/jsoneditor-icons.svg") -168px -48px; } .jsoneditor-text-errors tr.parse-error .jsoneditor-schema-error { - background: url("img/jsoneditor-icons.svg") -25px 0px; + background: url("img/jsoneditor-icons.svg") -25px 0px; } .fadein { - -webkit-animation: fadein .3s; - animation: fadein .3s; - -moz-animation: fadein .3s; - -o-animation: fadein .3s; -} - -@-webkit-keyframes fadein { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} - -@-moz-keyframes fadein { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } + -webkit-animation: fadein 0.3s; + animation: fadein 0.3s; + -moz-animation: fadein 0.3s; + -o-animation: fadein 0.3s; } @keyframes fadein { @@ -697,42 +612,25 @@ div.jsoneditor-tree .jsoneditor-button.jsoneditor-schema-error { opacity: 1; } } - -@-o-keyframes fadein { - 0% { - opacity: 0; - } - - 100% { - opacity: 1; - } -} -/* ContextMenu - main menu */ - -div.jsoneditor-contextmenu-root { +.jsoneditor-contextmenu-root { position: relative; width: 0; height: 0; } -div.jsoneditor-contextmenu { +.jsoneditor-contextmenu { position: absolute; box-sizing: content-box; - z-index: 99999; + z-index: 99; } -div.jsoneditor-contextmenu ul, -div.jsoneditor-contextmenu li { - box-sizing: content-box; - position: relative; -} - -div.jsoneditor-contextmenu ul { +.jsoneditor-contextmenu .jsoneditor-menu { position: relative; left: 0; top: 0; width: 128px; - background: white; + height: auto; + background: #ffffff; border: 1px solid #d3d3d3; box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); list-style: none; @@ -740,7 +638,7 @@ div.jsoneditor-contextmenu ul { padding: 0; } -div.jsoneditor-contextmenu ul li button { +.jsoneditor-contextmenu .jsoneditor-menu button { position: relative; padding: 0 4px 0 0; margin: 0; @@ -756,33 +654,61 @@ div.jsoneditor-contextmenu ul li button { text-align: left; } -/* Fix button padding in firefox */ - -div.jsoneditor-contextmenu ul li button::-moz-focus-inner { +.jsoneditor-contextmenu .jsoneditor-menu button::-moz-focus-inner { padding: 0; border: 0; } -div.jsoneditor-contextmenu ul li button:hover, -div.jsoneditor-contextmenu ul li button:focus { - color: #1a1a1a; - background-color: #f5f5f5; - outline: none; -} - -div.jsoneditor-contextmenu ul li button.jsoneditor-default { +.jsoneditor-contextmenu .jsoneditor-menu button.jsoneditor-default { width: 96px; - /* 128px - 32px */ } -div.jsoneditor-contextmenu ul li button.jsoneditor-expand { +.jsoneditor-contextmenu .jsoneditor-menu button.jsoneditor-expand { float: right; width: 32px; height: 24px; border-left: 1px solid #e5e5e5; } -div.jsoneditor-contextmenu div.jsoneditor-icon { +.jsoneditor-contextmenu .jsoneditor-menu li { + overflow: hidden; +} + +.jsoneditor-contextmenu .jsoneditor-menu li ul { + display: none; + position: relative; + left: -10px; + top: 0; + border: none; + box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5); + padding: 0 10px; + -webkit-transition: all 0.3s ease-out; + -moz-transition: all 0.3s ease-out; + -o-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; +} + +.jsoneditor-contextmenu .jsoneditor-menu li ul .jsoneditor-icon { + margin-left: 24px; +} + +.jsoneditor-contextmenu .jsoneditor-menu li ul li button { + padding-left: 24px; + animation: all ease-in-out 1s; +} + +.jsoneditor-contextmenu .jsoneditor-menu li button .jsoneditor-expand { + position: absolute; + top: 0; + right: 0; + width: 24px; + height: 24px; + padding: 0; + margin: 0 4px 0 0; + background: url("img/jsoneditor-icons.svg") 0 -72px; +} + +.jsoneditor-contextmenu .jsoneditor-icon { position: absolute; top: 0; left: 0; @@ -794,145 +720,105 @@ div.jsoneditor-contextmenu div.jsoneditor-icon { background-image: url("img/jsoneditor-icons.svg"); } -div.jsoneditor-contextmenu ul li ul div.jsoneditor-icon { - margin-left: 24px; -} - -div.jsoneditor-contextmenu div.jsoneditor-text { +.jsoneditor-contextmenu .jsoneditor-text { padding: 4px 0 4px 24px; word-wrap: break-word; } -div.jsoneditor-contextmenu div.jsoneditor-text.jsoneditor-right-margin { +.jsoneditor-contextmenu .jsoneditor-text.jsoneditor-right-margin { padding-right: 24px; } -div.jsoneditor-contextmenu ul li button div.jsoneditor-expand { - position: absolute; - top: 0; - right: 0; - width: 24px; - height: 24px; - padding: 0; - margin: 0 4px 0 0; - background: url("img/jsoneditor-icons.svg") 0 -72px; -} - -div.jsoneditor-contextmenu div.jsoneditor-separator { +.jsoneditor-contextmenu .jsoneditor-separator { height: 0; border-top: 1px solid #e5e5e5; padding-top: 5px; margin-top: 5px; } -div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-remove .jsoneditor-icon { background-position: -24px 0; } -div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-append .jsoneditor-icon { background-position: 0 0; } -div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-insert .jsoneditor-icon { background-position: 0 0; } -div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-duplicate .jsoneditor-icon { background-position: -48px 0; } -div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-sort-asc .jsoneditor-icon { background-position: -168px 0; } -div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-sort-desc .jsoneditor-icon { background-position: -192px 0; } -div.jsoneditor-contextmenu button.jsoneditor-transform > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-transform .jsoneditor-icon { background-position: -216px 0; } -div.jsoneditor-contextmenu button.jsoneditor-extract > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-extract .jsoneditor-icon { background-position: 0 -24px; } -/* ContextMenu - sub menu */ - -div.jsoneditor-contextmenu ul li button.jsoneditor-selected, -div.jsoneditor-contextmenu ul li button.jsoneditor-selected:hover, -div.jsoneditor-contextmenu ul li button.jsoneditor-selected:focus { - color: white; - background-color: #ee422e; -} - -div.jsoneditor-contextmenu ul li { - overflow: hidden; -} - -div.jsoneditor-contextmenu ul li ul { - display: none; - position: relative; - left: -10px; - top: 0; - border: none; - box-shadow: inset 0 0 10px rgba(128, 128, 128, 0.5); - padding: 0 10px; - /* TODO: transition is not supported on IE8-9 */ - -webkit-transition: all 0.3s ease-out; - -moz-transition: all 0.3s ease-out; - -o-transition: all 0.3s ease-out; - transition: all 0.3s ease-out; -} - - - -div.jsoneditor-contextmenu ul li ul li button { - padding-left: 24px; - animation: all ease-in-out 1s; -} - -div.jsoneditor-contextmenu ul li ul li button:hover, -div.jsoneditor-contextmenu ul li ul li button:focus { - background-color: #f5f5f5; -} - -div.jsoneditor-contextmenu button.jsoneditor-type-string > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-type-string .jsoneditor-icon { background-position: -144px 0; } -div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-type-auto .jsoneditor-icon { background-position: -120px 0; } -div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-type-object .jsoneditor-icon { background-position: -72px 0; } -div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-type-array .jsoneditor-icon { background-position: -96px 0; } -div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { +.jsoneditor-contextmenu button.jsoneditor-type-modes .jsoneditor-icon { background-image: none; width: 6px; } -/* pico modal styling */ +.jsoneditor-contextmenu ul, +.jsoneditor-contextmenu li { + box-sizing: content-box; + position: relative; +} -.jsoneditor-modal-overlay { - position: absolute !important; - background: rgb(1,1,1) !important; - opacity: 0.3 !important; +.jsoneditor-contextmenu .jsoneditor-menu button:hover, +.jsoneditor-contextmenu .jsoneditor-menu button:focus { + color: #1a1a1a; + background-color: #f5f5f5; + outline: none; +} + +.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected, +.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover, +.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus { + color: #ffffff; + background-color: #ee422e; +} + +.jsoneditor-contextmenu .jsoneditor-menu li ul li button:hover, +.jsoneditor-contextmenu .jsoneditor-menu li ul li button:focus { + background-color: #f5f5f5; } .jsoneditor-modal { - position: absolute !important; - max-width: 95% !important; - width: auto !important; + max-width: 95%; border-radius: 2px !important; padding: 45px 15px 15px 15px !important; - box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3) !important; + box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); color: #4d4d4d; line-height: 1.3em; } @@ -953,41 +839,17 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { font-family: arial, sans-serif; font-size: 11pt; background: #3883fa; - color: white; + color: #ffffff; } .jsoneditor-modal table { width: 100%; } -.jsoneditor-modal table th, -.jsoneditor-modal table td { - text-align: left; - vertical-align: top; - font-weight: normal; - color: #4d4d4d; - border-spacing: 0; - border-collapse: collapse; -} - .jsoneditor-modal table td { padding: 3px 0; } -.jsoneditor-modal p:first-child { - margin-top: 0; -} - -.jsoneditor-modal a { - color: #3883fa; -} - - - -.jsoneditor-modal .jsoneditor-jmespath-block { - margin-bottom: 10px; -} - .jsoneditor-modal table td.jsoneditor-modal-input { text-align: right; padding-right: 0; @@ -998,56 +860,28 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { padding-top: 15px; } +.jsoneditor-modal table th { + vertical-align: middle; +} + +.jsoneditor-modal p:first-child { + margin-top: 0; +} + +.jsoneditor-modal a { + color: #3883fa; +} + +.jsoneditor-modal .jsoneditor-jmespath-block { + margin-bottom: 10px; +} + .jsoneditor-modal .pico-close { background: none !important; font-size: 24px !important; top: 7px !important; right: 7px !important; - color: white; -} - -.jsoneditor-modal select, -.jsoneditor-modal textarea, -.jsoneditor-modal input, -.jsoneditor-modal #query { - background: #ffffff; - border: 1px solid #d3d3d3; - color: #4d4d4d; - border-radius: 3px; - padding: 4px; -} - -.jsoneditor-modal, -.jsoneditor-modal table td, -.jsoneditor-modal table th, -.jsoneditor-modal select, -.jsoneditor-modal option, -.jsoneditor-modal textarea, -.jsoneditor-modal input, -.jsoneditor-modal #query { - font-size: 10.5pt; - font-family: arial, sans-serif; -} - -.jsoneditor-modal table th { - vertical-align: middle; -} - -.jsoneditor-modal #query, -.jsoneditor-modal .jsoneditor-transform-preview { - font-family: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif; - font-size: 10pt; -} - -.jsoneditor-modal input[type="button"], -.jsoneditor-modal input[type="submit"] { - background: #f5f5f5; - padding: 4px 20px; -} - -.jsoneditor-modal select, -.jsoneditor-modal input { - cursor: pointer; + color: #ffffff; } .jsoneditor-modal input { @@ -1116,19 +950,6 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { border-bottom-right-radius: 3px; } -.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc, -.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc { - background: #3883fa; - border-color: #3883fa; - color: white; -} - -.jsoneditor-modal #query, -.jsoneditor-modal .jsoneditor-transform-preview { - width: 100%; - box-sizing: border-box; -} - .jsoneditor-modal .jsoneditor-transform-preview { background: #f5f5f5; height: 200px; @@ -1212,7 +1033,66 @@ div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon { background-color: #3883fa; border-radius: 5px; } -div.jsoneditor-menu { + +.jsoneditor-modal table th, +.jsoneditor-modal table td { + text-align: left; + vertical-align: top; + font-weight: normal; + color: #4d4d4d; + border-spacing: 0; + border-collapse: collapse; +} + +.jsoneditor-modal select, +.jsoneditor-modal textarea, +.jsoneditor-modal input, +.jsoneditor-modal #query { + background: #ffffff; + border: 1px solid #d3d3d3; + color: #4d4d4d; + border-radius: 3px; + padding: 4px; +} + +.jsoneditor-modal, +.jsoneditor-modal table td, +.jsoneditor-modal table th, +.jsoneditor-modal select, +.jsoneditor-modal option, +.jsoneditor-modal textarea, +.jsoneditor-modal input, +.jsoneditor-modal #query { + font-size: 10.5pt; + font-family: arial, sans-serif; +} + +.jsoneditor-modal #query, +.jsoneditor-modal .jsoneditor-transform-preview { + font-family: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif; + font-size: 10pt; + width: 100%; + box-sizing: border-box; +} + +.jsoneditor-modal input[type="button"], +.jsoneditor-modal input[type="submit"] { + background: #f5f5f5; + padding: 4px 20px; +} + +.jsoneditor-modal select, +.jsoneditor-modal input { + cursor: pointer; +} + +.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc, +.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc { + background: #3883fa; + border-color: #3883fa; + color: #ffffff; +} +.jsoneditor-menu { width: 100%; height: 35px; padding: 2px; @@ -1220,13 +1100,13 @@ div.jsoneditor-menu { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; - color: white; + color: #ffffff; background-color: #3883fa; border-bottom: 1px solid #3883fa; } -div.jsoneditor-menu > button, -div.jsoneditor-menu > div.jsoneditor-modes > button { +.jsoneditor-menu > button, +.jsoneditor-menu > .jsoneditor-modes > button { width: 26px; height: 26px; margin: 2px; @@ -1234,137 +1114,121 @@ div.jsoneditor-menu > div.jsoneditor-modes > button { border-radius: 2px; border: 1px solid transparent; background: transparent url("img/jsoneditor-icons.svg"); - color: white; + color: #ffffff; opacity: 0.8; font-family: arial, sans-serif; font-size: 10pt; float: left; } -div.jsoneditor-menu > button:hover, -div.jsoneditor-menu > div.jsoneditor-modes > button:hover { - background-color: rgba(255,255,255,0.2); - border: 1px solid rgba(255,255,255,0.4); +.jsoneditor-menu > button:hover, +.jsoneditor-menu > .jsoneditor-modes > button:hover { + background-color: rgba(255, 255, 255, 0.2); + border: 1px solid rgba(255, 255, 255, 0.4); } -div.jsoneditor-menu > button:focus, -div.jsoneditor-menu > button:active, -div.jsoneditor-menu > div.jsoneditor-modes > button:focus, -div.jsoneditor-menu > div.jsoneditor-modes > button:active { - background-color: rgba(255,255,255,0.3); +.jsoneditor-menu > button:focus, +.jsoneditor-menu > button:active, +.jsoneditor-menu > .jsoneditor-modes > button:focus, +.jsoneditor-menu > .jsoneditor-modes > button:active { + background-color: rgba(255, 255, 255, 0.3); } -div.jsoneditor-menu > button:disabled, -div.jsoneditor-menu > div.jsoneditor-modes > button:disabled { +.jsoneditor-menu > button:disabled, +.jsoneditor-menu > .jsoneditor-modes > button:disabled { opacity: 0.5; background-color: transparent; border: none; } -div.jsoneditor-menu > button.jsoneditor-collapse-all { +.jsoneditor-menu > button.jsoneditor-collapse-all { background-position: 0 -96px; } -div.jsoneditor-menu > button.jsoneditor-expand-all { +.jsoneditor-menu > button.jsoneditor-expand-all { background-position: 0 -120px; } -div.jsoneditor-menu > button.jsoneditor-sort { +.jsoneditor-menu > button.jsoneditor-sort { background-position: -120px -96px; } -div.jsoneditor-menu > button.jsoneditor-transform { +.jsoneditor-menu > button.jsoneditor-transform { background-position: -144px -96px; } -div.jsoneditor.jsoneditor-mode-view > div.jsoneditor-menu > button.jsoneditor-sort, -div.jsoneditor.jsoneditor-mode-form > div.jsoneditor-menu > button.jsoneditor-sort, -div.jsoneditor.jsoneditor-mode-view > div.jsoneditor-menu > button.jsoneditor-transform, -div.jsoneditor.jsoneditor-mode-form > div.jsoneditor-menu > button.jsoneditor-transform { +.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-sort, +.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-sort, +.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-transform, +.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-transform { display: none; } -div.jsoneditor-menu > button.jsoneditor-undo { +.jsoneditor-menu > button.jsoneditor-undo { background-position: -24px -96px; } -div.jsoneditor-menu > button.jsoneditor-undo:disabled { +.jsoneditor-menu > button.jsoneditor-undo:disabled { background-position: -24px -120px; } -div.jsoneditor-menu > button.jsoneditor-redo { +.jsoneditor-menu > button.jsoneditor-redo { background-position: -48px -96px; } -div.jsoneditor-menu > button.jsoneditor-redo:disabled { +.jsoneditor-menu > button.jsoneditor-redo:disabled { background-position: -48px -120px; } -div.jsoneditor-menu > button.jsoneditor-compact { +.jsoneditor-menu > button.jsoneditor-compact { background-position: -72px -96px; } -div.jsoneditor-menu > button.jsoneditor-format { +.jsoneditor-menu > button.jsoneditor-format { background-position: -72px -120px; } -div.jsoneditor-menu > button.jsoneditor-repair { +.jsoneditor-menu > button.jsoneditor-repair { background-position: -96px -96px; } -div.jsoneditor-menu > div.jsoneditor-modes { +.jsoneditor-menu > .jsoneditor-modes { display: inline-block; float: left; } -div.jsoneditor-menu > div.jsoneditor-modes > button { +.jsoneditor-menu > .jsoneditor-modes > button { background-image: none; width: auto; padding-left: 6px; padding-right: 6px; } -div.jsoneditor-menu > button.jsoneditor-separator, -div.jsoneditor-menu > div.jsoneditor-modes > button.jsoneditor-separator { +.jsoneditor-menu > button.jsoneditor-separator, +.jsoneditor-menu > .jsoneditor-modes > button.jsoneditor-separator { margin-left: 10px; } -div.jsoneditor-menu a { +.jsoneditor-menu a { font-family: arial, sans-serif; font-size: 10pt; - color: white; + color: #ffffff; opacity: 0.8; vertical-align: middle; } -div.jsoneditor-menu a:hover { +.jsoneditor-menu a:hover { opacity: 1; } -div.jsoneditor-menu a.jsoneditor-poweredBy { +.jsoneditor-menu a.jsoneditor-poweredBy { font-size: 8pt; position: absolute; right: 0; top: 0; padding: 10px; } -table.jsoneditor-search input, -table.jsoneditor-search div.jsoneditor-results { - font-family: arial, sans-serif; - font-size: 10pt; - color: #1A1A1A; - background: transparent; - /* For Firefox */ -} - -table.jsoneditor-search div.jsoneditor-results { - color: white; - padding-right: 5px; - line-height: 24px; - padding-top: 2px; -} - -table.jsoneditor-search { +.jsoneditor-search { position: absolute; right: 4px; top: 4px; @@ -1372,18 +1236,20 @@ table.jsoneditor-search { border-spacing: 0; } -table.jsoneditor-search div.jsoneditor-frame { +.jsoneditor-search .jsoneditor-results { + color: #ffffff; + padding-right: 5px; + line-height: 26px; +} + +.jsoneditor-search .jsoneditor-frame { border: 1px solid transparent; - background-color: white; + background-color: #ffffff; padding: 0 2px; margin: 0; } -table.jsoneditor-search div.jsoneditor-frame table { - border-collapse: collapse; -} - -table.jsoneditor-search input { +.jsoneditor-search input { width: 120px; border: none; outline: none; @@ -1391,7 +1257,7 @@ table.jsoneditor-search input { line-height: 20px; } -table.jsoneditor-search button { +.jsoneditor-search button { width: 16px; height: 24px; padding: 0; @@ -1401,36 +1267,41 @@ table.jsoneditor-search button { vertical-align: top; } -table.jsoneditor-search button:hover { +.jsoneditor-search button:hover { background-color: transparent; } -table.jsoneditor-search button.jsoneditor-refresh { +.jsoneditor-search button.jsoneditor-refresh { width: 18px; background-position: -99px -73px; } -table.jsoneditor-search button.jsoneditor-next { +.jsoneditor-search button.jsoneditor-next { cursor: pointer; background-position: -124px -73px; } -table.jsoneditor-search button.jsoneditor-next:hover { +.jsoneditor-search button.jsoneditor-next:hover { background-position: -124px -49px; } -table.jsoneditor-search button.jsoneditor-previous { +.jsoneditor-search button.jsoneditor-previous { cursor: pointer; background-position: -148px -73px; margin-right: 2px; } -table.jsoneditor-search button.jsoneditor-previous:hover { +.jsoneditor-search button.jsoneditor-previous:hover { background-position: -148px -49px; } -div.jsoneditor div.autocomplete.dropdown { + +.jsoneditor-search input, +.jsoneditor-search .jsoneditor-results { + font-family: arial, sans-serif; +} +.jsoneditor.autocomplete.dropdown { position: absolute; - background: white; + background: #ffffff; box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); border: 1px solid #d3d3d3; z-index: 100; @@ -1446,27 +1317,27 @@ div.jsoneditor div.autocomplete.dropdown { font-size: 10pt; } -div.jsoneditor div.autocomplete.dropdown .item { +.jsoneditor.autocomplete.dropdown .item { color: #333; } -div.jsoneditor div.autocomplete.dropdown .item.hover { +.jsoneditor.autocomplete.dropdown .item.hover { background-color: #ddd; } -div.jsoneditor div.autocomplete.hint { +.jsoneditor.autocomplete.hint { color: #aaa; top: 4px; left: 4px; } -div.jsoneditor-treepath { +.jsoneditor-treepath { padding: 0 5px; overflow: hidden; white-space: nowrap; outline: none; } -div.jsoneditor-treepath.show-all { +.jsoneditor-treepath.show-all { word-wrap: break-word; white-space: normal; position: absolute; @@ -1475,12 +1346,16 @@ div.jsoneditor-treepath.show-all { box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); } -div.jsoneditor-treepath div.jsoneditor-contextmenu-root { +.jsoneditor-treepath.show-all span.jsoneditor-treepath-show-all-btn { + display: none; +} + +.jsoneditor-treepath div.jsoneditor-contextmenu-root { position: absolute; left: 0; } -div.jsoneditor-treepath span.jsoneditor-treepath-show-all-btn { +.jsoneditor-treepath .jsoneditor-treepath-show-all-btn { position: absolute; background-color: #ebebeb; left: 0; @@ -1489,28 +1364,24 @@ div.jsoneditor-treepath span.jsoneditor-treepath-show-all-btn { cursor: pointer; } -div.jsoneditor-treepath.show-all span.jsoneditor-treepath-show-all-btn { - display: none; -} - -div.jsoneditor-treepath span.jsoneditor-treepath-element { +.jsoneditor-treepath .jsoneditor-treepath-element { margin: 1px; font-family: arial, sans-serif; font-size: 10pt; } -div.jsoneditor-treepath span.jsoneditor-treepath-seperator { +.jsoneditor-treepath .jsoneditor-treepath-seperator { margin: 2px; font-size: 9pt; font-family: arial, sans-serif; } -div.jsoneditor-treepath span.jsoneditor-treepath-element:hover, -div.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover { +.jsoneditor-treepath span.jsoneditor-treepath-element:hover, +.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover { cursor: pointer; text-decoration: underline; } -div.jsoneditor-statusbar { +.jsoneditor-statusbar { line-height: 26px; height: 26px; color: #808080; @@ -1522,20 +1393,15 @@ div.jsoneditor-statusbar { font-size: 10pt; } -div.jsoneditor-statusbar > .jsoneditor-curserinfo-label, -div.jsoneditor-statusbar > .jsoneditor-size-info { - margin: 0 4px; -} - -div.jsoneditor-statusbar > .jsoneditor-curserinfo-val { +.jsoneditor-statusbar .jsoneditor-curserinfo-val { margin-right: 12px; } -div.jsoneditor-statusbar > .jsoneditor-curserinfo-count { +.jsoneditor-statusbar .jsoneditor-curserinfo-count { margin-left: 4px; } -div.jsoneditor-statusbar > .jsoneditor-validation-error-icon { +.jsoneditor-statusbar .jsoneditor-validation-error-icon { float: right; width: 24px; height: 24px; @@ -1545,13 +1411,13 @@ div.jsoneditor-statusbar > .jsoneditor-validation-error-icon { cursor: pointer; } -div.jsoneditor-statusbar > .jsoneditor-validation-error-count { +.jsoneditor-statusbar .jsoneditor-validation-error-count { float: right; margin: 0 4px 0 0; cursor: pointer; } -div.jsoneditor-statusbar > .jsoneditor-parse-error-icon { +.jsoneditor-statusbar .jsoneditor-parse-error-icon { float: right; width: 24px; height: 24px; @@ -1560,10 +1426,15 @@ div.jsoneditor-statusbar > .jsoneditor-parse-error-icon { background: url("img/jsoneditor-icons.svg") -25px 0px; } -div.jsoneditor-statusbar .jsoneditor-array-info a { +.jsoneditor-statusbar .jsoneditor-array-info a { color: inherit; } -div.jsoneditor-navigation-bar { + +.jsoneditor-statusbar .jsoneditor-curserinfo-label, +.jsoneditor-statusbar .jsoneditor-size-info { + margin: 0 4px; +} +.jsoneditor-navigation-bar { width: 100%; height: 26px; line-height: 26px; @@ -1638,9 +1509,9 @@ div.jsoneditor-navigation-bar { width: 100%; padding: 7px 28px 7px 14px; cursor: pointer; - border: 1px solid #999; + border: 1px solid #999999; border-radius: 3px; - background-color: #fff; + background-color: #ffffff; } .selectr-selected::before { @@ -1696,7 +1567,7 @@ div.jsoneditor-navigation-bar { padding: 2px 25px 2px 8px; margin: 0 2px 2px 0; cursor: default; - color: #fff; + color: #ffffff; border: medium none; border-radius: 10px; background: #acb7bf none repeat scroll 0 0; @@ -1716,9 +1587,9 @@ div.jsoneditor-navigation-bar { width: 100%; border-width: 0 1px 1px; border-style: solid; - border-color: transparent #999 #999; + border-color: transparent #999999 #999999; border-radius: 0 0 3px 3px; - background-color: #fff; + background-color: #ffffff; } .selectr-container.open .selectr-options-container { @@ -1784,7 +1655,7 @@ div.jsoneditor-navigation-bar { top: 4px; width: 3px; height: 12px; - background-color: #fff; + background-color: #ffffff; } .selectr-clear:before, @@ -1819,7 +1690,7 @@ div.jsoneditor-navigation-bar { width: calc(100% - 30px); margin: 10px 15px; padding: 7px 30px 7px 9px; - border: 1px solid #999; + border: 1px solid #999999; border-radius: 3px; } @@ -1828,9 +1699,9 @@ div.jsoneditor-navigation-bar { box-sizing: border-box; width: 100%; padding: 8px 16px; - border-top: 1px solid #999; + border-top: 1px solid #999999; border-radius: 0 0 3px 3px; - background-color: #fff; + background-color: #ffffff; } .selectr-container.notice .selectr-notice { @@ -1891,7 +1762,7 @@ div.jsoneditor-navigation-bar { } .selectr-option.active { - color: #fff; + color: #ffffff; background-color: #5897fb; } @@ -1904,7 +1775,7 @@ div.jsoneditor-navigation-bar { } .selectr-container.open .selectr-selected { - border-color: #999 #999 transparent #999; + border-color: #999999 #999999 transparent #999999; border-radius: 3px 3px 0 0; } @@ -2020,15 +1891,15 @@ div.jsoneditor-navigation-bar { } .selectr-container.open.inverted .selectr-selected { - border-color: transparent #999 #999; + border-color: transparent #999999 #999999; border-radius: 0 0 3px 3px; } .selectr-container.inverted .selectr-options-container { border-width: 1px 1px 0; - border-color: #999 #999 transparent; + border-color: #999999 #999999 transparent; border-radius: 3px 3px 0 0; - background-color: #fff; + background-color: #ffffff; } .selectr-container.inverted .selectr-options-container { diff --git a/dist/jsoneditor.js b/dist/jsoneditor.js index 1f4c9b7..5602cfd 100644 --- a/dist/jsoneditor.js +++ b/dist/jsoneditor.js @@ -128,1442 +128,1442 @@ return /******/ (function(modules) { // webpackBootstrap /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -__webpack_require__(23); -var naturalSort = __webpack_require__(24); -var jsonlint = __webpack_require__(72); -var jsonMap = __webpack_require__(73); -var translate = __webpack_require__(1).translate; - -var MAX_ITEMS_FIELDS_COLLECTION = 10000; - -/** - * Parse JSON using the parser built-in in the browser. - * On exception, the jsonString is validated and a detailed error is thrown. - * @param {String} jsonString - * @return {JSON} json - */ -exports.parse = function parse(jsonString) { - try { - return JSON.parse(jsonString); - } - catch (err) { - // try to throw a more detailed error message using validate - exports.validate(jsonString); - - // rethrow the original error - throw err; - } -}; - -/** - * Repair a JSON-like string containing. For example changes JavaScript - * notation into JSON notation. - * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" - * into '{"a": 2, "b": {"c": "d"}' - * @param {string} jsString - * @returns {string} json - */ -exports.repair = function (jsString) { - // TODO: refactor this function, it's too large and complicated now - - // escape all single and double quotes inside strings - var chars = []; - var i = 0; - - //If JSON starts with a function (characters/digits/"_-"), remove this function. - //This is useful for "stripping" JSONP objects to become JSON - //For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}] - var match = jsString.match(/^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/); - if (match) { - jsString = match[3]; - } - - var controlChars = { - '\b': '\\b', - '\f': '\\f', - '\n': '\\n', - '\r': '\\r', - '\t': '\\t' - }; - - var quote = '\''; - var quoteDbl = '"'; - var quoteLeft = '\u2018'; - var quoteRight = '\u2019'; - var quoteDblLeft = '\u201C'; - var quoteDblRight = '\u201D'; - var graveAccent = '\u0060'; - var acuteAccent = '\u00B4'; - - // helper functions to get the current/prev/next character - function curr () { return jsString.charAt(i); } - function next() { return jsString.charAt(i + 1); } - function prev() { return jsString.charAt(i - 1); } - - function isWhiteSpace(c) { - return c === ' ' || c === '\n' || c === '\r' || c === '\t'; - } - - // get the last parsed non-whitespace character - function lastNonWhitespace () { - var p = chars.length - 1; - - while (p >= 0) { - var pp = chars[p]; - if (!isWhiteSpace(pp)) { - return pp; - } - p--; - } - - return ''; - } - - // get at the first next non-white space character - function nextNonWhiteSpace() { - var iNext = i + 1; - while (iNext < jsString.length && isWhiteSpace(jsString[iNext])) { - iNext++; - } - - return jsString[iNext]; - } - - // skip a block comment '/* ... */' - function skipBlockComment () { - i += 2; - while (i < jsString.length && (curr() !== '*' || next() !== '/')) { - i++; - } - i += 2; - } - - // skip a comment '// ...' - function skipComment () { - i += 2; - while (i < jsString.length && (curr() !== '\n')) { - i++; - } - } - - /** - * parse single or double quoted string. Returns the parsed string - * @param {string} endQuote - * @return {string} - */ - function parseString(endQuote) { - var string = ''; - - string += '"'; - i++; - var c = curr(); - while (i < jsString.length && c !== endQuote) { - if (c === '"' && prev() !== '\\') { - // unescaped double quote, escape it - string += '\\"'; - } - else if (controlChars.hasOwnProperty(c)) { - // replace unescaped control characters with escaped ones - string += controlChars[c] - } - else if (c === '\\') { - // remove the escape character when followed by a single quote ', not needed - i++; - c = curr(); - if (c !== '\'') { - string += '\\'; - } - string += c; - } - else { - // regular character - string += c; - } - - i++; - c = curr(); - } - if (c === endQuote) { - string += '"'; - i++; - } - - return string; - } - - // parse an unquoted key - function parseKey() { - var specialValues = ['null', 'true', 'false']; - var key = ''; - var c = curr(); - - var regexp = /[a-zA-Z_$\d]/; // letter, number, underscore, dollar character - while (regexp.test(c)) { - key += c; - i++; - c = curr(); - } - - if (specialValues.indexOf(key) === -1) { - return '"' + key + '"'; - } - else { - return key; - } - } - - function parseMongoDataType () { - var c = curr(); - var value; - var dataType = ''; - while (/[a-zA-Z_$]/.test(c)) { - dataType += c - i++; - c = curr(); - } - - if (dataType.length > 0 && c === '(') { - // This is an MongoDB data type like {"_id": ObjectId("123")} - i++; - c = curr(); - if (c === '"') { - // a data type containing a string, like ISODate("2012-12-19T06:01:17.171Z") - value = parseString(c); - c = curr(); - } - else { - // a data type containing a value, like 'NumberLong(2)' - value = ''; - while(c !== ')' && c !== '') { - value += c; - i++; - c = curr(); - } - } - - if (c === ')') { - // skip the closing bracket at the end - i++; - - // return the value (strip the data type object) - return value; - } - else { - // huh? that's unexpected. don't touch it - return dataType + '(' + value + c; - } - } - else { - // hm, no Mongo data type after all - return dataType; - } - } - - function isSpecialWhiteSpace (c) { - return ( - c === '\u00A0' || - (c >= '\u2000' && c <= '\u200A') || - c === '\u202F' || - c === '\u205F' || - c === '\u3000') - } - - while(i < jsString.length) { - var c = curr(); - - if (c === '/' && next() === '*') { - skipBlockComment(); - } - else if (c === '/' && next() === '/') { - skipComment(); - } - else if (isSpecialWhiteSpace(c)) { - // special white spaces (like non breaking space) - chars.push(' '); - i++ - } - else if (c === quote) { - chars.push(parseString(c)); - } - else if (c === quoteDbl) { - chars.push(parseString(quoteDbl)); - } - else if (c === graveAccent) { - chars.push(parseString(acuteAccent)); - } - else if (c === quoteLeft) { - chars.push(parseString(quoteRight)); - } - else if (c === quoteDblLeft) { - chars.push(parseString(quoteDblRight)); - } - else if (c === ',' && [']', '}'].indexOf(nextNonWhiteSpace()) !== -1) { - // skip trailing commas - i++; - } - else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) { - // an unquoted object key (like a in '{a:2}') - chars.push(parseKey()); - } - else { - if (/[a-zA-Z_$]/.test(c)) { - chars.push(parseMongoDataType()); - } - else { - chars.push(c); - i++; - } - } - } - - return chars.join(''); -}; - -/** - * Escape unicode characters. - * For example input '\u2661' (length 1) will output '\\u2661' (length 5). - * @param {string} text - * @return {string} - */ -exports.escapeUnicodeChars = function (text) { - // see https://www.wikiwand.com/en/UTF-16 - // note: we leave surrogate pairs as two individual chars, - // as JSON doesn't interpret them as a single unicode char. - return text.replace(/[\u007F-\uFFFF]/g, function(c) { - return '\\u'+('0000' + c.charCodeAt(0).toString(16)).slice(-4); - }) -}; - -/** - * Validate a string containing a JSON object - * This method uses JSONLint to validate the String. If JSONLint is not - * available, the built-in JSON parser of the browser is used. - * @param {String} jsonString String with an (invalid) JSON object - * @throws Error - */ -exports.validate = function validate(jsonString) { - if (typeof(jsonlint) != 'undefined') { - jsonlint.parse(jsonString); - } - else { - JSON.parse(jsonString); - } -}; - -/** - * Extend object a with the properties of object b - * @param {Object} a - * @param {Object} b - * @return {Object} a - */ -exports.extend = function extend(a, b) { - for (var prop in b) { - if (b.hasOwnProperty(prop)) { - a[prop] = b[prop]; - } - } - return a; -}; - -/** - * Remove all properties from object a - * @param {Object} a - * @return {Object} a - */ -exports.clear = function clear (a) { - for (var prop in a) { - if (a.hasOwnProperty(prop)) { - delete a[prop]; - } - } - return a; -}; - -/** - * Get the type of an object - * @param {*} object - * @return {String} type - */ -exports.type = function type (object) { - if (object === null) { - return 'null'; - } - if (object === undefined) { - return 'undefined'; - } - if ((object instanceof Number) || (typeof object === 'number')) { - return 'number'; - } - if ((object instanceof String) || (typeof object === 'string')) { - return 'string'; - } - if ((object instanceof Boolean) || (typeof object === 'boolean')) { - return 'boolean'; - } - if ((object instanceof RegExp) || (typeof object === 'regexp')) { - return 'regexp'; - } - if (exports.isArray(object)) { - return 'array'; - } - - return 'object'; -}; - -/** - * Test whether a text contains a url (matches when a string starts - * with 'http://*' or 'https://*' and has no whitespace characters) - * @param {String} text - */ -var isUrlRegex = /^https?:\/\/\S+$/; -exports.isUrl = function isUrl (text) { - return (typeof text == 'string' || text instanceof String) && - isUrlRegex.test(text); -}; - -/** - * Tes whether given object is an Array - * @param {*} obj - * @returns {boolean} returns true when obj is an array - */ -exports.isArray = function (obj) { - return Object.prototype.toString.call(obj) === '[object Array]'; -}; - -/** - * Retrieve the absolute left value of a DOM element - * @param {Element} elem A dom element, for example a div - * @return {Number} left The absolute left position of this element - * in the browser page. - */ -exports.getAbsoluteLeft = function getAbsoluteLeft(elem) { - var rect = elem.getBoundingClientRect(); - return rect.left + window.pageXOffset || document.scrollLeft || 0; -}; - -/** - * Retrieve the absolute top value of a DOM element - * @param {Element} elem A dom element, for example a div - * @return {Number} top The absolute top position of this element - * in the browser page. - */ -exports.getAbsoluteTop = function getAbsoluteTop(elem) { - var rect = elem.getBoundingClientRect(); - return rect.top + window.pageYOffset || document.scrollTop || 0; -}; - -/** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ -exports.addClassName = function addClassName(elem, className) { - var classes = elem.className.split(' '); - if (classes.indexOf(className) == -1) { - classes.push(className); // add the class to the array - elem.className = classes.join(' '); - } -}; - -/** - * remove all classes from the given elements style - * @param {Element} elem - */ -exports.removeAllClassNames = function removeAllClassNames(elem) { - elem.className = ""; -}; - -/** - * add a className to the given elements style - * @param {Element} elem - * @param {String} className - */ -exports.removeClassName = function removeClassName(elem, className) { - var classes = elem.className.split(' '); - var index = classes.indexOf(className); - if (index != -1) { - classes.splice(index, 1); // remove the class from the array - elem.className = classes.join(' '); - } -}; - -/** - * Strip the formatting from the contents of a div - * the formatting from the div itself is not stripped, only from its childs. - * @param {Element} divElement - */ -exports.stripFormatting = function stripFormatting(divElement) { - var childs = divElement.childNodes; - for (var i = 0, iMax = childs.length; i < iMax; i++) { - var child = childs[i]; - - // remove the style - if (child.style) { - // TODO: test if child.attributes does contain style - child.removeAttribute('style'); - } - - // remove all attributes - var attributes = child.attributes; - if (attributes) { - for (var j = attributes.length - 1; j >= 0; j--) { - var attribute = attributes[j]; - if (attribute.specified === true) { - child.removeAttribute(attribute.name); - } - } - } - - // recursively strip childs - exports.stripFormatting(child); - } -}; - -/** - * Set focus to the end of an editable div - * code from Nico Burns - * http://stackoverflow.com/users/140293/nico-burns - * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity - * @param {Element} contentEditableElement A content editable div - */ -exports.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { - var range, selection; - if(document.createRange) { - range = document.createRange();//Create a range (a range is a like the selection but invisible) - range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range - range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start - selection = window.getSelection();//get the selection object (allows you to change selection) - selection.removeAllRanges();//remove any selections already made - selection.addRange(range);//make the range you have just created the visible selection - } -}; - -/** - * Select all text of a content editable div. - * http://stackoverflow.com/a/3806004/1262753 - * @param {Element} contentEditableElement A content editable div - */ -exports.selectContentEditable = function selectContentEditable(contentEditableElement) { - if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { - return; - } - - var sel, range; - if (window.getSelection && document.createRange) { - range = document.createRange(); - range.selectNodeContents(contentEditableElement); - sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } -}; - -/** - * Get text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @return {Range | TextRange | null} range - */ -exports.getSelection = function getSelection() { - if (window.getSelection) { - var sel = window.getSelection(); - if (sel.getRangeAt && sel.rangeCount) { - return sel.getRangeAt(0); - } - } - return null; -}; - -/** - * Set text selection - * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore - * @param {Range | TextRange | null} range - */ -exports.setSelection = function setSelection(range) { - if (range) { - if (window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - sel.addRange(range); - } - } -}; - -/** - * Get selected text range - * @return {Object} params object containing parameters: - * {Number} startOffset - * {Number} endOffset - * {Element} container HTML element holding the - * selected text element - * Returns null if no text selection is found - */ -exports.getSelectionOffset = function getSelectionOffset() { - var range = exports.getSelection(); - - if (range && 'startOffset' in range && 'endOffset' in range && - range.startContainer && (range.startContainer == range.endContainer)) { - return { - startOffset: range.startOffset, - endOffset: range.endOffset, - container: range.startContainer.parentNode - }; - } - - return null; -}; - -/** - * Set selected text range in given element - * @param {Object} params An object containing: - * {Element} container - * {Number} startOffset - * {Number} endOffset - */ -exports.setSelectionOffset = function setSelectionOffset(params) { - if (document.createRange && window.getSelection) { - var selection = window.getSelection(); - if(selection) { - var range = document.createRange(); - - if (!params.container.firstChild) { - params.container.appendChild(document.createTextNode('')); - } - - // TODO: do not suppose that the first child of the container is a textnode, - // but recursively find the textnodes - range.setStart(params.container.firstChild, params.startOffset); - range.setEnd(params.container.firstChild, params.endOffset); - - exports.setSelection(range); - } - } -}; - -/** - * Get the inner text of an HTML element (for example a div element) - * @param {Element} element - * @param {Object} [buffer] - * @return {String} innerText - */ -exports.getInnerText = function getInnerText(element, buffer) { - var first = (buffer == undefined); - if (first) { - buffer = { - 'text': '', - 'flush': function () { - var text = this.text; - this.text = ''; - return text; - }, - 'set': function (text) { - this.text = text; - } - }; - } - - // text node - if (element.nodeValue) { - return buffer.flush() + element.nodeValue; - } - - // divs or other HTML elements - if (element.hasChildNodes()) { - var childNodes = element.childNodes; - var innerText = ''; - - for (var i = 0, iMax = childNodes.length; i < iMax; i++) { - var child = childNodes[i]; - - if (child.nodeName == 'DIV' || child.nodeName == 'P') { - var prevChild = childNodes[i - 1]; - var prevName = prevChild ? prevChild.nodeName : undefined; - if (prevName && prevName != 'DIV' && prevName != 'P' && prevName != 'BR') { - innerText += '\n'; - buffer.flush(); - } - innerText += exports.getInnerText(child, buffer); - buffer.set('\n'); - } - else if (child.nodeName == 'BR') { - innerText += buffer.flush(); - buffer.set('\n'); - } - else { - innerText += exports.getInnerText(child, buffer); - } - } - - return innerText; - } - else { - if (element.nodeName == 'P' && exports.getInternetExplorerVersion() != -1) { - // On Internet Explorer, a

with hasChildNodes()==false is - // rendered with a new line. Note that a

with - // hasChildNodes()==true is rendered without a new line - // Other browsers always ensure there is a
inside the

, - // and if not, the

does not render a new line - return buffer.flush(); - } - } - - // br or unknown - return ''; -}; - -/** - * Test whether an element has the provided parent node somewhere up the node tree. - * @param {Element} elem - * @param {Element} parent - * @return {boolean} - */ -exports.hasParentNode = function (elem, parent) { - var e = elem ? elem.parentNode : undefined; - - while (e) { - if (e === parent) { - return true; - } - e = e.parentNode; - } - - return false; -} - -/** - * Returns the version of Internet Explorer or a -1 - * (indicating the use of another browser). - * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx - * @return {Number} Internet Explorer version, or -1 in case of an other browser - */ -exports.getInternetExplorerVersion = function getInternetExplorerVersion() { - if (_ieVersion == -1) { - var rv = -1; // Return value assumes failure. - if (typeof navigator !== 'undefined' && navigator.appName == 'Microsoft Internet Explorer') { - var ua = navigator.userAgent; - var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); - if (re.exec(ua) != null) { - rv = parseFloat( RegExp.$1 ); - } - } - - _ieVersion = rv; - } - - return _ieVersion; -}; - -/** - * Test whether the current browser is Firefox - * @returns {boolean} isFirefox - */ -exports.isFirefox = function isFirefox () { - return (typeof navigator !== 'undefined' && navigator.userAgent.indexOf("Firefox") !== -1); -}; - -/** - * cached internet explorer version - * @type {Number} - * @private - */ -var _ieVersion = -1; - -/** - * Add and event listener. Works for all browsers - * @param {Element} element An html element - * @param {string} action The action, for example "click", - * without the prefix "on" - * @param {function} listener The callback function to be executed - * @param {boolean} [useCapture] false by default - * @return {function} the created event listener - */ -exports.addEventListener = function addEventListener(element, action, listener, useCapture) { - if (element.addEventListener) { - if (useCapture === undefined) - useCapture = false; - - if (action === "mousewheel" && exports.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } - - element.addEventListener(action, listener, useCapture); - return listener; - } else if (element.attachEvent) { - // Old IE browsers - var f = function () { - return listener.call(element, window.event); - }; - element.attachEvent("on" + action, f); - return f; - } -}; - -/** - * Remove an event listener from an element - * @param {Element} element An html dom element - * @param {string} action The name of the event, for example "mousedown" - * @param {function} listener The listener function - * @param {boolean} [useCapture] false by default - */ -exports.removeEventListener = function removeEventListener(element, action, listener, useCapture) { - if (element.removeEventListener) { - if (useCapture === undefined) - useCapture = false; - - if (action === "mousewheel" && exports.isFirefox()) { - action = "DOMMouseScroll"; // For Firefox - } - - element.removeEventListener(action, listener, useCapture); - } else if (element.detachEvent) { - // Old IE browsers - element.detachEvent("on" + action, listener); - } -}; - -/** - * Test if an element is a child of a parent element. - * @param {Element} elem - * @param {Element} parent - * @return {boolean} returns true if elem is a child of the parent - */ -exports.isChildOf = function (elem, parent) { - var e = elem.parentNode; - while (e) { - if (e === parent) { - return true; - } - e = e.parentNode; - } - - return false; -}; - -/** - * Parse a JSON path like '.items[3].name' into an array - * @param {string} jsonPath - * @return {Array} - */ -exports.parsePath = function parsePath(jsonPath) { - var path = []; - var i = 0; - - function parseProperty () { - var prop = '' - while (jsonPath[i] !== undefined && /[\w$]/.test(jsonPath[i])) { - prop += jsonPath[i]; - i++; - } - - if (prop === '') { - throw new Error('Invalid JSON path: property name expected at index ' + i); - } - - return prop; - } - - function parseIndex (end) { - var name = '' - while (jsonPath[i] !== undefined && jsonPath[i] !== end) { - name += jsonPath[i]; - i++; - } - - if (jsonPath[i] !== end) { - throw new Error('Invalid JSON path: unexpected end, character ' + end + ' expected') - } - - return name; - } - - while (jsonPath[i] !== undefined) { - if (jsonPath[i] === '.') { - i++; - path.push(parseProperty()); - } - else if (jsonPath[i] === '[') { - i++; - - if (jsonPath[i] === '\'' || jsonPath[i] === '"') { - var end = jsonPath[i] - i++; - - path.push(parseIndex(end)); - - if (jsonPath[i] !== end) { - throw new Error('Invalid JSON path: closing quote \' expected at index ' + i) - } - i++; - } - else { - var index = parseIndex(']').trim() - if (index.length === 0) { - throw new Error('Invalid JSON path: array value expected at index ' + i) - } - // Coerce numeric indices to numbers, but ignore star - index = index === '*' ? index : JSON.parse(index); - path.push(index); - } - - if (jsonPath[i] !== ']') { - throw new Error('Invalid JSON path: closing bracket ] expected at index ' + i) - } - i++; - } - else { - throw new Error('Invalid JSON path: unexpected character "' + jsonPath[i] + '" at index ' + i); - } - } - - return path; -}; - -/** - * Stringify an array with a path in a JSON path like '.items[3].name' - * @param {Array.} path - * @returns {string} - */ -exports.stringifyPath = function stringifyPath(path) { - return path - .map(function (p) { - if (typeof p === 'number'){ - return ('[' + p + ']'); - } else if(typeof p === 'string' && p.match(/^[A-Za-z0-9_$]+$/)) { - return '.' + p; - } else { - return '["' + p + '"]'; - } - }) - .join(''); -}; - -/** - * Improve the error message of a JSON schema error - * @param {Object} error - * @return {Object} The error - */ -exports.improveSchemaError = function (error) { - if (error.keyword === 'enum' && Array.isArray(error.schema)) { - var enums = error.schema; - if (enums) { - enums = enums.map(function (value) { - return JSON.stringify(value); - }); - - if (enums.length > 5) { - var more = ['(' + (enums.length - 5) + ' more...)']; - enums = enums.slice(0, 5); - enums.push(more); - } - error.message = 'should be equal to one of: ' + enums.join(', '); - } - } - - if (error.keyword === 'additionalProperties') { - error.message = 'should NOT have additional property: ' + error.params.additionalProperty; - } - - return error; -}; - -/** - * Test whether something is a Promise - * @param {*} object - * @returns {boolean} Returns true when object is a promise, false otherwise - */ -exports.isPromise = function (object) { - return object && typeof object.then === 'function' && typeof object.catch === 'function'; -}; - -/** - * Test whether a custom validation error has the correct structure - * @param {*} validationError The error to be checked. - * @returns {boolean} Returns true if the structure is ok, false otherwise - */ -exports.isValidValidationError = function (validationError) { - return typeof validationError === 'object' && - Array.isArray(validationError.path) && - typeof validationError.message === 'string'; -}; - -/** - * Test whether the child rect fits completely inside the parent rect. - * @param {ClientRect} parent - * @param {ClientRect} child - * @param {number} margin - */ -exports.insideRect = function (parent, child, margin) { - var _margin = margin !== undefined ? margin : 0; - return child.left - _margin >= parent.left - && child.right + _margin <= parent.right - && child.top - _margin >= parent.top - && child.bottom + _margin <= parent.bottom; -}; - -/** - * Returns a function, that, as long as it continues to be invoked, will not - * be triggered. The function will be called after it stops being called for - * N milliseconds. - * - * Source: https://davidwalsh.name/javascript-debounce-function - * - * @param {function} func - * @param {number} wait Number in milliseconds - * @param {boolean} [immediate=false] If `immediate` is passed, trigger the - * function on the leading edge, instead - * of the trailing. - * @return {function} Return the debounced function - */ -exports.debounce = function debounce(func, wait, immediate) { - var timeout; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) func.apply(context, args); - }; -}; - -/** - * Determines the difference between two texts. - * Can only detect one removed or inserted block of characters. - * @param {string} oldText - * @param {string} newText - * @return {{start: number, end: number}} Returns the start and end - * of the changed part in newText. - */ -exports.textDiff = function textDiff(oldText, newText) { - var len = newText.length; - var start = 0; - var oldEnd = oldText.length; - var newEnd = newText.length; - - while (newText.charAt(start) === oldText.charAt(start) - && start < len) { - start++; - } - - while (newText.charAt(newEnd - 1) === oldText.charAt(oldEnd - 1) - && newEnd > start && oldEnd > 0) { - newEnd--; - oldEnd--; - } - - return {start: start, end: newEnd}; -}; - - -/** - * Return an object with the selection range or cursor position (if both have the same value) - * Support also old browsers (IE8-) - * Source: http://ourcodeworld.com/articles/read/282/how-to-get-the-current-cursor-position-and-selection-within-a-text-input-or-textarea-in-javascript - * @param {DOMElement} el A dom element of a textarea or input text. - * @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text. - **/ -exports.getInputSelection = function(el) { - var startIndex = 0, endIndex = 0, normalizedValue, range, textInputRange, len, endRange; - - if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { - startIndex = el.selectionStart; - endIndex = el.selectionEnd; - } else { - range = document.selection.createRange(); - - if (range && range.parentElement() == el) { - len = el.value.length; - normalizedValue = el.value.replace(/\r\n/g, "\n"); - - // Create a working TextRange that lives only in the input - textInputRange = el.createTextRange(); - textInputRange.moveToBookmark(range.getBookmark()); - - // Check if the startIndex and endIndex of the selection are at the very end - // of the input, since moveStart/moveEnd doesn't return what we want - // in those cases - endRange = el.createTextRange(); - endRange.collapse(false); - - if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { - startIndex = endIndex = len; - } else { - startIndex = -textInputRange.moveStart("character", -len); - startIndex += normalizedValue.slice(0, startIndex).split("\n").length - 1; - - if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { - endIndex = len; - } else { - endIndex = -textInputRange.moveEnd("character", -len); - endIndex += normalizedValue.slice(0, endIndex).split("\n").length - 1; - } - } - } - } - - return { - startIndex: startIndex, - endIndex: endIndex, - start: _positionForIndex(startIndex), - end: _positionForIndex(endIndex) - }; - - /** - * Returns textarea row and column position for certain index - * @param {Number} index text index - * @returns {{row: Number, col: Number}} - */ - function _positionForIndex(index) { - var textTillIndex = el.value.substring(0,index); - var row = (textTillIndex.match(/\n/g) || []).length + 1; - var col = textTillIndex.length - textTillIndex.lastIndexOf("\n"); - - return { - row: row, - column: col - } - } -} - -/** - * Returns the index for certaion position in text element - * @param {DOMElement} el A dom element of a textarea or input text. - * @param {Number} row row value, > 0, if exceeds rows number - last row will be returned - * @param {Number} column column value, > 0, if exceeds column length - end of column will be returned - * @returns {Number} index of position in text, -1 if not found - */ -exports.getIndexForPosition = function(el, row, column) { - var text = el.value || ''; - if (row > 0 && column > 0) { - var rows = text.split('\n', row); - row = Math.min(rows.length, row); - column = Math.min(rows[row - 1].length, column - 1); - var columnCount = (row == 1 ? column : column + 1); // count new line on multiple rows - return rows.slice(0, row - 1).join('\n').length + columnCount; - } - return -1; -} - -/** - * Returns location of json paths in certain json string - * @param {String} text json string - * @param {Array} paths array of json paths - * @returns {Array<{path: String, line: Number, row: Number}>} - */ -exports.getPositionForPath = function(text, paths) { - var me = this; - var result = []; - var jsmap; - if (!paths || !paths.length) { - return result; - } - - try { - jsmap = jsonMap.parse(text); - } catch (err) { - return result; - } - - paths.forEach(function (path) { - var pathArr = me.parsePath(path); - var pointerName = pathArr.length ? "/" + pathArr.join("/") : ""; - var pointer = jsmap.pointers[pointerName]; - if (pointer) { - result.push({ - path: path, - line: pointer.key ? pointer.key.line : (pointer.value ? pointer.value.line : 0), - column: pointer.key ? pointer.key.column : (pointer.value ? pointer.value.column : 0) - }); - } - }); - - return result; - -} - -/** - * Get the applied color given a color name or code - * Source: https://stackoverflow.com/questions/6386090/validating-css-color-names/33184805 - * @param {string} color - * @returns {string | null} returns the color if the input is a valid - * color, and returns null otherwise. Example output: - * 'rgba(255,0,0,0.7)' or 'rgb(255,0,0)' - */ -exports.getColorCSS = function (color) { - var ele = document.createElement('div'); - ele.style.color = color; - return ele.style.color.split(/\s+/).join('').toLowerCase() || null; -} - -/** - * Test if a string contains a valid color name or code. - * @param {string} color - * @returns {boolean} returns true if a valid color, false otherwise - */ -exports.isValidColor = function (color) { - return !!exports.getColorCSS(color); -} - -/** - * Make a tooltip for a field based on the field's schema. - * @param {object} schema JSON schema - * @param {string} [locale] Locale code (for example, zh-CN) - * @returns {string} Field tooltip, may be empty string if all relevant schema properties are missing - */ -exports.makeFieldTooltip = function (schema, locale) { - if (!schema) { - return ''; - } - - var tooltip = ''; - if (schema.title) { - tooltip += schema.title; - } - - if (schema.description) { - if (tooltip.length > 0) { - tooltip += '\n'; - } - tooltip += schema.description; - } - - if (schema.default) { - if (tooltip.length > 0) { - tooltip += '\n\n'; - } - tooltip += translate('default', undefined, locale) + '\n'; - tooltip += JSON.stringify(schema.default, null, 2); - } - - if (Array.isArray(schema.examples) && schema.examples.length > 0) { - if (tooltip.length > 0) { - tooltip += '\n\n'; - } - tooltip += translate('examples', undefined, locale) + '\n'; - schema.examples.forEach(function (example, index) { - tooltip += JSON.stringify(example, null, 2); - if (index !== schema.examples.length - 1) { - tooltip += '\n'; - } - }); - } - - return tooltip; -} - -/** - * Get a nested property from an object. - * Returns undefined when the property does not exist. - * @param {Object} object - * @param {string[]} path - * @return {*} - */ -exports.get = function (object, path) { - var value = object - - for (var i = 0; i < path.length && value !== undefined && value !== null; i++) { - value = value[path[i]] - } - - return value; -} - -/** - * Find a unique name. Suffix the name with ' (copy)', '(copy 2)', etc - * until a unique name is found - * @param {string} name - * @param {Array} existingPropNames Array with existing prop names - */ -exports.findUniqueName = function(name, existingPropNames) { - var strippedName = name.replace(/ \(copy( \d+)?\)$/, '') - var validName = strippedName - var i = 1 - - while (existingPropNames.indexOf(validName) !== -1) { - var copy = 'copy' + (i > 1 ? (' ' + i) : '') - validName = strippedName + ' (' + copy + ')' - i++ - } - - return validName -} - -/** - * Get the child paths of an array - * @param {JSON} json - * @param {boolean} [includeObjects=false] If true, object and array paths are returned as well - * @return {string[]} - */ -exports.getChildPaths = function (json, includeObjects) { - var pathsMap = {}; - - function getObjectChildPaths (json, pathsMap, rootPath, includeObjects) { - var isValue = !Array.isArray(json) && !exports.isObject(json) - - if (isValue || includeObjects) { - pathsMap[rootPath || ''] = true; - } - - if (exports.isObject(json)) { - Object.keys(json).forEach(function (field) { - getObjectChildPaths(json[field], pathsMap, rootPath + '.' + field, includeObjects); - }); - } - } - - if (Array.isArray(json)) { - var max = Math.min(json.length, MAX_ITEMS_FIELDS_COLLECTION); - for (var i = 0; i < max; i++) { - var item = json[i]; - getObjectChildPaths(item, pathsMap, '', includeObjects); - } - } - else { - pathsMap[''] = true; - } - - return Object.keys(pathsMap).sort(); -} - -/** - * Sort object keys using natural sort - * @param {Array} array - * @param {String} [path] JSON pointer - * @param {'asc' | 'desc'} [direction] - */ -exports.sort = function (array, path, direction) { - var parsedPath = path && path !== '.' ? exports.parsePath(path) : [] - var sign = direction === 'desc' ? -1: 1 - - var sortedArray = array.slice() - sortedArray.sort(function (a, b) { - var aValue = exports.get(a, parsedPath); - var bValue = exports.get(b, parsedPath); - - return sign * (aValue > bValue ? 1 : aValue < bValue ? -1 : 0); - }) - - return sortedArray; -} - -/** - * Sort object keys using natural sort - * @param {Object} object - * @param {'asc' | 'desc'} [direction] - */ -exports.sortObjectKeys = function (object, direction) { - var sign = (direction === 'desc') ? -1 : 1; - var sortedFields = Object.keys(object).sort(function (a, b) { - return sign * naturalSort(a, b); - }); - - var sortedObject = {}; - sortedFields.forEach(function (field) { - sortedObject[field] = object[field]; - }); - - return sortedObject; -} - -/** - * Cast contents of a string to the correct type. - * This can be a string, a number, a boolean, etc - * @param {String} str - * @return {*} castedStr - * @private - */ -exports.parseString = function(str) { - var lower = str.toLowerCase(); - var num = Number(str); // will nicely fail with '123ab' - var numFloat = parseFloat(str); // will nicely fail with ' ' - - if (str === '') { - return ''; - } - else if (lower === 'null') { - return null; - } - else if (lower === 'true') { - return true; - } - else if (lower === 'false') { - return false; - } - else if (!isNaN(num) && !isNaN(numFloat)) { - return num; - } - else { - return str; - } -}; - -/** - * Return a human readable document size - * For example formatSize(7570718) outputs '7.2 MiB' - * @param {number} size - * @return {string} Returns a human readable size - */ -exports.formatSize = function (size) { - if (size < 900) { - return size.toFixed() + ' B'; - } - - var KiB = size / 1024; - if (KiB < 900) { - return KiB.toFixed(1) + ' KiB'; - } - - var MiB = KiB / 1024; - if (MiB < 900) { - return MiB.toFixed(1) + ' MiB'; - } - - var GiB = MiB / 1024; - if (GiB < 900) { - return GiB.toFixed(1) + ' GiB'; - } - - var TiB = GiB / 1024; - return TiB.toFixed(1) + ' TiB'; -} - -/** - * Limit text to a maximum number of characters - * @param {string} text - * @param {number} maxCharacterCount - * @return {string} Returns the limited text, - * ending with '...' if the max was exceeded - */ -exports.limitCharacters = function (text, maxCharacterCount) { - if (text.length <= maxCharacterCount) { - return text; - } - - return text.slice(0, maxCharacterCount) + '...'; -} - -/** - * Test whether a value is an Object - * @param {*} value - * @return {boolean} - */ -exports.isObject = function (value) { - return typeof value === 'object' && value !== null && !Array.isArray(value) -} - -/** - * Helper function to test whether an array contains an item - * @param {Array} array - * @param {*} item - * @return {boolean} Returns true if `item` is in `array`, returns false otherwise. - */ -exports.contains = function (array, item) { - return array.indexOf(item) !== -1; -} + + +__webpack_require__(23); +var naturalSort = __webpack_require__(24); +var jsonlint = __webpack_require__(72); +var jsonMap = __webpack_require__(73); +var translate = __webpack_require__(1).translate; + +var MAX_ITEMS_FIELDS_COLLECTION = 10000; + +/** + * Parse JSON using the parser built-in in the browser. + * On exception, the jsonString is validated and a detailed error is thrown. + * @param {String} jsonString + * @return {JSON} json + */ +exports.parse = function parse(jsonString) { + try { + return JSON.parse(jsonString); + } + catch (err) { + // try to throw a more detailed error message using validate + exports.validate(jsonString); + + // rethrow the original error + throw err; + } +}; + +/** + * Repair a JSON-like string containing. For example changes JavaScript + * notation into JSON notation. + * This function for example changes a string like "{a: 2, 'b': {c: 'd'}" + * into '{"a": 2, "b": {"c": "d"}' + * @param {string} jsString + * @returns {string} json + */ +exports.repair = function (jsString) { + // TODO: refactor this function, it's too large and complicated now + + // escape all single and double quotes inside strings + var chars = []; + var i = 0; + + //If JSON starts with a function (characters/digits/"_-"), remove this function. + //This is useful for "stripping" JSONP objects to become JSON + //For example: /* some comment */ function_12321321 ( [{"a":"b"}] ); => [{"a":"b"}] + var match = jsString.match(/^\s*(\/\*(.|[\r\n])*?\*\/)?\s*[\da-zA-Z_$]+\s*\(([\s\S]*)\)\s*;?\s*$/); + if (match) { + jsString = match[3]; + } + + var controlChars = { + '\b': '\\b', + '\f': '\\f', + '\n': '\\n', + '\r': '\\r', + '\t': '\\t' + }; + + var quote = '\''; + var quoteDbl = '"'; + var quoteLeft = '\u2018'; + var quoteRight = '\u2019'; + var quoteDblLeft = '\u201C'; + var quoteDblRight = '\u201D'; + var graveAccent = '\u0060'; + var acuteAccent = '\u00B4'; + + // helper functions to get the current/prev/next character + function curr () { return jsString.charAt(i); } + function next() { return jsString.charAt(i + 1); } + function prev() { return jsString.charAt(i - 1); } + + function isWhiteSpace(c) { + return c === ' ' || c === '\n' || c === '\r' || c === '\t'; + } + + // get the last parsed non-whitespace character + function lastNonWhitespace () { + var p = chars.length - 1; + + while (p >= 0) { + var pp = chars[p]; + if (!isWhiteSpace(pp)) { + return pp; + } + p--; + } + + return ''; + } + + // get at the first next non-white space character + function nextNonWhiteSpace() { + var iNext = i + 1; + while (iNext < jsString.length && isWhiteSpace(jsString[iNext])) { + iNext++; + } + + return jsString[iNext]; + } + + // skip a block comment '/* ... */' + function skipBlockComment () { + i += 2; + while (i < jsString.length && (curr() !== '*' || next() !== '/')) { + i++; + } + i += 2; + } + + // skip a comment '// ...' + function skipComment () { + i += 2; + while (i < jsString.length && (curr() !== '\n')) { + i++; + } + } + + /** + * parse single or double quoted string. Returns the parsed string + * @param {string} endQuote + * @return {string} + */ + function parseString(endQuote) { + var string = ''; + + string += '"'; + i++; + var c = curr(); + while (i < jsString.length && c !== endQuote) { + if (c === '"' && prev() !== '\\') { + // unescaped double quote, escape it + string += '\\"'; + } + else if (controlChars.hasOwnProperty(c)) { + // replace unescaped control characters with escaped ones + string += controlChars[c] + } + else if (c === '\\') { + // remove the escape character when followed by a single quote ', not needed + i++; + c = curr(); + if (c !== '\'') { + string += '\\'; + } + string += c; + } + else { + // regular character + string += c; + } + + i++; + c = curr(); + } + if (c === endQuote) { + string += '"'; + i++; + } + + return string; + } + + // parse an unquoted key + function parseKey() { + var specialValues = ['null', 'true', 'false']; + var key = ''; + var c = curr(); + + var regexp = /[a-zA-Z_$\d]/; // letter, number, underscore, dollar character + while (regexp.test(c)) { + key += c; + i++; + c = curr(); + } + + if (specialValues.indexOf(key) === -1) { + return '"' + key + '"'; + } + else { + return key; + } + } + + function parseMongoDataType () { + var c = curr(); + var value; + var dataType = ''; + while (/[a-zA-Z_$]/.test(c)) { + dataType += c + i++; + c = curr(); + } + + if (dataType.length > 0 && c === '(') { + // This is an MongoDB data type like {"_id": ObjectId("123")} + i++; + c = curr(); + if (c === '"') { + // a data type containing a string, like ISODate("2012-12-19T06:01:17.171Z") + value = parseString(c); + c = curr(); + } + else { + // a data type containing a value, like 'NumberLong(2)' + value = ''; + while(c !== ')' && c !== '') { + value += c; + i++; + c = curr(); + } + } + + if (c === ')') { + // skip the closing bracket at the end + i++; + + // return the value (strip the data type object) + return value; + } + else { + // huh? that's unexpected. don't touch it + return dataType + '(' + value + c; + } + } + else { + // hm, no Mongo data type after all + return dataType; + } + } + + function isSpecialWhiteSpace (c) { + return ( + c === '\u00A0' || + (c >= '\u2000' && c <= '\u200A') || + c === '\u202F' || + c === '\u205F' || + c === '\u3000') + } + + while(i < jsString.length) { + var c = curr(); + + if (c === '/' && next() === '*') { + skipBlockComment(); + } + else if (c === '/' && next() === '/') { + skipComment(); + } + else if (isSpecialWhiteSpace(c)) { + // special white spaces (like non breaking space) + chars.push(' '); + i++ + } + else if (c === quote) { + chars.push(parseString(c)); + } + else if (c === quoteDbl) { + chars.push(parseString(quoteDbl)); + } + else if (c === graveAccent) { + chars.push(parseString(acuteAccent)); + } + else if (c === quoteLeft) { + chars.push(parseString(quoteRight)); + } + else if (c === quoteDblLeft) { + chars.push(parseString(quoteDblRight)); + } + else if (c === ',' && [']', '}'].indexOf(nextNonWhiteSpace()) !== -1) { + // skip trailing commas + i++; + } + else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) { + // an unquoted object key (like a in '{a:2}') + chars.push(parseKey()); + } + else { + if (/[a-zA-Z_$]/.test(c)) { + chars.push(parseMongoDataType()); + } + else { + chars.push(c); + i++; + } + } + } + + return chars.join(''); +}; + +/** + * Escape unicode characters. + * For example input '\u2661' (length 1) will output '\\u2661' (length 5). + * @param {string} text + * @return {string} + */ +exports.escapeUnicodeChars = function (text) { + // see https://www.wikiwand.com/en/UTF-16 + // note: we leave surrogate pairs as two individual chars, + // as JSON doesn't interpret them as a single unicode char. + return text.replace(/[\u007F-\uFFFF]/g, function(c) { + return '\\u'+('0000' + c.charCodeAt(0).toString(16)).slice(-4); + }) +}; + +/** + * Validate a string containing a JSON object + * This method uses JSONLint to validate the String. If JSONLint is not + * available, the built-in JSON parser of the browser is used. + * @param {String} jsonString String with an (invalid) JSON object + * @throws Error + */ +exports.validate = function validate(jsonString) { + if (typeof(jsonlint) != 'undefined') { + jsonlint.parse(jsonString); + } + else { + JSON.parse(jsonString); + } +}; + +/** + * Extend object a with the properties of object b + * @param {Object} a + * @param {Object} b + * @return {Object} a + */ +exports.extend = function extend(a, b) { + for (var prop in b) { + if (b.hasOwnProperty(prop)) { + a[prop] = b[prop]; + } + } + return a; +}; + +/** + * Remove all properties from object a + * @param {Object} a + * @return {Object} a + */ +exports.clear = function clear (a) { + for (var prop in a) { + if (a.hasOwnProperty(prop)) { + delete a[prop]; + } + } + return a; +}; + +/** + * Get the type of an object + * @param {*} object + * @return {String} type + */ +exports.type = function type (object) { + if (object === null) { + return 'null'; + } + if (object === undefined) { + return 'undefined'; + } + if ((object instanceof Number) || (typeof object === 'number')) { + return 'number'; + } + if ((object instanceof String) || (typeof object === 'string')) { + return 'string'; + } + if ((object instanceof Boolean) || (typeof object === 'boolean')) { + return 'boolean'; + } + if ((object instanceof RegExp) || (typeof object === 'regexp')) { + return 'regexp'; + } + if (exports.isArray(object)) { + return 'array'; + } + + return 'object'; +}; + +/** + * Test whether a text contains a url (matches when a string starts + * with 'http://*' or 'https://*' and has no whitespace characters) + * @param {String} text + */ +var isUrlRegex = /^https?:\/\/\S+$/; +exports.isUrl = function isUrl (text) { + return (typeof text == 'string' || text instanceof String) && + isUrlRegex.test(text); +}; + +/** + * Tes whether given object is an Array + * @param {*} obj + * @returns {boolean} returns true when obj is an array + */ +exports.isArray = function (obj) { + return Object.prototype.toString.call(obj) === '[object Array]'; +}; + +/** + * Retrieve the absolute left value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {Number} left The absolute left position of this element + * in the browser page. + */ +exports.getAbsoluteLeft = function getAbsoluteLeft(elem) { + var rect = elem.getBoundingClientRect(); + return rect.left + window.pageXOffset || document.scrollLeft || 0; +}; + +/** + * Retrieve the absolute top value of a DOM element + * @param {Element} elem A dom element, for example a div + * @return {Number} top The absolute top position of this element + * in the browser page. + */ +exports.getAbsoluteTop = function getAbsoluteTop(elem) { + var rect = elem.getBoundingClientRect(); + return rect.top + window.pageYOffset || document.scrollTop || 0; +}; + +/** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ +exports.addClassName = function addClassName(elem, className) { + var classes = elem.className.split(' '); + if (classes.indexOf(className) == -1) { + classes.push(className); // add the class to the array + elem.className = classes.join(' '); + } +}; + +/** + * remove all classes from the given elements style + * @param {Element} elem + */ +exports.removeAllClassNames = function removeAllClassNames(elem) { + elem.className = ""; +}; + +/** + * add a className to the given elements style + * @param {Element} elem + * @param {String} className + */ +exports.removeClassName = function removeClassName(elem, className) { + var classes = elem.className.split(' '); + var index = classes.indexOf(className); + if (index != -1) { + classes.splice(index, 1); // remove the class from the array + elem.className = classes.join(' '); + } +}; + +/** + * Strip the formatting from the contents of a div + * the formatting from the div itself is not stripped, only from its childs. + * @param {Element} divElement + */ +exports.stripFormatting = function stripFormatting(divElement) { + var childs = divElement.childNodes; + for (var i = 0, iMax = childs.length; i < iMax; i++) { + var child = childs[i]; + + // remove the style + if (child.style) { + // TODO: test if child.attributes does contain style + child.removeAttribute('style'); + } + + // remove all attributes + var attributes = child.attributes; + if (attributes) { + for (var j = attributes.length - 1; j >= 0; j--) { + var attribute = attributes[j]; + if (attribute.specified === true) { + child.removeAttribute(attribute.name); + } + } + } + + // recursively strip childs + exports.stripFormatting(child); + } +}; + +/** + * Set focus to the end of an editable div + * code from Nico Burns + * http://stackoverflow.com/users/140293/nico-burns + * http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity + * @param {Element} contentEditableElement A content editable div + */ +exports.setEndOfContentEditable = function setEndOfContentEditable(contentEditableElement) { + var range, selection; + if(document.createRange) { + range = document.createRange();//Create a range (a range is a like the selection but invisible) + range.selectNodeContents(contentEditableElement);//Select the entire contents of the element with the range + range.collapse(false);//collapse the range to the end point. false means collapse to end rather than the start + selection = window.getSelection();//get the selection object (allows you to change selection) + selection.removeAllRanges();//remove any selections already made + selection.addRange(range);//make the range you have just created the visible selection + } +}; + +/** + * Select all text of a content editable div. + * http://stackoverflow.com/a/3806004/1262753 + * @param {Element} contentEditableElement A content editable div + */ +exports.selectContentEditable = function selectContentEditable(contentEditableElement) { + if (!contentEditableElement || contentEditableElement.nodeName != 'DIV') { + return; + } + + var sel, range; + if (window.getSelection && document.createRange) { + range = document.createRange(); + range.selectNodeContents(contentEditableElement); + sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } +}; + +/** + * Get text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @return {Range | TextRange | null} range + */ +exports.getSelection = function getSelection() { + if (window.getSelection) { + var sel = window.getSelection(); + if (sel.getRangeAt && sel.rangeCount) { + return sel.getRangeAt(0); + } + } + return null; +}; + +/** + * Set text selection + * http://stackoverflow.com/questions/4687808/contenteditable-selected-text-save-and-restore + * @param {Range | TextRange | null} range + */ +exports.setSelection = function setSelection(range) { + if (range) { + if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + sel.addRange(range); + } + } +}; + +/** + * Get selected text range + * @return {Object} params object containing parameters: + * {Number} startOffset + * {Number} endOffset + * {Element} container HTML element holding the + * selected text element + * Returns null if no text selection is found + */ +exports.getSelectionOffset = function getSelectionOffset() { + var range = exports.getSelection(); + + if (range && 'startOffset' in range && 'endOffset' in range && + range.startContainer && (range.startContainer == range.endContainer)) { + return { + startOffset: range.startOffset, + endOffset: range.endOffset, + container: range.startContainer.parentNode + }; + } + + return null; +}; + +/** + * Set selected text range in given element + * @param {Object} params An object containing: + * {Element} container + * {Number} startOffset + * {Number} endOffset + */ +exports.setSelectionOffset = function setSelectionOffset(params) { + if (document.createRange && window.getSelection) { + var selection = window.getSelection(); + if(selection) { + var range = document.createRange(); + + if (!params.container.firstChild) { + params.container.appendChild(document.createTextNode('')); + } + + // TODO: do not suppose that the first child of the container is a textnode, + // but recursively find the textnodes + range.setStart(params.container.firstChild, params.startOffset); + range.setEnd(params.container.firstChild, params.endOffset); + + exports.setSelection(range); + } + } +}; + +/** + * Get the inner text of an HTML element (for example a div element) + * @param {Element} element + * @param {Object} [buffer] + * @return {String} innerText + */ +exports.getInnerText = function getInnerText(element, buffer) { + var first = (buffer == undefined); + if (first) { + buffer = { + 'text': '', + 'flush': function () { + var text = this.text; + this.text = ''; + return text; + }, + 'set': function (text) { + this.text = text; + } + }; + } + + // text node + if (element.nodeValue) { + return buffer.flush() + element.nodeValue; + } + + // divs or other HTML elements + if (element.hasChildNodes()) { + var childNodes = element.childNodes; + var innerText = ''; + + for (var i = 0, iMax = childNodes.length; i < iMax; i++) { + var child = childNodes[i]; + + if (child.nodeName == 'DIV' || child.nodeName == 'P') { + var prevChild = childNodes[i - 1]; + var prevName = prevChild ? prevChild.nodeName : undefined; + if (prevName && prevName != 'DIV' && prevName != 'P' && prevName != 'BR') { + innerText += '\n'; + buffer.flush(); + } + innerText += exports.getInnerText(child, buffer); + buffer.set('\n'); + } + else if (child.nodeName == 'BR') { + innerText += buffer.flush(); + buffer.set('\n'); + } + else { + innerText += exports.getInnerText(child, buffer); + } + } + + return innerText; + } + else { + if (element.nodeName == 'P' && exports.getInternetExplorerVersion() != -1) { + // On Internet Explorer, a

with hasChildNodes()==false is + // rendered with a new line. Note that a

with + // hasChildNodes()==true is rendered without a new line + // Other browsers always ensure there is a
inside the

, + // and if not, the

does not render a new line + return buffer.flush(); + } + } + + // br or unknown + return ''; +}; + +/** + * Test whether an element has the provided parent node somewhere up the node tree. + * @param {Element} elem + * @param {Element} parent + * @return {boolean} + */ +exports.hasParentNode = function (elem, parent) { + var e = elem ? elem.parentNode : undefined; + + while (e) { + if (e === parent) { + return true; + } + e = e.parentNode; + } + + return false; +} + +/** + * Returns the version of Internet Explorer or a -1 + * (indicating the use of another browser). + * Source: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx + * @return {Number} Internet Explorer version, or -1 in case of an other browser + */ +exports.getInternetExplorerVersion = function getInternetExplorerVersion() { + if (_ieVersion == -1) { + var rv = -1; // Return value assumes failure. + if (typeof navigator !== 'undefined' && navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) { + rv = parseFloat( RegExp.$1 ); + } + } + + _ieVersion = rv; + } + + return _ieVersion; +}; + +/** + * Test whether the current browser is Firefox + * @returns {boolean} isFirefox + */ +exports.isFirefox = function isFirefox () { + return (typeof navigator !== 'undefined' && navigator.userAgent.indexOf("Firefox") !== -1); +}; + +/** + * cached internet explorer version + * @type {Number} + * @private + */ +var _ieVersion = -1; + +/** + * Add and event listener. Works for all browsers + * @param {Element} element An html element + * @param {string} action The action, for example "click", + * without the prefix "on" + * @param {function} listener The callback function to be executed + * @param {boolean} [useCapture] false by default + * @return {function} the created event listener + */ +exports.addEventListener = function addEventListener(element, action, listener, useCapture) { + if (element.addEventListener) { + if (useCapture === undefined) + useCapture = false; + + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox + } + + element.addEventListener(action, listener, useCapture); + return listener; + } else if (element.attachEvent) { + // Old IE browsers + var f = function () { + return listener.call(element, window.event); + }; + element.attachEvent("on" + action, f); + return f; + } +}; + +/** + * Remove an event listener from an element + * @param {Element} element An html dom element + * @param {string} action The name of the event, for example "mousedown" + * @param {function} listener The listener function + * @param {boolean} [useCapture] false by default + */ +exports.removeEventListener = function removeEventListener(element, action, listener, useCapture) { + if (element.removeEventListener) { + if (useCapture === undefined) + useCapture = false; + + if (action === "mousewheel" && exports.isFirefox()) { + action = "DOMMouseScroll"; // For Firefox + } + + element.removeEventListener(action, listener, useCapture); + } else if (element.detachEvent) { + // Old IE browsers + element.detachEvent("on" + action, listener); + } +}; + +/** + * Test if an element is a child of a parent element. + * @param {Element} elem + * @param {Element} parent + * @return {boolean} returns true if elem is a child of the parent + */ +exports.isChildOf = function (elem, parent) { + var e = elem.parentNode; + while (e) { + if (e === parent) { + return true; + } + e = e.parentNode; + } + + return false; +}; + +/** + * Parse a JSON path like '.items[3].name' into an array + * @param {string} jsonPath + * @return {Array} + */ +exports.parsePath = function parsePath(jsonPath) { + var path = []; + var i = 0; + + function parseProperty () { + var prop = '' + while (jsonPath[i] !== undefined && /[\w$]/.test(jsonPath[i])) { + prop += jsonPath[i]; + i++; + } + + if (prop === '') { + throw new Error('Invalid JSON path: property name expected at index ' + i); + } + + return prop; + } + + function parseIndex (end) { + var name = '' + while (jsonPath[i] !== undefined && jsonPath[i] !== end) { + name += jsonPath[i]; + i++; + } + + if (jsonPath[i] !== end) { + throw new Error('Invalid JSON path: unexpected end, character ' + end + ' expected') + } + + return name; + } + + while (jsonPath[i] !== undefined) { + if (jsonPath[i] === '.') { + i++; + path.push(parseProperty()); + } + else if (jsonPath[i] === '[') { + i++; + + if (jsonPath[i] === '\'' || jsonPath[i] === '"') { + var end = jsonPath[i] + i++; + + path.push(parseIndex(end)); + + if (jsonPath[i] !== end) { + throw new Error('Invalid JSON path: closing quote \' expected at index ' + i) + } + i++; + } + else { + var index = parseIndex(']').trim() + if (index.length === 0) { + throw new Error('Invalid JSON path: array value expected at index ' + i) + } + // Coerce numeric indices to numbers, but ignore star + index = index === '*' ? index : JSON.parse(index); + path.push(index); + } + + if (jsonPath[i] !== ']') { + throw new Error('Invalid JSON path: closing bracket ] expected at index ' + i) + } + i++; + } + else { + throw new Error('Invalid JSON path: unexpected character "' + jsonPath[i] + '" at index ' + i); + } + } + + return path; +}; + +/** + * Stringify an array with a path in a JSON path like '.items[3].name' + * @param {Array.} path + * @returns {string} + */ +exports.stringifyPath = function stringifyPath(path) { + return path + .map(function (p) { + if (typeof p === 'number'){ + return ('[' + p + ']'); + } else if(typeof p === 'string' && p.match(/^[A-Za-z0-9_$]+$/)) { + return '.' + p; + } else { + return '["' + p + '"]'; + } + }) + .join(''); +}; + +/** + * Improve the error message of a JSON schema error + * @param {Object} error + * @return {Object} The error + */ +exports.improveSchemaError = function (error) { + if (error.keyword === 'enum' && Array.isArray(error.schema)) { + var enums = error.schema; + if (enums) { + enums = enums.map(function (value) { + return JSON.stringify(value); + }); + + if (enums.length > 5) { + var more = ['(' + (enums.length - 5) + ' more...)']; + enums = enums.slice(0, 5); + enums.push(more); + } + error.message = 'should be equal to one of: ' + enums.join(', '); + } + } + + if (error.keyword === 'additionalProperties') { + error.message = 'should NOT have additional property: ' + error.params.additionalProperty; + } + + return error; +}; + +/** + * Test whether something is a Promise + * @param {*} object + * @returns {boolean} Returns true when object is a promise, false otherwise + */ +exports.isPromise = function (object) { + return object && typeof object.then === 'function' && typeof object.catch === 'function'; +}; + +/** + * Test whether a custom validation error has the correct structure + * @param {*} validationError The error to be checked. + * @returns {boolean} Returns true if the structure is ok, false otherwise + */ +exports.isValidValidationError = function (validationError) { + return typeof validationError === 'object' && + Array.isArray(validationError.path) && + typeof validationError.message === 'string'; +}; + +/** + * Test whether the child rect fits completely inside the parent rect. + * @param {ClientRect} parent + * @param {ClientRect} child + * @param {number} margin + */ +exports.insideRect = function (parent, child, margin) { + var _margin = margin !== undefined ? margin : 0; + return child.left - _margin >= parent.left + && child.right + _margin <= parent.right + && child.top - _margin >= parent.top + && child.bottom + _margin <= parent.bottom; +}; + +/** + * Returns a function, that, as long as it continues to be invoked, will not + * be triggered. The function will be called after it stops being called for + * N milliseconds. + * + * Source: https://davidwalsh.name/javascript-debounce-function + * + * @param {function} func + * @param {number} wait Number in milliseconds + * @param {boolean} [immediate=false] If `immediate` is passed, trigger the + * function on the leading edge, instead + * of the trailing. + * @return {function} Return the debounced function + */ +exports.debounce = function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; + +/** + * Determines the difference between two texts. + * Can only detect one removed or inserted block of characters. + * @param {string} oldText + * @param {string} newText + * @return {{start: number, end: number}} Returns the start and end + * of the changed part in newText. + */ +exports.textDiff = function textDiff(oldText, newText) { + var len = newText.length; + var start = 0; + var oldEnd = oldText.length; + var newEnd = newText.length; + + while (newText.charAt(start) === oldText.charAt(start) + && start < len) { + start++; + } + + while (newText.charAt(newEnd - 1) === oldText.charAt(oldEnd - 1) + && newEnd > start && oldEnd > 0) { + newEnd--; + oldEnd--; + } + + return {start: start, end: newEnd}; +}; + + +/** + * Return an object with the selection range or cursor position (if both have the same value) + * Support also old browsers (IE8-) + * Source: http://ourcodeworld.com/articles/read/282/how-to-get-the-current-cursor-position-and-selection-within-a-text-input-or-textarea-in-javascript + * @param {DOMElement} el A dom element of a textarea or input text. + * @return {Object} reference Object with 2 properties (start and end) with the identifier of the location of the cursor and selected text. + **/ +exports.getInputSelection = function(el) { + var startIndex = 0, endIndex = 0, normalizedValue, range, textInputRange, len, endRange; + + if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") { + startIndex = el.selectionStart; + endIndex = el.selectionEnd; + } else { + range = document.selection.createRange(); + + if (range && range.parentElement() == el) { + len = el.value.length; + normalizedValue = el.value.replace(/\r\n/g, "\n"); + + // Create a working TextRange that lives only in the input + textInputRange = el.createTextRange(); + textInputRange.moveToBookmark(range.getBookmark()); + + // Check if the startIndex and endIndex of the selection are at the very end + // of the input, since moveStart/moveEnd doesn't return what we want + // in those cases + endRange = el.createTextRange(); + endRange.collapse(false); + + if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) { + startIndex = endIndex = len; + } else { + startIndex = -textInputRange.moveStart("character", -len); + startIndex += normalizedValue.slice(0, startIndex).split("\n").length - 1; + + if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) { + endIndex = len; + } else { + endIndex = -textInputRange.moveEnd("character", -len); + endIndex += normalizedValue.slice(0, endIndex).split("\n").length - 1; + } + } + } + } + + return { + startIndex: startIndex, + endIndex: endIndex, + start: _positionForIndex(startIndex), + end: _positionForIndex(endIndex) + }; + + /** + * Returns textarea row and column position for certain index + * @param {Number} index text index + * @returns {{row: Number, col: Number}} + */ + function _positionForIndex(index) { + var textTillIndex = el.value.substring(0,index); + var row = (textTillIndex.match(/\n/g) || []).length + 1; + var col = textTillIndex.length - textTillIndex.lastIndexOf("\n"); + + return { + row: row, + column: col + } + } +} + +/** + * Returns the index for certaion position in text element + * @param {DOMElement} el A dom element of a textarea or input text. + * @param {Number} row row value, > 0, if exceeds rows number - last row will be returned + * @param {Number} column column value, > 0, if exceeds column length - end of column will be returned + * @returns {Number} index of position in text, -1 if not found + */ +exports.getIndexForPosition = function(el, row, column) { + var text = el.value || ''; + if (row > 0 && column > 0) { + var rows = text.split('\n', row); + row = Math.min(rows.length, row); + column = Math.min(rows[row - 1].length, column - 1); + var columnCount = (row == 1 ? column : column + 1); // count new line on multiple rows + return rows.slice(0, row - 1).join('\n').length + columnCount; + } + return -1; +} + +/** + * Returns location of json paths in certain json string + * @param {String} text json string + * @param {Array} paths array of json paths + * @returns {Array<{path: String, line: Number, row: Number}>} + */ +exports.getPositionForPath = function(text, paths) { + var me = this; + var result = []; + var jsmap; + if (!paths || !paths.length) { + return result; + } + + try { + jsmap = jsonMap.parse(text); + } catch (err) { + return result; + } + + paths.forEach(function (path) { + var pathArr = me.parsePath(path); + var pointerName = pathArr.length ? "/" + pathArr.join("/") : ""; + var pointer = jsmap.pointers[pointerName]; + if (pointer) { + result.push({ + path: path, + line: pointer.key ? pointer.key.line : (pointer.value ? pointer.value.line : 0), + column: pointer.key ? pointer.key.column : (pointer.value ? pointer.value.column : 0) + }); + } + }); + + return result; + +} + +/** + * Get the applied color given a color name or code + * Source: https://stackoverflow.com/questions/6386090/validating-css-color-names/33184805 + * @param {string} color + * @returns {string | null} returns the color if the input is a valid + * color, and returns null otherwise. Example output: + * 'rgba(255,0,0,0.7)' or 'rgb(255,0,0)' + */ +exports.getColorCSS = function (color) { + var ele = document.createElement('div'); + ele.style.color = color; + return ele.style.color.split(/\s+/).join('').toLowerCase() || null; +} + +/** + * Test if a string contains a valid color name or code. + * @param {string} color + * @returns {boolean} returns true if a valid color, false otherwise + */ +exports.isValidColor = function (color) { + return !!exports.getColorCSS(color); +} + +/** + * Make a tooltip for a field based on the field's schema. + * @param {object} schema JSON schema + * @param {string} [locale] Locale code (for example, zh-CN) + * @returns {string} Field tooltip, may be empty string if all relevant schema properties are missing + */ +exports.makeFieldTooltip = function (schema, locale) { + if (!schema) { + return ''; + } + + var tooltip = ''; + if (schema.title) { + tooltip += schema.title; + } + + if (schema.description) { + if (tooltip.length > 0) { + tooltip += '\n'; + } + tooltip += schema.description; + } + + if (schema.default) { + if (tooltip.length > 0) { + tooltip += '\n\n'; + } + tooltip += translate('default', undefined, locale) + '\n'; + tooltip += JSON.stringify(schema.default, null, 2); + } + + if (Array.isArray(schema.examples) && schema.examples.length > 0) { + if (tooltip.length > 0) { + tooltip += '\n\n'; + } + tooltip += translate('examples', undefined, locale) + '\n'; + schema.examples.forEach(function (example, index) { + tooltip += JSON.stringify(example, null, 2); + if (index !== schema.examples.length - 1) { + tooltip += '\n'; + } + }); + } + + return tooltip; +} + +/** + * Get a nested property from an object. + * Returns undefined when the property does not exist. + * @param {Object} object + * @param {string[]} path + * @return {*} + */ +exports.get = function (object, path) { + var value = object + + for (var i = 0; i < path.length && value !== undefined && value !== null; i++) { + value = value[path[i]] + } + + return value; +} + +/** + * Find a unique name. Suffix the name with ' (copy)', '(copy 2)', etc + * until a unique name is found + * @param {string} name + * @param {Array} existingPropNames Array with existing prop names + */ +exports.findUniqueName = function(name, existingPropNames) { + var strippedName = name.replace(/ \(copy( \d+)?\)$/, '') + var validName = strippedName + var i = 1 + + while (existingPropNames.indexOf(validName) !== -1) { + var copy = 'copy' + (i > 1 ? (' ' + i) : '') + validName = strippedName + ' (' + copy + ')' + i++ + } + + return validName +} + +/** + * Get the child paths of an array + * @param {JSON} json + * @param {boolean} [includeObjects=false] If true, object and array paths are returned as well + * @return {string[]} + */ +exports.getChildPaths = function (json, includeObjects) { + var pathsMap = {}; + + function getObjectChildPaths (json, pathsMap, rootPath, includeObjects) { + var isValue = !Array.isArray(json) && !exports.isObject(json) + + if (isValue || includeObjects) { + pathsMap[rootPath || ''] = true; + } + + if (exports.isObject(json)) { + Object.keys(json).forEach(function (field) { + getObjectChildPaths(json[field], pathsMap, rootPath + '.' + field, includeObjects); + }); + } + } + + if (Array.isArray(json)) { + var max = Math.min(json.length, MAX_ITEMS_FIELDS_COLLECTION); + for (var i = 0; i < max; i++) { + var item = json[i]; + getObjectChildPaths(item, pathsMap, '', includeObjects); + } + } + else { + pathsMap[''] = true; + } + + return Object.keys(pathsMap).sort(); +} + +/** + * Sort object keys using natural sort + * @param {Array} array + * @param {String} [path] JSON pointer + * @param {'asc' | 'desc'} [direction] + */ +exports.sort = function (array, path, direction) { + var parsedPath = path && path !== '.' ? exports.parsePath(path) : [] + var sign = direction === 'desc' ? -1: 1 + + var sortedArray = array.slice() + sortedArray.sort(function (a, b) { + var aValue = exports.get(a, parsedPath); + var bValue = exports.get(b, parsedPath); + + return sign * (aValue > bValue ? 1 : aValue < bValue ? -1 : 0); + }) + + return sortedArray; +} + +/** + * Sort object keys using natural sort + * @param {Object} object + * @param {'asc' | 'desc'} [direction] + */ +exports.sortObjectKeys = function (object, direction) { + var sign = (direction === 'desc') ? -1 : 1; + var sortedFields = Object.keys(object).sort(function (a, b) { + return sign * naturalSort(a, b); + }); + + var sortedObject = {}; + sortedFields.forEach(function (field) { + sortedObject[field] = object[field]; + }); + + return sortedObject; +} + +/** + * Cast contents of a string to the correct type. + * This can be a string, a number, a boolean, etc + * @param {String} str + * @return {*} castedStr + * @private + */ +exports.parseString = function(str) { + var lower = str.toLowerCase(); + var num = Number(str); // will nicely fail with '123ab' + var numFloat = parseFloat(str); // will nicely fail with ' ' + + if (str === '') { + return ''; + } + else if (lower === 'null') { + return null; + } + else if (lower === 'true') { + return true; + } + else if (lower === 'false') { + return false; + } + else if (!isNaN(num) && !isNaN(numFloat)) { + return num; + } + else { + return str; + } +}; + +/** + * Return a human readable document size + * For example formatSize(7570718) outputs '7.2 MiB' + * @param {number} size + * @return {string} Returns a human readable size + */ +exports.formatSize = function (size) { + if (size < 900) { + return size.toFixed() + ' B'; + } + + var KiB = size / 1024; + if (KiB < 900) { + return KiB.toFixed(1) + ' KiB'; + } + + var MiB = KiB / 1024; + if (MiB < 900) { + return MiB.toFixed(1) + ' MiB'; + } + + var GiB = MiB / 1024; + if (GiB < 900) { + return GiB.toFixed(1) + ' GiB'; + } + + var TiB = GiB / 1024; + return TiB.toFixed(1) + ' TiB'; +} + +/** + * Limit text to a maximum number of characters + * @param {string} text + * @param {number} maxCharacterCount + * @return {string} Returns the limited text, + * ending with '...' if the max was exceeded + */ +exports.limitCharacters = function (text, maxCharacterCount) { + if (text.length <= maxCharacterCount) { + return text; + } + + return text.slice(0, maxCharacterCount) + '...'; +} + +/** + * Test whether a value is an Object + * @param {*} value + * @return {boolean} + */ +exports.isObject = function (value) { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +/** + * Helper function to test whether an array contains an item + * @param {Array} array + * @param {*} item + * @return {boolean} Returns true if `item` is in `array`, returns false otherwise. + */ +exports.contains = function (array, item) { + return array.indexOf(item) !== -1; +} /***/ }), @@ -1571,451 +1571,451 @@ exports.contains = function (array, item) { /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -__webpack_require__(23); - -var _locales = ['en', 'pt-BR', 'zh-CN', 'tr']; -var _defs = { - en: { - array: 'Array', - auto: 'Auto', - appendText: 'Append', - appendTitle: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', - appendSubmenuTitle: 'Select the type of the field to be appended', - appendTitleAuto: 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', - ascending: 'Ascending', - ascendingTitle: 'Sort the childs of this ${type} in ascending order', - actionsMenu: 'Click to open the actions menu (Ctrl+M)', - collapseAll: 'Collapse all fields', - descending: 'Descending', - descendingTitle: 'Sort the childs of this ${type} in descending order', - drag: 'Drag to move this field (Alt+Shift+Arrows)', - duplicateKey: 'duplicate key', - duplicateText: 'Duplicate', - duplicateTitle: 'Duplicate selected fields (Ctrl+D)', - duplicateField: 'Duplicate this field (Ctrl+D)', - duplicateFieldError: 'Duplicate field name', - cannotParseFieldError: 'Cannot parse field into JSON', - cannotParseValueError: 'Cannot parse value into JSON', - empty: 'empty', - expandAll: 'Expand all fields', - expandTitle: 'Click to expand/collapse this field (Ctrl+E). \n' + - 'Ctrl+Click to expand/collapse including all childs.', - insert: 'Insert', - insertTitle: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', - insertSub: 'Select the type of the field to be inserted', - object: 'Object', - ok: 'Ok', - redo: 'Redo (Ctrl+Shift+Z)', - removeText: 'Remove', - removeTitle: 'Remove selected fields (Ctrl+Del)', - removeField: 'Remove this field (Ctrl+Del)', - selectNode: 'Select a node...', - showAll: 'show all', - showMore: 'show more', - showMoreStatus: 'displaying ${visibleChilds} of ${totalChilds} items.', - sort: 'Sort', - sortTitle: 'Sort the childs of this ${type}', - sortTitleShort: 'Sort contents', - sortFieldLabel: 'Field:', - sortDirectionLabel: 'Direction:', - sortFieldTitle: 'Select the nested field by which to sort the array or object', - sortAscending: 'Ascending', - sortAscendingTitle: 'Sort the selected field in ascending order', - sortDescending: 'Descending', - sortDescendingTitle: 'Sort the selected field in descending order', - string: 'String', - transform: 'Transform', - transformTitle: 'Filter, sort, or transform the childs of this ${type}', - transformTitleShort: 'Filter, sort, or transform contents', - extract: 'Extract', - extractTitle: 'Extract this ${type}', - transformQueryTitle: 'Enter a JMESPath query', - transformWizardLabel: 'Wizard', - transformWizardFilter: 'Filter', - transformWizardSortBy: 'Sort by', - transformWizardSelectFields: 'Select fields', - transformQueryLabel: 'Query', - transformPreviewLabel: 'Preview', - type: 'Type', - typeTitle: 'Change the type of this field', - openUrl: 'Ctrl+Click or Ctrl+Enter to open url in new window', - undo: 'Undo last action (Ctrl+Z)', - validationCannotMove: 'Cannot move a field into a child of itself', - autoType: 'Field type "auto". ' + - 'The field type is automatically determined from the value ' + - 'and can be a string, number, boolean, or null.', - objectType: 'Field type "object". ' + - 'An object contains an unordered set of key/value pairs.', - arrayType: 'Field type "array". ' + - 'An array contains an ordered collection of values.', - stringType: 'Field type "string". ' + - 'Field type is not determined from the value, ' + - 'but always returned as string.', - modeCodeText: 'Code', - modeCodeTitle: 'Switch to code highlighter', - modeFormText: 'Form', - modeFormTitle: 'Switch to form editor', - modeTextText: 'Text', - modeTextTitle: 'Switch to plain text editor', - modeTreeText: 'Tree', - modeTreeTitle: 'Switch to tree editor', - modeViewText: 'View', - modeViewTitle: 'Switch to tree view', - modePreviewText: 'Preview', - modePreviewTitle: 'Switch to preview mode', - examples: 'Examples', - default: 'Default', - }, - 'zh-CN': { - array: '数组', - auto: '自动', - appendText: '追加', - appendTitle: '在此字段后追加一个类型为“auto”的新字段 (Ctrl+Shift+Ins)', - appendSubmenuTitle: '选择要追加的字段类型', - appendTitleAuto: '追加类型为“auto”的新字段 (Ctrl+Shift+Ins)', - ascending: '升序', - ascendingTitle: '升序排列${type}的子节点', - actionsMenu: '点击打开动作菜单(Ctrl+M)', - collapseAll: '缩进所有字段', - descending: '降序', - descendingTitle: '降序排列${type}的子节点', - drag: '拖拽移动该节点(Alt+Shift+Arrows)', - duplicateKey: '重复键', - duplicateText: '复制', - duplicateTitle: '复制选中字段(Ctrl+D)', - duplicateField: '复制该字段(Ctrl+D)', - duplicateFieldError: '重复的字段名称', - cannotParseFieldError: '无法将字段解析为JSON', - cannotParseValueError: '无法将值解析为JSON', - empty: '清空', - expandAll: '展开所有字段', - expandTitle: '点击 展开/收缩 该字段(Ctrl+E). \n' + - 'Ctrl+Click 展开/收缩 包含所有子节点.', - insert: '插入', - insertTitle: '在此字段前插入类型为“auto”的新字段 (Ctrl+Ins)', - insertSub: '选择要插入的字段类型', - object: '对象', - ok: 'Ok', - redo: '重做 (Ctrl+Shift+Z)', - removeText: '移除', - removeTitle: '移除选中字段 (Ctrl+Del)', - removeField: '移除该字段 (Ctrl+Del)', - selectNode: '选择一个节点...', - showAll: '展示全部', - showMore: '展示更多', - showMoreStatus: '显示${totalChilds}的${visibleChilds}项目.', - sort: '排序', - sortTitle: '排序${type}的子节点', - sortTitleShort: '内容排序', - sortFieldLabel: '字段:', - sortDirectionLabel: '方向:', - sortFieldTitle: '选择用于对数组或对象排序的嵌套字段', - sortAscending: '升序排序', - sortAscendingTitle: '按照该字段升序排序', - sortDescending: '降序排序', - sortDescendingTitle: '按照该字段降序排序', - string: '字符串', - transform: '变换', - transformTitle: '筛选,排序,或者转换${type}的子节点', - transformTitleShort: '筛选,排序,或者转换内容', - extract: '提取', - extractTitle: '提取这个 ${type}', - transformQueryTitle: '输入JMESPath查询', - transformWizardLabel: '向导', - transformWizardFilter: '筛选', - transformWizardSortBy: '排序', - transformWizardSelectFields: '选择字段', - transformQueryLabel: '查询', - transformPreviewLabel: '预览', - type: '类型', - typeTitle: '更改字段类型', - openUrl: 'Ctrl+Click 或者 Ctrl+Enter 在新窗口打开链接', - undo: '撤销上次动作 (Ctrl+Z)', - validationCannotMove: '无法将字段移入其子节点', - autoType: '字段类型 "auto". ' + - '字段类型由值自动确定 ' + - '可以为 string,number,boolean,或者 null.', - objectType: '字段类型 "object". ' + - '对象包含一组无序的键/值对.', - arrayType: '字段类型 "array". ' + - '数组包含值的有序集合.', - stringType: '字段类型 "string". ' + - '字段类型由值自动确定,' + - '但始终作为字符串返回.', - modeCodeText: '代码', - modeCodeTitle: '切换至代码高亮', - modeFormText: '表单', - modeFormTitle: '切换至表单编辑', - modeTextText: '文本', - modeTextTitle: '切换至文本编辑', - modeTreeText: '树', - modeTreeTitle: '切换至树编辑', - modeViewText: '视图', - modeViewTitle: '切换至树视图', - modePreviewText: '预览', - modePreviewTitle: '切换至预览模式', - examples: '例子', - default: '缺省', - }, - 'pt-BR': { - array: 'Lista', - auto: 'Automatico', - appendText: 'Adicionar', - appendTitle: 'Adicionar novo campo com tipo \'auto\' depois deste campo (Ctrl+Shift+Ins)', - appendSubmenuTitle: 'Selecione o tipo do campo a ser adicionado', - appendTitleAuto: 'Adicionar novo campo com tipo \'auto\' (Ctrl+Shift+Ins)', - ascending: 'Ascendente', - ascendingTitle: 'Organizar filhor do tipo ${type} em crescente', - actionsMenu: 'Clique para abrir o menu de ações (Ctrl+M)', - collapseAll: 'Fechar todos campos', - descending: 'Descendente', - descendingTitle: 'Organizar o filhos do tipo ${type} em decrescente', - duplicateKey: 'chave duplicada', - drag: 'Arraste para mover este campo (Alt+Shift+Arrows)', - duplicateText: 'Duplicar', - duplicateTitle: 'Duplicar campos selecionados (Ctrl+D)', - duplicateField: 'Duplicar este campo (Ctrl+D)', - duplicateFieldError: 'Nome do campo duplicado', - cannotParseFieldError: 'Não é possível analisar o campo no JSON', - cannotParseValueError: 'Não é possível analisar o valor em JSON', - empty: 'vazio', - expandAll: 'Expandir todos campos', - expandTitle: 'Clique para expandir/encolher este campo (Ctrl+E). \n' + - 'Ctrl+Click para expandir/encolher incluindo todos os filhos.', - insert: 'Inserir', - insertTitle: 'Inserir um novo campo do tipo \'auto\' antes deste campo (Ctrl+Ins)', - insertSub: 'Selecionar o tipo de campo a ser inserido', - object: 'Objeto', - ok: 'Ok', - redo: 'Refazer (Ctrl+Shift+Z)', - removeText: 'Remover', - removeTitle: 'Remover campos selecionados (Ctrl+Del)', - removeField: 'Remover este campo (Ctrl+Del)', - // TODO: correctly translate - selectNode: 'Select a node...', - // TODO: correctly translate - showAll: 'mostre tudo', - // TODO: correctly translate - showMore: 'mostre mais', - // TODO: correctly translate - showMoreStatus: 'exibindo ${visibleChilds} de ${totalChilds} itens.', - sort: 'Organizar', - sortTitle: 'Organizar os filhos deste ${type}', - // TODO: correctly translate - sortTitleShort: 'Organizar os filhos', - // TODO: correctly translate - sortFieldLabel: 'Field:', - // TODO: correctly translate - sortDirectionLabel: 'Direction:', - // TODO: correctly translate - sortFieldTitle: 'Select the nested field by which to sort the array or object', - // TODO: correctly translate - sortAscending: 'Ascending', - // TODO: correctly translate - sortAscendingTitle: 'Sort the selected field in ascending order', - // TODO: correctly translate - sortDescending: 'Descending', - // TODO: correctly translate - sortDescendingTitle: 'Sort the selected field in descending order', - string: 'Texto', - // TODO: correctly translate - transform: 'Transform', - // TODO: correctly translate - transformTitle: 'Filter, sort, or transform the childs of this ${type}', - // TODO: correctly translate - transformTitleShort: 'Filter, sort, or transform contents', - // TODO: correctly translate - transformQueryTitle: 'Enter a JMESPath query', - // TODO: correctly translate - transformWizardLabel: 'Wizard', - // TODO: correctly translate - transformWizardFilter: 'Filter', - // TODO: correctly translate - transformWizardSortBy: 'Sort by', - // TODO: correctly translate - transformWizardSelectFields: 'Select fields', - // TODO: correctly translate - transformQueryLabel: 'Query', - // TODO: correctly translate - transformPreviewLabel: 'Preview', - type: 'Tipo', - typeTitle: 'Mudar o tipo deste campo', - openUrl: 'Ctrl+Click ou Ctrl+Enter para abrir link em nova janela', - undo: 'Desfazer último ação (Ctrl+Z)', - validationCannotMove: 'Não pode mover um campo como filho dele mesmo', - autoType: 'Campo do tipo "auto". ' + - 'O tipo do campo é determinao automaticamente a partir do seu valor ' + - 'e pode ser texto, número, verdade/falso ou nulo.', - objectType: 'Campo do tipo "objeto". ' + - 'Um objeto contém uma lista de pares com chave e valor.', - arrayType: 'Campo do tipo "lista". ' + - 'Uma lista contem uma coleção de valores ordenados.', - stringType: 'Campo do tipo "string". ' + - 'Campo do tipo nao é determinado através do seu valor, ' + - 'mas sempre retornara um texto.', - examples: 'Exemplos', - default: 'Revelia', - }, - tr: { - array: 'Dizin', - auto: 'Otomatik', - appendText: 'Ekle', - appendTitle: 'Bu alanın altına \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', - appendSubmenuTitle: 'Eklenecek alanın tipini seç', - appendTitleAuto: '\'Otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', - ascending: 'Artan', - ascendingTitle: '${type}\'ın alt tiplerini artan düzende sırala', - actionsMenu: 'Aksiyon menüsünü açmak için tıklayın (Ctrl+M)', - collapseAll: 'Tüm alanları kapat', - descending: 'Azalan', - descendingTitle: '${type}\'ın alt tiplerini azalan düzende sırala', - drag: 'Bu alanı taşımak için sürükleyin (Alt+Shift+Arrows)', - duplicateKey: 'Var olan anahtar', - duplicateText: 'Aşağıya kopyala', - duplicateTitle: 'Seçili alanlardan bir daha oluştur (Ctrl+D)', - duplicateField: 'Bu alandan bir daha oluştur (Ctrl+D)', - duplicateFieldError: 'Duplicate field name', - cannotParseFieldError: 'Alan JSON\'a ayrıştırılamıyor', - cannotParseValueError: 'JSON\'a değer ayrıştırılamıyor', - empty: 'boş', - expandAll: 'Tüm alanları aç', - expandTitle: 'Bu alanı açmak/kapatmak için tıkla (Ctrl+E). \n' + - 'Alt alanlarda dahil tüm alanları açmak için Ctrl+Click ', - insert: 'Ekle', - insertTitle: 'Bu alanın üstüne \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Ins)', - insertSub: 'Araya eklenecek alanın tipini seç', - object: 'Nesne', - ok: 'Tamam', - redo: 'Yeniden yap (Ctrl+Shift+Z)', - removeText: 'Kaldır', - removeTitle: 'Seçilen alanları kaldır (Ctrl+Del)', - removeField: 'Bu alanı kaldır (Ctrl+Del)', - selectNode: 'Bir nesne seç...', - showAll: 'tümünü göster', - showMore: 'daha fazla göster', - showMoreStatus: '${totalChilds} alanın ${visibleChilds} alt alanları gösteriliyor', - sort: 'Sırala', - sortTitle: '${type}\'ın alt alanlarını sırala', - sortTitleShort: 'İçerikleri sırala', - sortFieldLabel: 'Alan:', - sortDirectionLabel: 'Yön:', - sortFieldTitle: 'Diziyi veya nesneyi sıralamak için iç içe geçmiş alanı seçin', - sortAscending: 'Artan', - sortAscendingTitle: 'Seçili alanı artan düzende sırala', - sortDescending: 'Azalan', - sortDescendingTitle: 'Seçili alanı azalan düzende sırala', - string: 'Karakter Dizisi', - transform: 'Dönüştür', - transformTitle: '${type}\'ın alt alanlarını filtrele, sırala veya dönüştür', - transformTitleShort: 'İçerikleri filterele, sırala veya dönüştür', - transformQueryTitle: 'JMESPath sorgusu gir', - transformWizardLabel: 'Sihirbaz', - transformWizardFilter: 'Filtre', - transformWizardSortBy: 'Sırala', - transformWizardSelectFields: 'Alanları seç', - transformQueryLabel: 'Sorgu', - transformPreviewLabel: 'Önizleme', - type: 'Tip', - typeTitle: 'Bu alanın tipini değiştir', - openUrl: 'URL\'i yeni bir pencerede açmak için Ctrl+Click veya Ctrl+Enter', - undo: 'Son değişikliği geri al (Ctrl+Z)', - validationCannotMove: 'Alt alan olarak taşınamıyor', - autoType: 'Alan tipi "otomatik". ' + - 'Alan türü otomatik olarak değerden belirlenir' + - 've bir dize, sayı, boolean veya null olabilir.', - objectType: 'Alan tipi "nesne". ' + - 'Bir nesne, sıralanmamış bir anahtar / değer çifti kümesi içerir.', - arrayType: 'Alan tipi "dizi". ' + - 'Bir dizi, düzenli değerler koleksiyonu içerir.', - stringType: 'Alan tipi "karakter dizisi". ' + - 'Alan türü değerden belirlenmez,' + - 'ancak her zaman karakter dizisi olarak döndürülür.', - modeCodeText: 'Kod', - modeCodeTitle: 'Kod vurgulayıcıya geç', - modeFormText: 'Form', - modeFormTitle: 'Form düzenleyiciye geç', - modeTextText: 'Metin', - modeTextTitle: 'Düz metin düzenleyiciye geç', - modeTreeText: 'Ağaç', - modeTreeTitle: 'Ağaç düzenleyiciye geç', - modeViewText: 'Görünüm', - modeViewTitle: 'Ağaç görünümüne geç', - examples: 'Örnekler', - default: 'Varsayılan', - } -}; - -var _defaultLang = 'en'; -var _lang; -var userLang = typeof navigator !== 'undefined' ? - navigator.language || navigator.userLanguage : - undefined; -_lang = _locales.find(function (l) { - return l === userLang; -}); -if (!_lang) { - _lang = _defaultLang; -} - -module.exports = { - // supported locales - _locales: _locales, - _defs: _defs, - _lang: _lang, - setLanguage: function (lang) { - if (!lang) { - return; - } - var langFound = _locales.find(function (l) { - return l === lang; - }); - if (langFound) { - _lang = langFound; - } else { - console.error('Language not found'); - } - }, - setLanguages: function (languages) { - if (!languages) { - return; - } - for (var key in languages) { - var langFound = _locales.find(function (l) { - return l === key; - }); - if (!langFound) { - _locales.push(key); - } - _defs[key] = Object.assign({}, _defs[_defaultLang], _defs[key], languages[key]); - } - }, - translate: function (key, data, lang) { - if (!lang) { - lang = _lang; - } - var text = _defs[lang][key]; - if (data) { - for (key in data) { - text = text.replace('${' + key + '}', data[key]); - } - } - return text || key; - } + + +__webpack_require__(23); + +var _locales = ['en', 'pt-BR', 'zh-CN', 'tr']; +var _defs = { + en: { + array: 'Array', + auto: 'Auto', + appendText: 'Append', + appendTitle: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)', + appendSubmenuTitle: 'Select the type of the field to be appended', + appendTitleAuto: 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)', + ascending: 'Ascending', + ascendingTitle: 'Sort the childs of this ${type} in ascending order', + actionsMenu: 'Click to open the actions menu (Ctrl+M)', + collapseAll: 'Collapse all fields', + descending: 'Descending', + descendingTitle: 'Sort the childs of this ${type} in descending order', + drag: 'Drag to move this field (Alt+Shift+Arrows)', + duplicateKey: 'duplicate key', + duplicateText: 'Duplicate', + duplicateTitle: 'Duplicate selected fields (Ctrl+D)', + duplicateField: 'Duplicate this field (Ctrl+D)', + duplicateFieldError: 'Duplicate field name', + cannotParseFieldError: 'Cannot parse field into JSON', + cannotParseValueError: 'Cannot parse value into JSON', + empty: 'empty', + expandAll: 'Expand all fields', + expandTitle: 'Click to expand/collapse this field (Ctrl+E). \n' + + 'Ctrl+Click to expand/collapse including all childs.', + insert: 'Insert', + insertTitle: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)', + insertSub: 'Select the type of the field to be inserted', + object: 'Object', + ok: 'Ok', + redo: 'Redo (Ctrl+Shift+Z)', + removeText: 'Remove', + removeTitle: 'Remove selected fields (Ctrl+Del)', + removeField: 'Remove this field (Ctrl+Del)', + selectNode: 'Select a node...', + showAll: 'show all', + showMore: 'show more', + showMoreStatus: 'displaying ${visibleChilds} of ${totalChilds} items.', + sort: 'Sort', + sortTitle: 'Sort the childs of this ${type}', + sortTitleShort: 'Sort contents', + sortFieldLabel: 'Field:', + sortDirectionLabel: 'Direction:', + sortFieldTitle: 'Select the nested field by which to sort the array or object', + sortAscending: 'Ascending', + sortAscendingTitle: 'Sort the selected field in ascending order', + sortDescending: 'Descending', + sortDescendingTitle: 'Sort the selected field in descending order', + string: 'String', + transform: 'Transform', + transformTitle: 'Filter, sort, or transform the childs of this ${type}', + transformTitleShort: 'Filter, sort, or transform contents', + extract: 'Extract', + extractTitle: 'Extract this ${type}', + transformQueryTitle: 'Enter a JMESPath query', + transformWizardLabel: 'Wizard', + transformWizardFilter: 'Filter', + transformWizardSortBy: 'Sort by', + transformWizardSelectFields: 'Select fields', + transformQueryLabel: 'Query', + transformPreviewLabel: 'Preview', + type: 'Type', + typeTitle: 'Change the type of this field', + openUrl: 'Ctrl+Click or Ctrl+Enter to open url in new window', + undo: 'Undo last action (Ctrl+Z)', + validationCannotMove: 'Cannot move a field into a child of itself', + autoType: 'Field type "auto". ' + + 'The field type is automatically determined from the value ' + + 'and can be a string, number, boolean, or null.', + objectType: 'Field type "object". ' + + 'An object contains an unordered set of key/value pairs.', + arrayType: 'Field type "array". ' + + 'An array contains an ordered collection of values.', + stringType: 'Field type "string". ' + + 'Field type is not determined from the value, ' + + 'but always returned as string.', + modeCodeText: 'Code', + modeCodeTitle: 'Switch to code highlighter', + modeFormText: 'Form', + modeFormTitle: 'Switch to form editor', + modeTextText: 'Text', + modeTextTitle: 'Switch to plain text editor', + modeTreeText: 'Tree', + modeTreeTitle: 'Switch to tree editor', + modeViewText: 'View', + modeViewTitle: 'Switch to tree view', + modePreviewText: 'Preview', + modePreviewTitle: 'Switch to preview mode', + examples: 'Examples', + default: 'Default', + }, + 'zh-CN': { + array: '数组', + auto: '自动', + appendText: '追加', + appendTitle: '在此字段后追加一个类型为“auto”的新字段 (Ctrl+Shift+Ins)', + appendSubmenuTitle: '选择要追加的字段类型', + appendTitleAuto: '追加类型为“auto”的新字段 (Ctrl+Shift+Ins)', + ascending: '升序', + ascendingTitle: '升序排列${type}的子节点', + actionsMenu: '点击打开动作菜单(Ctrl+M)', + collapseAll: '缩进所有字段', + descending: '降序', + descendingTitle: '降序排列${type}的子节点', + drag: '拖拽移动该节点(Alt+Shift+Arrows)', + duplicateKey: '重复键', + duplicateText: '复制', + duplicateTitle: '复制选中字段(Ctrl+D)', + duplicateField: '复制该字段(Ctrl+D)', + duplicateFieldError: '重复的字段名称', + cannotParseFieldError: '无法将字段解析为JSON', + cannotParseValueError: '无法将值解析为JSON', + empty: '清空', + expandAll: '展开所有字段', + expandTitle: '点击 展开/收缩 该字段(Ctrl+E). \n' + + 'Ctrl+Click 展开/收缩 包含所有子节点.', + insert: '插入', + insertTitle: '在此字段前插入类型为“auto”的新字段 (Ctrl+Ins)', + insertSub: '选择要插入的字段类型', + object: '对象', + ok: 'Ok', + redo: '重做 (Ctrl+Shift+Z)', + removeText: '移除', + removeTitle: '移除选中字段 (Ctrl+Del)', + removeField: '移除该字段 (Ctrl+Del)', + selectNode: '选择一个节点...', + showAll: '展示全部', + showMore: '展示更多', + showMoreStatus: '显示${totalChilds}的${visibleChilds}项目.', + sort: '排序', + sortTitle: '排序${type}的子节点', + sortTitleShort: '内容排序', + sortFieldLabel: '字段:', + sortDirectionLabel: '方向:', + sortFieldTitle: '选择用于对数组或对象排序的嵌套字段', + sortAscending: '升序排序', + sortAscendingTitle: '按照该字段升序排序', + sortDescending: '降序排序', + sortDescendingTitle: '按照该字段降序排序', + string: '字符串', + transform: '变换', + transformTitle: '筛选,排序,或者转换${type}的子节点', + transformTitleShort: '筛选,排序,或者转换内容', + extract: '提取', + extractTitle: '提取这个 ${type}', + transformQueryTitle: '输入JMESPath查询', + transformWizardLabel: '向导', + transformWizardFilter: '筛选', + transformWizardSortBy: '排序', + transformWizardSelectFields: '选择字段', + transformQueryLabel: '查询', + transformPreviewLabel: '预览', + type: '类型', + typeTitle: '更改字段类型', + openUrl: 'Ctrl+Click 或者 Ctrl+Enter 在新窗口打开链接', + undo: '撤销上次动作 (Ctrl+Z)', + validationCannotMove: '无法将字段移入其子节点', + autoType: '字段类型 "auto". ' + + '字段类型由值自动确定 ' + + '可以为 string,number,boolean,或者 null.', + objectType: '字段类型 "object". ' + + '对象包含一组无序的键/值对.', + arrayType: '字段类型 "array". ' + + '数组包含值的有序集合.', + stringType: '字段类型 "string". ' + + '字段类型由值自动确定,' + + '但始终作为字符串返回.', + modeCodeText: '代码', + modeCodeTitle: '切换至代码高亮', + modeFormText: '表单', + modeFormTitle: '切换至表单编辑', + modeTextText: '文本', + modeTextTitle: '切换至文本编辑', + modeTreeText: '树', + modeTreeTitle: '切换至树编辑', + modeViewText: '视图', + modeViewTitle: '切换至树视图', + modePreviewText: '预览', + modePreviewTitle: '切换至预览模式', + examples: '例子', + default: '缺省', + }, + 'pt-BR': { + array: 'Lista', + auto: 'Automatico', + appendText: 'Adicionar', + appendTitle: 'Adicionar novo campo com tipo \'auto\' depois deste campo (Ctrl+Shift+Ins)', + appendSubmenuTitle: 'Selecione o tipo do campo a ser adicionado', + appendTitleAuto: 'Adicionar novo campo com tipo \'auto\' (Ctrl+Shift+Ins)', + ascending: 'Ascendente', + ascendingTitle: 'Organizar filhor do tipo ${type} em crescente', + actionsMenu: 'Clique para abrir o menu de ações (Ctrl+M)', + collapseAll: 'Fechar todos campos', + descending: 'Descendente', + descendingTitle: 'Organizar o filhos do tipo ${type} em decrescente', + duplicateKey: 'chave duplicada', + drag: 'Arraste para mover este campo (Alt+Shift+Arrows)', + duplicateText: 'Duplicar', + duplicateTitle: 'Duplicar campos selecionados (Ctrl+D)', + duplicateField: 'Duplicar este campo (Ctrl+D)', + duplicateFieldError: 'Nome do campo duplicado', + cannotParseFieldError: 'Não é possível analisar o campo no JSON', + cannotParseValueError: 'Não é possível analisar o valor em JSON', + empty: 'vazio', + expandAll: 'Expandir todos campos', + expandTitle: 'Clique para expandir/encolher este campo (Ctrl+E). \n' + + 'Ctrl+Click para expandir/encolher incluindo todos os filhos.', + insert: 'Inserir', + insertTitle: 'Inserir um novo campo do tipo \'auto\' antes deste campo (Ctrl+Ins)', + insertSub: 'Selecionar o tipo de campo a ser inserido', + object: 'Objeto', + ok: 'Ok', + redo: 'Refazer (Ctrl+Shift+Z)', + removeText: 'Remover', + removeTitle: 'Remover campos selecionados (Ctrl+Del)', + removeField: 'Remover este campo (Ctrl+Del)', + // TODO: correctly translate + selectNode: 'Select a node...', + // TODO: correctly translate + showAll: 'mostre tudo', + // TODO: correctly translate + showMore: 'mostre mais', + // TODO: correctly translate + showMoreStatus: 'exibindo ${visibleChilds} de ${totalChilds} itens.', + sort: 'Organizar', + sortTitle: 'Organizar os filhos deste ${type}', + // TODO: correctly translate + sortTitleShort: 'Organizar os filhos', + // TODO: correctly translate + sortFieldLabel: 'Field:', + // TODO: correctly translate + sortDirectionLabel: 'Direction:', + // TODO: correctly translate + sortFieldTitle: 'Select the nested field by which to sort the array or object', + // TODO: correctly translate + sortAscending: 'Ascending', + // TODO: correctly translate + sortAscendingTitle: 'Sort the selected field in ascending order', + // TODO: correctly translate + sortDescending: 'Descending', + // TODO: correctly translate + sortDescendingTitle: 'Sort the selected field in descending order', + string: 'Texto', + // TODO: correctly translate + transform: 'Transform', + // TODO: correctly translate + transformTitle: 'Filter, sort, or transform the childs of this ${type}', + // TODO: correctly translate + transformTitleShort: 'Filter, sort, or transform contents', + // TODO: correctly translate + transformQueryTitle: 'Enter a JMESPath query', + // TODO: correctly translate + transformWizardLabel: 'Wizard', + // TODO: correctly translate + transformWizardFilter: 'Filter', + // TODO: correctly translate + transformWizardSortBy: 'Sort by', + // TODO: correctly translate + transformWizardSelectFields: 'Select fields', + // TODO: correctly translate + transformQueryLabel: 'Query', + // TODO: correctly translate + transformPreviewLabel: 'Preview', + type: 'Tipo', + typeTitle: 'Mudar o tipo deste campo', + openUrl: 'Ctrl+Click ou Ctrl+Enter para abrir link em nova janela', + undo: 'Desfazer último ação (Ctrl+Z)', + validationCannotMove: 'Não pode mover um campo como filho dele mesmo', + autoType: 'Campo do tipo "auto". ' + + 'O tipo do campo é determinao automaticamente a partir do seu valor ' + + 'e pode ser texto, número, verdade/falso ou nulo.', + objectType: 'Campo do tipo "objeto". ' + + 'Um objeto contém uma lista de pares com chave e valor.', + arrayType: 'Campo do tipo "lista". ' + + 'Uma lista contem uma coleção de valores ordenados.', + stringType: 'Campo do tipo "string". ' + + 'Campo do tipo nao é determinado através do seu valor, ' + + 'mas sempre retornara um texto.', + examples: 'Exemplos', + default: 'Revelia', + }, + tr: { + array: 'Dizin', + auto: 'Otomatik', + appendText: 'Ekle', + appendTitle: 'Bu alanın altına \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', + appendSubmenuTitle: 'Eklenecek alanın tipini seç', + appendTitleAuto: '\'Otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)', + ascending: 'Artan', + ascendingTitle: '${type}\'ın alt tiplerini artan düzende sırala', + actionsMenu: 'Aksiyon menüsünü açmak için tıklayın (Ctrl+M)', + collapseAll: 'Tüm alanları kapat', + descending: 'Azalan', + descendingTitle: '${type}\'ın alt tiplerini azalan düzende sırala', + drag: 'Bu alanı taşımak için sürükleyin (Alt+Shift+Arrows)', + duplicateKey: 'Var olan anahtar', + duplicateText: 'Aşağıya kopyala', + duplicateTitle: 'Seçili alanlardan bir daha oluştur (Ctrl+D)', + duplicateField: 'Bu alandan bir daha oluştur (Ctrl+D)', + duplicateFieldError: 'Duplicate field name', + cannotParseFieldError: 'Alan JSON\'a ayrıştırılamıyor', + cannotParseValueError: 'JSON\'a değer ayrıştırılamıyor', + empty: 'boş', + expandAll: 'Tüm alanları aç', + expandTitle: 'Bu alanı açmak/kapatmak için tıkla (Ctrl+E). \n' + + 'Alt alanlarda dahil tüm alanları açmak için Ctrl+Click ', + insert: 'Ekle', + insertTitle: 'Bu alanın üstüne \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Ins)', + insertSub: 'Araya eklenecek alanın tipini seç', + object: 'Nesne', + ok: 'Tamam', + redo: 'Yeniden yap (Ctrl+Shift+Z)', + removeText: 'Kaldır', + removeTitle: 'Seçilen alanları kaldır (Ctrl+Del)', + removeField: 'Bu alanı kaldır (Ctrl+Del)', + selectNode: 'Bir nesne seç...', + showAll: 'tümünü göster', + showMore: 'daha fazla göster', + showMoreStatus: '${totalChilds} alanın ${visibleChilds} alt alanları gösteriliyor', + sort: 'Sırala', + sortTitle: '${type}\'ın alt alanlarını sırala', + sortTitleShort: 'İçerikleri sırala', + sortFieldLabel: 'Alan:', + sortDirectionLabel: 'Yön:', + sortFieldTitle: 'Diziyi veya nesneyi sıralamak için iç içe geçmiş alanı seçin', + sortAscending: 'Artan', + sortAscendingTitle: 'Seçili alanı artan düzende sırala', + sortDescending: 'Azalan', + sortDescendingTitle: 'Seçili alanı azalan düzende sırala', + string: 'Karakter Dizisi', + transform: 'Dönüştür', + transformTitle: '${type}\'ın alt alanlarını filtrele, sırala veya dönüştür', + transformTitleShort: 'İçerikleri filterele, sırala veya dönüştür', + transformQueryTitle: 'JMESPath sorgusu gir', + transformWizardLabel: 'Sihirbaz', + transformWizardFilter: 'Filtre', + transformWizardSortBy: 'Sırala', + transformWizardSelectFields: 'Alanları seç', + transformQueryLabel: 'Sorgu', + transformPreviewLabel: 'Önizleme', + type: 'Tip', + typeTitle: 'Bu alanın tipini değiştir', + openUrl: 'URL\'i yeni bir pencerede açmak için Ctrl+Click veya Ctrl+Enter', + undo: 'Son değişikliği geri al (Ctrl+Z)', + validationCannotMove: 'Alt alan olarak taşınamıyor', + autoType: 'Alan tipi "otomatik". ' + + 'Alan türü otomatik olarak değerden belirlenir' + + 've bir dize, sayı, boolean veya null olabilir.', + objectType: 'Alan tipi "nesne". ' + + 'Bir nesne, sıralanmamış bir anahtar / değer çifti kümesi içerir.', + arrayType: 'Alan tipi "dizi". ' + + 'Bir dizi, düzenli değerler koleksiyonu içerir.', + stringType: 'Alan tipi "karakter dizisi". ' + + 'Alan türü değerden belirlenmez,' + + 'ancak her zaman karakter dizisi olarak döndürülür.', + modeCodeText: 'Kod', + modeCodeTitle: 'Kod vurgulayıcıya geç', + modeFormText: 'Form', + modeFormTitle: 'Form düzenleyiciye geç', + modeTextText: 'Metin', + modeTextTitle: 'Düz metin düzenleyiciye geç', + modeTreeText: 'Ağaç', + modeTreeTitle: 'Ağaç düzenleyiciye geç', + modeViewText: 'Görünüm', + modeViewTitle: 'Ağaç görünümüne geç', + examples: 'Örnekler', + default: 'Varsayılan', + } +}; + +var _defaultLang = 'en'; +var _lang; +var userLang = typeof navigator !== 'undefined' ? + navigator.language || navigator.userLanguage : + undefined; +_lang = _locales.find(function (l) { + return l === userLang; +}); +if (!_lang) { + _lang = _defaultLang; +} + +module.exports = { + // supported locales + _locales: _locales, + _defs: _defs, + _lang: _lang, + setLanguage: function (lang) { + if (!lang) { + return; + } + var langFound = _locales.find(function (l) { + return l === lang; + }); + if (langFound) { + _lang = langFound; + } else { + console.error('Language not found'); + } + }, + setLanguages: function (languages) { + if (!languages) { + return; + } + for (var key in languages) { + var langFound = _locales.find(function (l) { + return l === key; + }); + if (!langFound) { + _locales.push(key); + } + _defs[key] = Object.assign({}, _defs[_defaultLang], _defs[key], languages[key]); + } + }, + translate: function (key, data, lang) { + if (!lang) { + lang = _lang; + } + var text = _defs[lang][key]; + if (data) { + for (key in data) { + text = text.replace('${' + key + '}', data[key]); + } + } + return text || key; + } }; /***/ }), /* 2 */ /***/ (function(module, exports) { - -exports.DEFAULT_MODAL_ANCHOR = document.body; -exports.SIZE_LARGE = 10 * 1024 * 1024; // 10 MB - -exports.MAX_PREVIEW_CHARACTERS = 20000; - -exports.PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024; // 2 GB + +exports.DEFAULT_MODAL_ANCHOR = document.body; +exports.SIZE_LARGE = 10 * 1024 * 1024; // 10 MB + +exports.MAX_PREVIEW_CHARACTERS = 20000; + +exports.PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024; // 2 GB /***/ }), @@ -2304,444 +2304,444 @@ function unescapeJsonPointer(str) { /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -var createAbsoluteAnchor = __webpack_require__(25).createAbsoluteAnchor; -var util = __webpack_require__(0); -var translate = __webpack_require__(1).translate; - -/** - * A context menu - * @param {Object[]} items Array containing the menu structure - * TODO: describe structure - * @param {Object} [options] Object with options. Available options: - * {function} close Callback called when the - * context menu is being closed. - * @constructor - */ -function ContextMenu (items, options) { - this.dom = {}; - - var me = this; - var dom = this.dom; - this.anchor = undefined; - this.items = items; - this.eventListeners = {}; - this.selection = undefined; // holds the selection before the menu was opened - this.onClose = options ? options.close : undefined; - - // create root element - var root = document.createElement('div'); - root.className = 'jsoneditor-contextmenu-root'; - dom.root = root; - - // create a container element - var menu = document.createElement('div'); - menu.className = 'jsoneditor-contextmenu'; - dom.menu = menu; - root.appendChild(menu); - - // create a list to hold the menu items - var list = document.createElement('ul'); - list.className = 'jsoneditor-menu'; - menu.appendChild(list); - dom.list = list; - dom.items = []; // list with all buttons - - // create a (non-visible) button to set the focus to the menu - var focusButton = document.createElement('button'); - focusButton.type = 'button'; - dom.focusButton = focusButton; - var li = document.createElement('li'); - li.style.overflow = 'hidden'; - li.style.height = '0'; - li.appendChild(focusButton); - list.appendChild(li); - - function createMenuItems (list, domItems, items) { - items.forEach(function (item) { - if (item.type == 'separator') { - // create a separator - var separator = document.createElement('div'); - separator.className = 'jsoneditor-separator'; - li = document.createElement('li'); - li.appendChild(separator); - list.appendChild(li); - } - else { - var domItem = {}; - - // create a menu item - var li = document.createElement('li'); - list.appendChild(li); - - // create a button in the menu item - var button = document.createElement('button'); - button.type = 'button'; - button.className = item.className; - domItem.button = button; - if (item.title) { - button.title = item.title; - } - if (item.click) { - button.onclick = function (event) { - event.preventDefault(); - me.hide(); - item.click(); - }; - } - li.appendChild(button); - - // create the contents of the button - if (item.submenu) { - // add the icon to the button - var divIcon = document.createElement('div'); - divIcon.className = 'jsoneditor-icon'; - button.appendChild(divIcon); - var divText = document.createElement('div'); - divText.className = 'jsoneditor-text' + - (item.click ? '' : ' jsoneditor-right-margin'); - divText.appendChild(document.createTextNode(item.text)); - button.appendChild(divText); - - var buttonSubmenu; - if (item.click) { - // submenu and a button with a click handler - button.className += ' jsoneditor-default'; - - var buttonExpand = document.createElement('button'); - buttonExpand.type = 'button'; - domItem.buttonExpand = buttonExpand; - buttonExpand.className = 'jsoneditor-expand'; - buttonExpand.innerHTML = '

'; - li.appendChild(buttonExpand); - if (item.submenuTitle) { - buttonExpand.title = item.submenuTitle; - } - - buttonSubmenu = buttonExpand; - } - else { - // submenu and a button without a click handler - var divExpand = document.createElement('div'); - divExpand.className = 'jsoneditor-expand'; - button.appendChild(divExpand); - - buttonSubmenu = button; - } - - // attach a handler to expand/collapse the submenu - buttonSubmenu.onclick = function (event) { - event.preventDefault(); - me._onExpandItem(domItem); - buttonSubmenu.focus(); - }; - - // create the submenu - var domSubItems = []; - domItem.subItems = domSubItems; - var ul = document.createElement('ul'); - domItem.ul = ul; - ul.className = 'jsoneditor-menu'; - ul.style.height = '0'; - li.appendChild(ul); - createMenuItems(ul, domSubItems, item.submenu); - } - else { - // no submenu, just a button with clickhandler - button.innerHTML = '
' + - '
' + translate(item.text) + '
'; - } - - domItems.push(domItem); - } - }); - } - createMenuItems(list, this.dom.items, items); - - // TODO: when the editor is small, show the submenu on the right instead of inline? - - // calculate the max height of the menu with one submenu expanded - this.maxHeight = 0; // height in pixels - items.forEach(function (item) { - var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; - me.maxHeight = Math.max(me.maxHeight, height); - }); -} - -/** - * Get the currently visible buttons - * @return {Array.} buttons - * @private - */ -ContextMenu.prototype._getVisibleButtons = function () { - var buttons = []; - var me = this; - this.dom.items.forEach(function (item) { - buttons.push(item.button); - if (item.buttonExpand) { - buttons.push(item.buttonExpand); - } - if (item.subItems && item == me.expandedItem) { - item.subItems.forEach(function (subItem) { - buttons.push(subItem.button); - if (subItem.buttonExpand) { - buttons.push(subItem.buttonExpand); - } - // TODO: change to fully recursive method - }); - } - }); - - return buttons; -}; - -// currently displayed context menu, a singleton. We may only have one visible context menu -ContextMenu.visibleMenu = undefined; - -/** - * Attach the menu to an anchor - * @param {HTMLElement} anchor Anchor where the menu will be attached as sibling. - * @param {HTMLElement} frame The root of the JSONEditor window - * @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute - */ -ContextMenu.prototype.show = function (anchor, frame, ignoreParent) { - this.hide(); - - // determine whether to display the menu below or above the anchor - var showBelow = true; - var parent = anchor.parentNode; - var anchorRect = anchor.getBoundingClientRect(); - var parentRect = parent.getBoundingClientRect(); - var frameRect = frame.getBoundingClientRect(); - - var me = this; - this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, function () { - me.hide() - }); - - if (anchorRect.bottom + this.maxHeight < frameRect.bottom) { - // fits below -> show below - } - else if (anchorRect.top - this.maxHeight > frameRect.top) { - // fits above -> show above - showBelow = false; - } - else { - // doesn't fit above nor below -> show below - } - - var topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top); - - // position the menu - if (showBelow) { - // display the menu below the anchor - var anchorHeight = anchor.offsetHeight; - this.dom.menu.style.left = '0'; - this.dom.menu.style.top = topGap + anchorHeight + 'px'; - this.dom.menu.style.bottom = ''; - } - else { - // display the menu above the anchor - this.dom.menu.style.left = '0'; - this.dom.menu.style.top = ''; - this.dom.menu.style.bottom = '0px'; - } - - // attach the menu to the temporary, absolute anchor - // parent.insertBefore(this.dom.root, anchor); - this.dom.absoluteAnchor.appendChild(this.dom.root); - - // move focus to the first button in the context menu - this.selection = util.getSelection(); - this.anchor = anchor; - setTimeout(function () { - me.dom.focusButton.focus(); - }, 0); - - if (ContextMenu.visibleMenu) { - ContextMenu.visibleMenu.hide(); - } - ContextMenu.visibleMenu = this; -}; - -/** - * Hide the context menu if visible - */ -ContextMenu.prototype.hide = function () { - // remove temporary absolutely positioned anchor - if (this.dom.absoluteAnchor) { - this.dom.absoluteAnchor.destroy(); - delete this.dom.absoluteAnchor; - } - - // remove the menu from the DOM - if (this.dom.root.parentNode) { - this.dom.root.parentNode.removeChild(this.dom.root); - if (this.onClose) { - this.onClose(); - } - } - - if (ContextMenu.visibleMenu == this) { - ContextMenu.visibleMenu = undefined; - } -}; - -/** - * Expand a submenu - * Any currently expanded submenu will be hided. - * @param {Object} domItem - * @private - */ -ContextMenu.prototype._onExpandItem = function (domItem) { - var me = this; - var alreadyVisible = (domItem == this.expandedItem); - - // hide the currently visible submenu - var expandedItem = this.expandedItem; - if (expandedItem) { - //var ul = expandedItem.ul; - expandedItem.ul.style.height = '0'; - expandedItem.ul.style.padding = ''; - setTimeout(function () { - if (me.expandedItem != expandedItem) { - expandedItem.ul.style.display = ''; - util.removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected'); - } - }, 300); // timeout duration must match the css transition duration - this.expandedItem = undefined; - } - - if (!alreadyVisible) { - var ul = domItem.ul; - ul.style.display = 'block'; - var height = ul.clientHeight; // force a reflow in Firefox - setTimeout(function () { - if (me.expandedItem == domItem) { - var childsHeight = 0; - for (var i = 0; i < ul.childNodes.length; i++) { - childsHeight += ul.childNodes[i].clientHeight; - } - ul.style.height = childsHeight + 'px'; - ul.style.padding = '5px 10px'; - } - }, 0); - util.addClassName(ul.parentNode, 'jsoneditor-selected'); - this.expandedItem = domItem; - } -}; - -/** - * Handle onkeydown event - * @param {Event} event - * @private - */ -ContextMenu.prototype._onKeyDown = function (event) { - var target = event.target; - var keynum = event.which; - var handled = false; - var buttons, targetIndex, prevButton, nextButton; - - if (keynum == 27) { // ESC - // hide the menu on ESC key - - // restore previous selection and focus - if (this.selection) { - util.setSelection(this.selection); - } - if (this.anchor) { - this.anchor.focus(); - } - - this.hide(); - - handled = true; - } - else if (keynum == 9) { // Tab - if (!event.shiftKey) { // Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == buttons.length - 1) { - // move to first button - buttons[0].focus(); - handled = true; - } - } - else { // Shift+Tab - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - if (targetIndex == 0) { - // move to last button - buttons[buttons.length - 1].focus(); - handled = true; - } - } - } - else if (keynum == 37) { // Arrow Left - if (target.className == 'jsoneditor-expand') { - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - prevButton = buttons[targetIndex - 1]; - if (prevButton) { - prevButton.focus(); - } - } - handled = true; - } - else if (keynum == 38) { // Arrow Up - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - prevButton = buttons[targetIndex - 1]; - if (prevButton && prevButton.className == 'jsoneditor-expand') { - // skip expand button - prevButton = buttons[targetIndex - 2]; - } - if (!prevButton) { - // move to last button - prevButton = buttons[buttons.length - 1]; - } - if (prevButton) { - prevButton.focus(); - } - handled = true; - } - else if (keynum == 39) { // Arrow Right - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'jsoneditor-expand') { - nextButton.focus(); - } - handled = true; - } - else if (keynum == 40) { // Arrow Down - buttons = this._getVisibleButtons(); - targetIndex = buttons.indexOf(target); - nextButton = buttons[targetIndex + 1]; - if (nextButton && nextButton.className == 'jsoneditor-expand') { - // skip expand button - nextButton = buttons[targetIndex + 2]; - } - if (!nextButton) { - // move to first button - nextButton = buttons[0]; - } - if (nextButton) { - nextButton.focus(); - handled = true; - } - handled = true; - } - // TODO: arrow left and right - - if (handled) { - event.stopPropagation(); - event.preventDefault(); - } -}; - -module.exports = ContextMenu; + + +var createAbsoluteAnchor = __webpack_require__(25).createAbsoluteAnchor; +var util = __webpack_require__(0); +var translate = __webpack_require__(1).translate; + +/** + * A context menu + * @param {Object[]} items Array containing the menu structure + * TODO: describe structure + * @param {Object} [options] Object with options. Available options: + * {function} close Callback called when the + * context menu is being closed. + * @constructor + */ +function ContextMenu (items, options) { + this.dom = {}; + + var me = this; + var dom = this.dom; + this.anchor = undefined; + this.items = items; + this.eventListeners = {}; + this.selection = undefined; // holds the selection before the menu was opened + this.onClose = options ? options.close : undefined; + + // create root element + var root = document.createElement('div'); + root.className = 'jsoneditor-contextmenu-root'; + dom.root = root; + + // create a container element + var menu = document.createElement('div'); + menu.className = 'jsoneditor-contextmenu'; + dom.menu = menu; + root.appendChild(menu); + + // create a list to hold the menu items + var list = document.createElement('ul'); + list.className = 'jsoneditor-menu'; + menu.appendChild(list); + dom.list = list; + dom.items = []; // list with all buttons + + // create a (non-visible) button to set the focus to the menu + var focusButton = document.createElement('button'); + focusButton.type = 'button'; + dom.focusButton = focusButton; + var li = document.createElement('li'); + li.style.overflow = 'hidden'; + li.style.height = '0'; + li.appendChild(focusButton); + list.appendChild(li); + + function createMenuItems (list, domItems, items) { + items.forEach(function (item) { + if (item.type == 'separator') { + // create a separator + var separator = document.createElement('div'); + separator.className = 'jsoneditor-separator'; + li = document.createElement('li'); + li.appendChild(separator); + list.appendChild(li); + } + else { + var domItem = {}; + + // create a menu item + var li = document.createElement('li'); + list.appendChild(li); + + // create a button in the menu item + var button = document.createElement('button'); + button.type = 'button'; + button.className = item.className; + domItem.button = button; + if (item.title) { + button.title = item.title; + } + if (item.click) { + button.onclick = function (event) { + event.preventDefault(); + me.hide(); + item.click(); + }; + } + li.appendChild(button); + + // create the contents of the button + if (item.submenu) { + // add the icon to the button + var divIcon = document.createElement('div'); + divIcon.className = 'jsoneditor-icon'; + button.appendChild(divIcon); + var divText = document.createElement('div'); + divText.className = 'jsoneditor-text' + + (item.click ? '' : ' jsoneditor-right-margin'); + divText.appendChild(document.createTextNode(item.text)); + button.appendChild(divText); + + var buttonSubmenu; + if (item.click) { + // submenu and a button with a click handler + button.className += ' jsoneditor-default'; + + var buttonExpand = document.createElement('button'); + buttonExpand.type = 'button'; + domItem.buttonExpand = buttonExpand; + buttonExpand.className = 'jsoneditor-expand'; + buttonExpand.innerHTML = '
'; + li.appendChild(buttonExpand); + if (item.submenuTitle) { + buttonExpand.title = item.submenuTitle; + } + + buttonSubmenu = buttonExpand; + } + else { + // submenu and a button without a click handler + var divExpand = document.createElement('div'); + divExpand.className = 'jsoneditor-expand'; + button.appendChild(divExpand); + + buttonSubmenu = button; + } + + // attach a handler to expand/collapse the submenu + buttonSubmenu.onclick = function (event) { + event.preventDefault(); + me._onExpandItem(domItem); + buttonSubmenu.focus(); + }; + + // create the submenu + var domSubItems = []; + domItem.subItems = domSubItems; + var ul = document.createElement('ul'); + domItem.ul = ul; + ul.className = 'jsoneditor-menu'; + ul.style.height = '0'; + li.appendChild(ul); + createMenuItems(ul, domSubItems, item.submenu); + } + else { + // no submenu, just a button with clickhandler + button.innerHTML = '
' + + '
' + translate(item.text) + '
'; + } + + domItems.push(domItem); + } + }); + } + createMenuItems(list, this.dom.items, items); + + // TODO: when the editor is small, show the submenu on the right instead of inline? + + // calculate the max height of the menu with one submenu expanded + this.maxHeight = 0; // height in pixels + items.forEach(function (item) { + var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24; + me.maxHeight = Math.max(me.maxHeight, height); + }); +} + +/** + * Get the currently visible buttons + * @return {Array.} buttons + * @private + */ +ContextMenu.prototype._getVisibleButtons = function () { + var buttons = []; + var me = this; + this.dom.items.forEach(function (item) { + buttons.push(item.button); + if (item.buttonExpand) { + buttons.push(item.buttonExpand); + } + if (item.subItems && item == me.expandedItem) { + item.subItems.forEach(function (subItem) { + buttons.push(subItem.button); + if (subItem.buttonExpand) { + buttons.push(subItem.buttonExpand); + } + // TODO: change to fully recursive method + }); + } + }); + + return buttons; +}; + +// currently displayed context menu, a singleton. We may only have one visible context menu +ContextMenu.visibleMenu = undefined; + +/** + * Attach the menu to an anchor + * @param {HTMLElement} anchor Anchor where the menu will be attached as sibling. + * @param {HTMLElement} frame The root of the JSONEditor window + * @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute + */ +ContextMenu.prototype.show = function (anchor, frame, ignoreParent) { + this.hide(); + + // determine whether to display the menu below or above the anchor + var showBelow = true; + var parent = anchor.parentNode; + var anchorRect = anchor.getBoundingClientRect(); + var parentRect = parent.getBoundingClientRect(); + var frameRect = frame.getBoundingClientRect(); + + var me = this; + this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, function () { + me.hide() + }); + + if (anchorRect.bottom + this.maxHeight < frameRect.bottom) { + // fits below -> show below + } + else if (anchorRect.top - this.maxHeight > frameRect.top) { + // fits above -> show above + showBelow = false; + } + else { + // doesn't fit above nor below -> show below + } + + var topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top); + + // position the menu + if (showBelow) { + // display the menu below the anchor + var anchorHeight = anchor.offsetHeight; + this.dom.menu.style.left = '0'; + this.dom.menu.style.top = topGap + anchorHeight + 'px'; + this.dom.menu.style.bottom = ''; + } + else { + // display the menu above the anchor + this.dom.menu.style.left = '0'; + this.dom.menu.style.top = ''; + this.dom.menu.style.bottom = '0px'; + } + + // attach the menu to the temporary, absolute anchor + // parent.insertBefore(this.dom.root, anchor); + this.dom.absoluteAnchor.appendChild(this.dom.root); + + // move focus to the first button in the context menu + this.selection = util.getSelection(); + this.anchor = anchor; + setTimeout(function () { + me.dom.focusButton.focus(); + }, 0); + + if (ContextMenu.visibleMenu) { + ContextMenu.visibleMenu.hide(); + } + ContextMenu.visibleMenu = this; +}; + +/** + * Hide the context menu if visible + */ +ContextMenu.prototype.hide = function () { + // remove temporary absolutely positioned anchor + if (this.dom.absoluteAnchor) { + this.dom.absoluteAnchor.destroy(); + delete this.dom.absoluteAnchor; + } + + // remove the menu from the DOM + if (this.dom.root.parentNode) { + this.dom.root.parentNode.removeChild(this.dom.root); + if (this.onClose) { + this.onClose(); + } + } + + if (ContextMenu.visibleMenu == this) { + ContextMenu.visibleMenu = undefined; + } +}; + +/** + * Expand a submenu + * Any currently expanded submenu will be hided. + * @param {Object} domItem + * @private + */ +ContextMenu.prototype._onExpandItem = function (domItem) { + var me = this; + var alreadyVisible = (domItem == this.expandedItem); + + // hide the currently visible submenu + var expandedItem = this.expandedItem; + if (expandedItem) { + //var ul = expandedItem.ul; + expandedItem.ul.style.height = '0'; + expandedItem.ul.style.padding = ''; + setTimeout(function () { + if (me.expandedItem != expandedItem) { + expandedItem.ul.style.display = ''; + util.removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected'); + } + }, 300); // timeout duration must match the css transition duration + this.expandedItem = undefined; + } + + if (!alreadyVisible) { + var ul = domItem.ul; + ul.style.display = 'block'; + var height = ul.clientHeight; // force a reflow in Firefox + setTimeout(function () { + if (me.expandedItem == domItem) { + var childsHeight = 0; + for (var i = 0; i < ul.childNodes.length; i++) { + childsHeight += ul.childNodes[i].clientHeight; + } + ul.style.height = childsHeight + 'px'; + ul.style.padding = '5px 10px'; + } + }, 0); + util.addClassName(ul.parentNode, 'jsoneditor-selected'); + this.expandedItem = domItem; + } +}; + +/** + * Handle onkeydown event + * @param {Event} event + * @private + */ +ContextMenu.prototype._onKeyDown = function (event) { + var target = event.target; + var keynum = event.which; + var handled = false; + var buttons, targetIndex, prevButton, nextButton; + + if (keynum == 27) { // ESC + // hide the menu on ESC key + + // restore previous selection and focus + if (this.selection) { + util.setSelection(this.selection); + } + if (this.anchor) { + this.anchor.focus(); + } + + this.hide(); + + handled = true; + } + else if (keynum == 9) { // Tab + if (!event.shiftKey) { // Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == buttons.length - 1) { + // move to first button + buttons[0].focus(); + handled = true; + } + } + else { // Shift+Tab + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + if (targetIndex == 0) { + // move to last button + buttons[buttons.length - 1].focus(); + handled = true; + } + } + } + else if (keynum == 37) { // Arrow Left + if (target.className == 'jsoneditor-expand') { + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + prevButton = buttons[targetIndex - 1]; + if (prevButton) { + prevButton.focus(); + } + } + handled = true; + } + else if (keynum == 38) { // Arrow Up + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + prevButton = buttons[targetIndex - 1]; + if (prevButton && prevButton.className == 'jsoneditor-expand') { + // skip expand button + prevButton = buttons[targetIndex - 2]; + } + if (!prevButton) { + // move to last button + prevButton = buttons[buttons.length - 1]; + } + if (prevButton) { + prevButton.focus(); + } + handled = true; + } + else if (keynum == 39) { // Arrow Right + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'jsoneditor-expand') { + nextButton.focus(); + } + handled = true; + } + else if (keynum == 40) { // Arrow Down + buttons = this._getVisibleButtons(); + targetIndex = buttons.indexOf(target); + nextButton = buttons[targetIndex + 1]; + if (nextButton && nextButton.className == 'jsoneditor-expand') { + // skip expand button + nextButton = buttons[targetIndex + 2]; + } + if (!nextButton) { + // move to first button + nextButton = buttons[0]; + } + if (nextButton) { + nextButton.focus(); + handled = true; + } + handled = true; + } + // TODO: arrow left and right + + if (handled) { + event.stopPropagation(); + event.preventDefault(); + } +}; + +module.exports = ContextMenu; /***/ }), @@ -4801,437 +4801,445 @@ function errorSubclass(Subclass) { /* 9 */ /***/ (function(module, exports, __webpack_require__) { -var picoModal = __webpack_require__(26); -var translate = __webpack_require__(1).translate; -var util = __webpack_require__(0); - -/** - * Show advanced sorting modal - * @param {HTMLElement} container The container where to center - * the modal and create an overlay - * @param {JSON} json The JSON data to be sorted. - * @param {function} onSort Callback function, invoked with - * an object containing the selected - * path and direction - * @param {Object} options - * Available options: - * - {string} path The selected path - * - {'asc' | 'desc'} direction The selected direction - */ -function showSortModal (container, json, onSort, options) { - var paths = Array.isArray(json) - ? util.getChildPaths(json) - : ['']; - var selectedPath = options && options.path && util.contains(paths, options.path) - ? options.path - : paths[0] - var selectedDirection = options && options.direction || 'asc' - - var content = '
' + - '
' + translate('sort') + '
' + - '
' + - '' + - '' + - '' + - ' ' + - ' ' + - '' + - '' + - ' ' + - ' ' + - '' + - '' + - '' + - '' + - '' + - '
' + translate('sortFieldLabel') + ' ' + - '
' + - ' ' + - '
' + - '
' + translate('sortDirectionLabel') + ' ' + - '
' + - '' + - '' + - '
' + - '
' + - ' ' + - '
' + - '
' + - '
'; - - picoModal({ - parent: container, - content: content, - overlayClass: 'jsoneditor-modal-overlay', - modalClass: 'jsoneditor-modal jsoneditor-modal-sort' - }) - .afterCreate(function (modal) { - var form = modal.modalElem().querySelector('form'); - var ok = modal.modalElem().querySelector('#ok'); - var field = modal.modalElem().querySelector('#field'); - var direction = modal.modalElem().querySelector('#direction'); - - function preprocessPath(path) { - return (path === '') - ? '@' - : (path[0] === '.') - ? path.slice(1) - : path; - } - - paths.forEach(function (path) { - var option = document.createElement('option'); - option.text = preprocessPath(path); - option.value = path; - field.appendChild(option); - }); - - function setDirection(value) { - direction.value = value; - direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value; - } - - field.value = selectedPath || paths[0]; - setDirection(selectedDirection || 'asc'); - - direction.onclick = function (event) { - setDirection(event.target.getAttribute('data-value')); - }; - - ok.onclick = function (event) { - event.preventDefault(); - event.stopPropagation(); - - modal.close(); - - onSort({ - path: field.value, - direction: direction.value - }) - }; - - if (form) { // form is not available when JSONEditor is created inside a form - form.onsubmit = ok.onclick; - } - }) - .afterClose(function (modal) { - modal.destroy(); - }) - .show(); -} - -module.exports = showSortModal; +var picoModal = __webpack_require__(26); +var translate = __webpack_require__(1).translate; +var util = __webpack_require__(0); + +/** + * Show advanced sorting modal + * @param {HTMLElement} container The container where to center + * the modal and create an overlay + * @param {JSON} json The JSON data to be sorted. + * @param {function} onSort Callback function, invoked with + * an object containing the selected + * path and direction + * @param {Object} options + * Available options: + * - {string} path The selected path + * - {'asc' | 'desc'} direction The selected direction + */ +function showSortModal (container, json, onSort, options) { + var paths = Array.isArray(json) + ? util.getChildPaths(json) + : ['']; + var selectedPath = options && options.path && util.contains(paths, options.path) + ? options.path + : paths[0] + var selectedDirection = options && options.direction || 'asc' + + var content = '
' + + '
' + translate('sort') + '
' + + '
' + + '' + + '' + + '' + + ' ' + + ' ' + + '' + + '' + + ' ' + + ' ' + + '' + + '' + + '' + + '' + + '' + + '
' + translate('sortFieldLabel') + ' ' + + '
' + + ' ' + + '
' + + '
' + translate('sortDirectionLabel') + ' ' + + '
' + + '' + + '' + + '
' + + '
' + + ' ' + + '
' + + '
' + + '
'; + + picoModal({ + parent: container, + content: content, + overlayClass: 'jsoneditor-modal-overlay', + overlayStyles: { + backgroundColor: "rgb(1,1,1)", + opacity: 0.3 + }, + modalClass: 'jsoneditor-modal jsoneditor-modal-sort' + }) + .afterCreate(function (modal) { + var form = modal.modalElem().querySelector('form'); + var ok = modal.modalElem().querySelector('#ok'); + var field = modal.modalElem().querySelector('#field'); + var direction = modal.modalElem().querySelector('#direction'); + + function preprocessPath(path) { + return (path === '') + ? '@' + : (path[0] === '.') + ? path.slice(1) + : path; + } + + paths.forEach(function (path) { + var option = document.createElement('option'); + option.text = preprocessPath(path); + option.value = path; + field.appendChild(option); + }); + + function setDirection(value) { + direction.value = value; + direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value; + } + + field.value = selectedPath || paths[0]; + setDirection(selectedDirection || 'asc'); + + direction.onclick = function (event) { + setDirection(event.target.getAttribute('data-value')); + }; + + ok.onclick = function (event) { + event.preventDefault(); + event.stopPropagation(); + + modal.close(); + + onSort({ + path: field.value, + direction: direction.value + }) + }; + + if (form) { // form is not available when JSONEditor is created inside a form + form.onsubmit = ok.onclick; + } + }) + .afterClose(function (modal) { + modal.destroy(); + }) + .show(); +} + +module.exports = showSortModal; /***/ }), /* 10 */ /***/ (function(module, exports, __webpack_require__) { -var jmespath = __webpack_require__(5); -var picoModal = __webpack_require__(26); -var Selectr = __webpack_require__(79); -var translate = __webpack_require__(1).translate; -var stringifyPartial = __webpack_require__(80).stringifyPartial; -var util = __webpack_require__(0); -var MAX_PREVIEW_CHARACTERS = __webpack_require__(2).MAX_PREVIEW_CHARACTERS -var debounce = util.debounce; - -/** - * Show advanced filter and transform modal using JMESPath - * @param {HTMLElement} container The container where to center - * the modal and create an overlay - * @param {JSON} json The json data to be transformed - * @param {function} onTransform Callback invoked with the created - * query as callback - */ -function showTransformModal (container, json, onTransform) { - var value = json; - - var content = '