Merge branch 'develop' of https://github.com/josdejong/jsoneditor into develop
This commit is contained in:
commit
371a7b2d14
|
@ -1,4 +1,5 @@
|
|||
.idea
|
||||
.vscode
|
||||
build
|
||||
downloads
|
||||
node_modules
|
||||
|
|
51
HISTORY.md
51
HISTORY.md
|
@ -3,6 +3,57 @@
|
|||
https://github.com/josdejong/jsoneditor
|
||||
|
||||
|
||||
## 2017-11-22, version 5.11.0
|
||||
|
||||
- Upgraded dependencies
|
||||
- `ajv@5.4.0`
|
||||
- `brace@0.11.0`
|
||||
- Fixed dropdown for JSON Schema enums when defined inside pattern
|
||||
properties. Thanks @alquist.
|
||||
- Fixed code containing a non UTF-8 character. Thanks @alshakero.
|
||||
|
||||
|
||||
## 2017-11-15, version 5.10.1
|
||||
|
||||
- Some styling tweaks in the navigation bar and status bar.
|
||||
- Don't display status bar in `text` mode (which doesn't yet support
|
||||
row and col counts).
|
||||
|
||||
|
||||
## 2017-11-15, version 5.10.0
|
||||
|
||||
- Implemented a navigation bar showing the path. Thanks @meirotstein.
|
||||
- Implemented a status bar showing cursor location.
|
||||
Thanks @meirotstein.
|
||||
- Implemented repairing JSON objects containing left and right single
|
||||
and double quotes (which you get when typing a JSON object in Word)
|
||||
in `text` and `code` mode.
|
||||
- Implemented repairing JSON objects containing special white space
|
||||
characters like non-breaking space.
|
||||
- Upgraded dependency `ajv` to version `5.3.0`.
|
||||
- Fixed #481: A polyfill required `DocumentType` which is not defined
|
||||
in all environments.
|
||||
|
||||
|
||||
## 2017-09-16, version 5.9.6
|
||||
|
||||
- Fixed displaying a dropdown for enums inside composite schemas.
|
||||
Thanks @hachichaud.
|
||||
- Fixed #461: Urls opening twice on Firefox and Safari.
|
||||
|
||||
|
||||
## 2017-08-26, version 5.9.5
|
||||
|
||||
- Fixed a regression introduced in `v5.9.4`: after using the context
|
||||
menu once, it was not possible to set focus to an other input field
|
||||
anymore.
|
||||
|
||||
|
||||
## 2017-08-20, version 5.9.4
|
||||
|
||||
- Fixed #447: context menus not working in Shadow DOM. Thanks @tomalec.
|
||||
|
||||
|
||||
## 2017-07-24, version 5.9.3
|
||||
|
||||
- Fixed broken multi-selection (regression).
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# JSON Editor
|
||||
|
||||
[![Version](https://img.shields.io/npm/v/jsoneditor.svg)](https://www.npmjs.com/package/jsoneditor)
|
||||
[![Downloads](https://img.shields.io/npm/dm/jsoneditor.svg)](https://www.npmjs.com/package/jsoneditor)
|
||||
![Maintenance](https://img.shields.io/maintenance/yes/2017.svg)
|
||||
[![License](https://img.shields.io/github/license/josdejong/jsoneditor.svg)](https://github.com/josdejong/jsoneditor/blob/master/LICENSE)
|
||||
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fjosdejong%2Fjsoneditor.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fjosdejong%2Fjsoneditor?ref=badge_shield)
|
||||
|
||||
JSON Editor is a web-based tool to view, edit, format, and validate JSON.
|
||||
It has various modes such as a tree editor, a code editor, and a plain text
|
||||
editor.
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
height="144"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="jsoneditor-icons.svg">
|
||||
<title
|
||||
id="title6512">JSON Editor Icons</title>
|
||||
|
@ -39,12 +39,12 @@
|
|||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1028"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview4144"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="97.217248"
|
||||
inkscape:cy="59.950227"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="101.13921"
|
||||
inkscape:cy="34.512712"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
|
@ -710,49 +710,49 @@
|
|||
width="15.99999"
|
||||
y="101"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-0"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="105"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-7"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="109"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1"
|
||||
height="1.9999945"
|
||||
width="12"
|
||||
y="125"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="137"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="129"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4-3"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="133"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
|
@ -890,4 +890,10 @@
|
|||
id="path4300-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.80000001"
|
||||
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
|
||||
id="path4268"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccc" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -228,6 +228,16 @@ div.jsoneditor-outer {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.jsoneditor-outer.has-nav-bar {
|
||||
margin: -61px 0 0 0;
|
||||
padding: 61px 0 0 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-outer.has-status-bar {
|
||||
margin: -35px 0 -26px 0;
|
||||
padding: 35px 0 26px 0;
|
||||
}
|
||||
|
||||
textarea.jsoneditor-text,
|
||||
.ace-jsoneditor {
|
||||
min-height: 150px;
|
||||
|
@ -255,7 +265,7 @@ textarea.jsoneditor-text {
|
|||
|
||||
tr.jsoneditor-highlight,
|
||||
tr.jsoneditor-selected {
|
||||
background-color: #e6e6e6;
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
|
||||
tr.jsoneditor-selected button.jsoneditor-dragarea,
|
||||
|
@ -831,6 +841,10 @@ div.jsoneditor-menu > button.jsoneditor-format {
|
|||
background-position: -72px -120px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > button.jsoneditor-repair {
|
||||
background-position: -96px -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > div.jsoneditor-modes {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
|
@ -976,4 +990,76 @@ div.jsoneditor div.autocomplete.hint {
|
|||
color: #aaa;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
}
|
||||
div.jsoneditor-treepath {
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath div.jsoneditor-contextmenu-root {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath span.jsoneditor-treepath-element {
|
||||
margin: 1px;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath span.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 {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
div.jsoneditor-statusbar {
|
||||
line-height: 26px;
|
||||
height: 26px;
|
||||
margin-top: -26px;
|
||||
color: #808080;
|
||||
background-color: #ebebeb;
|
||||
border-top: 1px solid #d3d3d3;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-label {
|
||||
margin: 0 2px 0 4px;
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-val {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-count {
|
||||
margin-left: 4px;
|
||||
}
|
||||
div.jsoneditor-navigation-bar {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #808080;
|
||||
background-color: #ebebeb;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-navigation-bar.nav-bar-empty:after {
|
||||
content: 'Select a node ...';
|
||||
color: rgba(104, 104, 91, 0.56);
|
||||
position: absolute;
|
||||
margin-left: 5px;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -180,6 +180,13 @@ Constructs a new JSONEditor.
|
|||
- Can return an object `{startFrom: number, options: string[]}`. Here `startFrom` determines the start character from where the existing text will be replaced. `startFrom` is `0` by default, replacing the whole text.
|
||||
- Can return a `Promise` resolving one of the return types above to support asynchronously retrieving a list with options.
|
||||
|
||||
- `{boolean} navigationBar`
|
||||
|
||||
Adds navigation bar to the menu - the navigation bar visualize the current position on the tree structure as well as allows breadcrumbs navigation. True by default. Only applicable when `mode` is 'tree', 'form' or 'view'.
|
||||
|
||||
- `{boolean} statusBar`
|
||||
|
||||
Adds status bar to the buttom of the editor - the status bar shows the cursor position (currently only for 'code' `mode`) and a count of the selected charcters. True by default. Only applicable when `mode` is 'code' or 'text'.
|
||||
|
||||
### Methods
|
||||
|
||||
|
|
|
@ -131,7 +131,10 @@ gulp.task('bundle-css', ['mkdir'], function () {
|
|||
'src/css/contextmenu.css',
|
||||
'src/css/menu.css',
|
||||
'src/css/searchbox.css',
|
||||
'src/css/autocomplete.css'
|
||||
'src/css/autocomplete.css',
|
||||
'src/css/treepath.css',
|
||||
'src/css/statusbar.css',
|
||||
'src/css/navigationbar.css'
|
||||
])
|
||||
.pipe(concatCss(NAME + '.css'))
|
||||
.pipe(gulp.dest(DIST))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jsoneditor",
|
||||
"version": "5.9.3",
|
||||
"version": "5.11.0",
|
||||
"main": "./index",
|
||||
"description": "A web-based tool to view, edit, format, and validate JSON",
|
||||
"tags": [
|
||||
|
@ -23,8 +23,8 @@
|
|||
"test": "mocha test"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "5.2.0",
|
||||
"brace": "0.10.0",
|
||||
"ajv": "5.4.0",
|
||||
"brace": "0.11.0",
|
||||
"javascript-natural-sort": "0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
height="144"
|
||||
id="svg4136"
|
||||
version="1.1"
|
||||
inkscape:version="0.91 r"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="jsoneditor-icons.svg">
|
||||
<title
|
||||
id="title6512">JSON Editor Icons</title>
|
||||
|
@ -39,12 +39,12 @@
|
|||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1028"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview4144"
|
||||
showgrid="true"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="97.217248"
|
||||
inkscape:cy="59.950227"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="101.13921"
|
||||
inkscape:cy="34.512712"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
|
@ -710,49 +710,49 @@
|
|||
width="15.99999"
|
||||
y="101"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-0"
|
||||
height="1.9999945"
|
||||
width="15.99999"
|
||||
y="105"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-7"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="109"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1"
|
||||
height="1.9999945"
|
||||
width="12"
|
||||
y="125"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="137"
|
||||
x="76.000008"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4"
|
||||
height="1.9999945"
|
||||
width="10"
|
||||
y="129"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<rect
|
||||
id="rect4761-1-1-4-4-3"
|
||||
height="1.9999945"
|
||||
width="9"
|
||||
y="133"
|
||||
x="82"
|
||||
style="fill:#ffffff;fill-opacity:0.80000007;stroke:none;stroke-width:0" />
|
||||
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
|
@ -890,4 +890,10 @@
|
|||
id="path4300-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cccc" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.80000001"
|
||||
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
|
||||
id="path4268"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccccccc" />
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 36 KiB |
|
@ -210,6 +210,16 @@ div.jsoneditor-outer {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
div.jsoneditor-outer.has-nav-bar {
|
||||
margin: -61px 0 0 0;
|
||||
padding: 61px 0 0 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-outer.has-status-bar {
|
||||
margin: -35px 0 -26px 0;
|
||||
padding: 35px 0 26px 0;
|
||||
}
|
||||
|
||||
textarea.jsoneditor-text,
|
||||
.ace-jsoneditor {
|
||||
min-height: 150px;
|
||||
|
@ -239,7 +249,7 @@ textarea.jsoneditor-text {
|
|||
|
||||
tr.jsoneditor-highlight,
|
||||
tr.jsoneditor-selected {
|
||||
background-color: #e6e6e6;
|
||||
background-color: #d3d3d3;
|
||||
}
|
||||
|
||||
tr.jsoneditor-selected button.jsoneditor-dragarea,
|
||||
|
|
|
@ -71,6 +71,9 @@ div.jsoneditor-menu > button.jsoneditor-compact {
|
|||
div.jsoneditor-menu > button.jsoneditor-format {
|
||||
background-position: -72px -120px;
|
||||
}
|
||||
div.jsoneditor-menu > button.jsoneditor-repair {
|
||||
background-position: -96px -96px;
|
||||
}
|
||||
|
||||
div.jsoneditor-menu > div.jsoneditor-modes {
|
||||
display: inline-block;
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
div.jsoneditor-navigation-bar {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-bottom: 1px solid #d3d3d3;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: #808080;
|
||||
background-color: #ebebeb;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-navigation-bar.nav-bar-empty:after {
|
||||
content: 'Select a node ...';
|
||||
color: rgba(104, 104, 91, 0.56);
|
||||
position: absolute;
|
||||
margin-left: 5px;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
div.jsoneditor-statusbar {
|
||||
line-height: 26px;
|
||||
height: 26px;
|
||||
margin-top: -26px;
|
||||
color: #808080;
|
||||
background-color: #ebebeb;
|
||||
border-top: 1px solid #d3d3d3;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
font-size: 10pt;
|
||||
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-label {
|
||||
margin: 0 2px 0 4px;
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-val {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
div.jsoneditor-statusbar > .jsoneditor-curserinfo-count {
|
||||
margin-left: 4px;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
div.jsoneditor-treepath {
|
||||
padding: 0 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath div.jsoneditor-contextmenu-root {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath span.jsoneditor-treepath-element{
|
||||
margin: 1px;
|
||||
font-family: arial, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
div.jsoneditor-treepath span.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 {
|
||||
cursor:pointer;
|
||||
text-decoration:underline;
|
||||
}
|
|
@ -2,6 +2,15 @@
|
|||
|
||||
var util = require('./util');
|
||||
|
||||
/**
|
||||
* Node.getRootNode shim
|
||||
* @param {Node} node node to check
|
||||
* @return {Node} node's rootNode or `window` if there is ShadowDOM is not supported.
|
||||
*/
|
||||
function getRootNode(node){
|
||||
return node.getRootNode && node.getRootNode() || window;
|
||||
}
|
||||
|
||||
/**
|
||||
* A context menu
|
||||
* @param {Object[]} items Array containing the menu structure
|
||||
|
@ -202,8 +211,12 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
|||
|
||||
// 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()
|
||||
|
||||
if (contentWindow) {
|
||||
var anchorRect = anchor.getBoundingClientRect();
|
||||
|
||||
var contentRect = contentWindow.getBoundingClientRect();
|
||||
|
||||
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
|
||||
|
@ -218,29 +231,34 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
|||
}
|
||||
}
|
||||
|
||||
var leftGap = anchorRect.left - parentRect.left;
|
||||
var topGap = anchorRect.top - parentRect.top;
|
||||
|
||||
// position the menu
|
||||
if (showBelow) {
|
||||
// display the menu below the anchor
|
||||
var anchorHeight = anchor.offsetHeight;
|
||||
this.dom.menu.style.left = '0px';
|
||||
this.dom.menu.style.top = anchorHeight + 'px';
|
||||
this.dom.menu.style.left = leftGap + 'px';
|
||||
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 = '0px';
|
||||
this.dom.menu.style.top = '';
|
||||
this.dom.menu.style.left = leftGap + 'px';
|
||||
this.dom.menu.style.top = topGap + 'px';
|
||||
this.dom.menu.style.bottom = '0px';
|
||||
}
|
||||
|
||||
// find the root node of the page (window, or a shadow dom root element)
|
||||
this.rootNode = getRootNode(anchor);
|
||||
|
||||
// attach the menu to the parent of the anchor
|
||||
var parent = anchor.parentNode;
|
||||
parent.insertBefore(this.dom.root, parent.firstChild);
|
||||
|
||||
// create and attach event listeners
|
||||
var me = this;
|
||||
var list = this.dom.list;
|
||||
this.eventListeners.mousedown = util.addEventListener(window, 'mousedown', function (event) {
|
||||
this.eventListeners.mousedown = util.addEventListener(this.rootNode, 'mousedown', function (event) {
|
||||
// hide menu on click outside of the menu
|
||||
var target = event.target;
|
||||
if ((target != list) && !me._isChildOf(target, list)) {
|
||||
|
@ -249,7 +267,7 @@ ContextMenu.prototype.show = function (anchor, contentWindow) {
|
|||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
this.eventListeners.keydown = util.addEventListener(window, 'keydown', function (event) {
|
||||
this.eventListeners.keydown = util.addEventListener(this.rootNode, 'keydown', function (event) {
|
||||
me._onKeyDown(event);
|
||||
});
|
||||
|
||||
|
@ -284,7 +302,7 @@ ContextMenu.prototype.hide = function () {
|
|||
if (this.eventListeners.hasOwnProperty(name)) {
|
||||
var fn = this.eventListeners[name];
|
||||
if (fn) {
|
||||
util.removeEventListener(window, name, fn);
|
||||
util.removeEventListener(this.rootNode, name, fn);
|
||||
}
|
||||
delete this.eventListeners[name];
|
||||
}
|
||||
|
|
|
@ -82,7 +82,8 @@ function JSONEditor (container, options, json) {
|
|||
'ajv', 'schema', 'schemaRefs','templates',
|
||||
'ace', 'theme','autocomplete',
|
||||
'onChange', 'onEditable', 'onError', 'onModeChange',
|
||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
|
||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
||||
'sortObjectKeys', 'navigationBar', 'statusBar'
|
||||
];
|
||||
|
||||
Object.keys(options).forEach(function (option) {
|
||||
|
|
|
@ -539,6 +539,20 @@ Node.prototype.hideChilds = function() {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* Goes through the path from the node to the root and ensures that it is expanded
|
||||
*/
|
||||
Node.prototype.expandTo = function() {
|
||||
var currentNode = this.parent;
|
||||
while (currentNode) {
|
||||
if (!currentNode.expanded) {
|
||||
currentNode.expand();
|
||||
}
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a new child to the node.
|
||||
* Only applicable when Node value is of type array or object
|
||||
|
@ -1998,7 +2012,9 @@ Node.prototype._updateSchema = function () {
|
|||
//Locating the schema of the node and checking for any enum type
|
||||
if(this.editor && this.editor.options) {
|
||||
// find the part of the json schema matching this nodes path
|
||||
this.schema = Node._findSchema(this.editor.options.schema, this.getPath());
|
||||
this.schema = this.editor.options.schema
|
||||
? Node._findSchema(this.editor.options.schema, this.getPath())
|
||||
: null;
|
||||
if (this.schema) {
|
||||
this.enum = Node._findEnum(this.schema);
|
||||
}
|
||||
|
@ -2040,18 +2056,46 @@ Node._findEnum = function (schema) {
|
|||
*/
|
||||
Node._findSchema = function (schema, path) {
|
||||
var childSchema = schema;
|
||||
var foundSchema = childSchema;
|
||||
|
||||
for (var i = 0; i < path.length && childSchema; i++) {
|
||||
var key = path[i];
|
||||
if (typeof key === 'string' && childSchema.properties) {
|
||||
childSchema = childSchema.properties[key] || null
|
||||
}
|
||||
else if (typeof key === 'number' && childSchema.items) {
|
||||
childSchema = childSchema.items
|
||||
}
|
||||
var allSchemas = schema.oneOf || schema.anyOf || schema.allOf;
|
||||
if (!allSchemas) {
|
||||
allSchemas = [schema];
|
||||
}
|
||||
|
||||
return childSchema
|
||||
for (var j = 0; j < allSchemas.length; j++) {
|
||||
childSchema = allSchemas[j];
|
||||
|
||||
for (var i = 0; i < path.length && childSchema; i++) {
|
||||
var key = path[i];
|
||||
|
||||
if (typeof key === 'string' && childSchema.patternProperties && i == path.length - 1) {
|
||||
for (var prop in childSchema.patternProperties) {
|
||||
foundSchema = Node._findSchema(childSchema.patternProperties[prop], path.slice(i, path.length));
|
||||
}
|
||||
}
|
||||
else if (childSchema.items && childSchema.items.properties) {
|
||||
childSchema = childSchema.items.properties[key];
|
||||
if (childSchema) {
|
||||
foundSchema = Node._findSchema(childSchema, path.slice(i, path.length));
|
||||
}
|
||||
}
|
||||
else if (typeof key === 'string' && childSchema.properties) {
|
||||
childSchema = childSchema.properties[key] || null;
|
||||
if (childSchema) {
|
||||
foundSchema = Node._findSchema(childSchema, path.slice(i, path.length));
|
||||
}
|
||||
}
|
||||
else if (typeof key === 'number' && childSchema.items) {
|
||||
childSchema = childSchema.items;
|
||||
if (childSchema) {
|
||||
foundSchema = Node._findSchema(childSchema, path.slice(i, path.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return foundSchema
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -2284,8 +2328,10 @@ Node.prototype.onEvent = function (event) {
|
|||
break;
|
||||
|
||||
case 'click':
|
||||
if (event.ctrlKey || !this.editable.value) {
|
||||
if (event.ctrlKey && this.editable.value) {
|
||||
// if read-only, we use the regular click behavior of an anchor
|
||||
if (util.isUrl(this.value)) {
|
||||
event.preventDefault();
|
||||
window.open(this.value, '_blank');
|
||||
}
|
||||
}
|
||||
|
@ -3347,7 +3393,7 @@ Node.prototype.showContextMenu = function (anchor, onClose) {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// create insert button
|
||||
var insertSubmenu = [
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
'use strict';
|
||||
|
||||
var ContextMenu = require('./ContextMenu');
|
||||
|
||||
/**
|
||||
* Creates a component that visualize path selection in tree based editors
|
||||
* @param {HTMLElement} container
|
||||
* @constructor
|
||||
*/
|
||||
function TreePath(container) {
|
||||
if (container) {
|
||||
this.path = document.createElement('div');
|
||||
this.path.className = 'jsoneditor-treepath';
|
||||
container.appendChild(this.path);
|
||||
this.reset();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset component to initial status
|
||||
*/
|
||||
TreePath.prototype.reset = function () {
|
||||
this.path.innerHTML = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the component UI according to a given path objects
|
||||
* @param {Array<name: String, childs: Array>} pathObjs a list of path objects
|
||||
*
|
||||
*/
|
||||
TreePath.prototype.setPath = function (pathObjs) {
|
||||
var me = this;
|
||||
this.reset();
|
||||
if (pathObjs && pathObjs.length) {
|
||||
pathObjs.forEach(function (pathObj, idx) {
|
||||
var pathEl = document.createElement('span');
|
||||
var sepEl;
|
||||
pathEl.className = 'jsoneditor-treepath-element';
|
||||
pathEl.innerText = pathObj.name;
|
||||
pathEl.onclick = _onSegmentClick.bind(me, pathObj);
|
||||
|
||||
me.path.appendChild(pathEl);
|
||||
|
||||
if (pathObj.children.length) {
|
||||
sepEl = document.createElement('span');
|
||||
sepEl.className = 'jsoneditor-treepath-seperator';
|
||||
sepEl.innerHTML = '►';
|
||||
|
||||
sepEl.onclick = function () {
|
||||
var items = [];
|
||||
pathObj.children.forEach(function (child) {
|
||||
items.push({
|
||||
'text': child.name,
|
||||
'className': 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''),
|
||||
'click': _onContextMenuItemClick.bind(me, pathObj, child.name)
|
||||
});
|
||||
});
|
||||
var menu = new ContextMenu(items);
|
||||
menu.show(sepEl);
|
||||
};
|
||||
|
||||
me.path.appendChild(sepEl, me.container);
|
||||
}
|
||||
|
||||
if(idx === pathObjs.length - 1) {
|
||||
var leftRectPos = (sepEl || pathEl).getBoundingClientRect().left;
|
||||
if(me.path.offsetWidth < leftRectPos) {
|
||||
me.path.scrollLeft = leftRectPos;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _onSegmentClick(pathObj) {
|
||||
if (this.selectionCallback) {
|
||||
this.selectionCallback(pathObj);
|
||||
}
|
||||
};
|
||||
|
||||
function _onContextMenuItemClick(pathObj, selection) {
|
||||
if (this.contextMenuCallback) {
|
||||
this.contextMenuCallback(pathObj, selection);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* set a callback function for selection of path section
|
||||
* @param {Function} callback function to invoke when section is selected
|
||||
*/
|
||||
TreePath.prototype.onSectionSelected = function (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.selectionCallback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* set a callback function for selection of path section
|
||||
* @param {Function} callback function to invoke when section is selected
|
||||
*/
|
||||
TreePath.prototype.onContextMenuItemSelected = function (callback) {
|
||||
if (typeof callback === 'function') {
|
||||
this.contextMenuCallback = callback;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = TreePath;
|
|
@ -1,4 +1,4 @@
|
|||
'use strict';
|
||||
'use strict';
|
||||
|
||||
function completely(config) {
|
||||
config = config || {};
|
||||
|
|
|
@ -36,6 +36,11 @@ var DEFAULT_THEME = 'ace/theme/jsoneditor';
|
|||
textmode.create = function (container, options) {
|
||||
// read options
|
||||
options = options || {};
|
||||
|
||||
if(typeof options.statusBar === 'undefined') {
|
||||
options.statusBar = true;
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
|
||||
// indentation
|
||||
|
@ -131,6 +136,22 @@ textmode.create = function (container, options) {
|
|||
}
|
||||
};
|
||||
|
||||
// create repair button
|
||||
var buttonRepair = document.createElement('button');
|
||||
buttonRepair.type = 'button';
|
||||
buttonRepair.className = 'jsoneditor-repair';
|
||||
buttonRepair.title = 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.';
|
||||
this.menu.appendChild(buttonRepair);
|
||||
buttonRepair.onclick = function () {
|
||||
try {
|
||||
me.repair();
|
||||
me._onChange();
|
||||
}
|
||||
catch (err) {
|
||||
me._onError(err);
|
||||
}
|
||||
};
|
||||
|
||||
// create mode box
|
||||
if (this.options && this.options.modes && this.options.modes.length) {
|
||||
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch(mode) {
|
||||
|
@ -200,6 +221,7 @@ textmode.create = function (container, options) {
|
|||
|
||||
// register onchange event
|
||||
aceEditor.on('change', this._onChange.bind(this));
|
||||
aceEditor.on('changeSelection', this._onSelect.bind(this));
|
||||
}
|
||||
else {
|
||||
// load a plain text textarea
|
||||
|
@ -218,9 +240,65 @@ textmode.create = function (container, options) {
|
|||
// oninput is undefined. For IE8-
|
||||
this.textarea.onchange = this._onChange.bind(this);
|
||||
}
|
||||
|
||||
textarea.onselect = this._onSelect.bind(this);
|
||||
textarea.onmousedown = this._onMouseDown.bind(this);
|
||||
textarea.onblur = this._onBlur.bind(this);
|
||||
}
|
||||
|
||||
this.setSchema(this.options.schema, this.options.schemaRefs);
|
||||
if (options.statusBar) {
|
||||
if (this.mode === 'code') {
|
||||
util.addClassName(this.content, 'has-status-bar');
|
||||
|
||||
this.curserInfoElements = {};
|
||||
var statusBar = document.createElement('div');
|
||||
statusBar.className = 'jsoneditor-statusbar';
|
||||
this.frame.appendChild(statusBar);
|
||||
|
||||
var lnLabel = document.createElement('span');
|
||||
lnLabel.className = 'jsoneditor-curserinfo-label';
|
||||
lnLabel.innerText = 'Ln:';
|
||||
|
||||
var lnVal = document.createElement('span');
|
||||
lnVal.className = 'jsoneditor-curserinfo-val';
|
||||
lnVal.innerText = 0;
|
||||
|
||||
statusBar.appendChild(lnLabel);
|
||||
statusBar.appendChild(lnVal);
|
||||
|
||||
var colLabel = document.createElement('span');
|
||||
colLabel.className = 'jsoneditor-curserinfo-label';
|
||||
colLabel.innerText = 'Col:';
|
||||
|
||||
var colVal = document.createElement('span');
|
||||
colVal.className = 'jsoneditor-curserinfo-val';
|
||||
colVal.innerText = 0;
|
||||
|
||||
statusBar.appendChild(colLabel);
|
||||
statusBar.appendChild(colVal);
|
||||
|
||||
this.curserInfoElements.colVal = colVal;
|
||||
this.curserInfoElements.lnVal = lnVal;
|
||||
|
||||
var countLabel = document.createElement('span');
|
||||
countLabel.className = 'jsoneditor-curserinfo-label';
|
||||
countLabel.innerText = 'characters selected';
|
||||
countLabel.style.display = 'none';
|
||||
|
||||
var countVal = document.createElement('span');
|
||||
countVal.className = 'jsoneditor-curserinfo-count';
|
||||
countVal.innerText = 0;
|
||||
countVal.style.display = 'none';
|
||||
|
||||
this.curserInfoElements.countLabel = countLabel;
|
||||
this.curserInfoElements.countVal = countVal;
|
||||
|
||||
statusBar.appendChild(countVal);
|
||||
statusBar.appendChild(countLabel);
|
||||
}
|
||||
}
|
||||
|
||||
this.setSchema(this.options.schema, this.options.schemaRefs);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -244,6 +322,29 @@ textmode._onChange = function () {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle text selection
|
||||
* Calculates the cursor position and selection range and updates menu
|
||||
* @private
|
||||
*/
|
||||
textmode._onSelect = function () {
|
||||
if(this.options.statusBar) {
|
||||
if (this.textarea) {
|
||||
var selectionRange = util.getInputSelection(this.textarea);
|
||||
if (selectionRange.start !== selectionRange.end) {
|
||||
this._setSelectionCountDisplay(Math.abs(selectionRange.end - selectionRange.start));
|
||||
}
|
||||
} else if (this.aceEditor && this.curserInfoElements) {
|
||||
var curserPos = this.aceEditor.getCursorPosition();
|
||||
var selectedText = this.aceEditor.getSelectedText();
|
||||
|
||||
this.curserInfoElements.lnVal.innerText = curserPos.row + 1;
|
||||
this.curserInfoElements.colVal.innerText = curserPos.column + 1;
|
||||
this._setSelectionCountDisplay(selectedText.length);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for keydown. Handles shortcut keys
|
||||
* @param {Event} event
|
||||
|
@ -269,6 +370,39 @@ textmode._onKeyDown = function (event) {
|
|||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this._setSelectionCountDisplay();
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for mousedown.
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
textmode._onMouseDown = function (event) {
|
||||
this._setSelectionCountDisplay();
|
||||
};
|
||||
|
||||
/**
|
||||
* Event handler for blur.
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
textmode._onBlur = function (event) {
|
||||
this._setSelectionCountDisplay();
|
||||
};
|
||||
|
||||
textmode._setSelectionCountDisplay = function (value) {
|
||||
if (this.options.statusBar && this.curserInfoElements) {
|
||||
if (value && this.curserInfoElements && this.curserInfoElements.countVal) {
|
||||
this.curserInfoElements.countVal.innerText = value;
|
||||
this.curserInfoElements.countVal.style.display = 'inline';
|
||||
this.curserInfoElements.countLabel.style.display = 'inline';
|
||||
} else {
|
||||
this.curserInfoElements.countVal.style.display = 'none';
|
||||
this.curserInfoElements.countLabel.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -296,7 +430,7 @@ textmode.destroy = function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Compact the code in the formatter
|
||||
* Compact the code in the text editor
|
||||
*/
|
||||
textmode.compact = function () {
|
||||
var json = this.get();
|
||||
|
@ -305,7 +439,7 @@ textmode.compact = function () {
|
|||
};
|
||||
|
||||
/**
|
||||
* Format the code in the formatter
|
||||
* Format the code in the text editor
|
||||
*/
|
||||
textmode.format = function () {
|
||||
var json = this.get();
|
||||
|
@ -313,6 +447,15 @@ textmode.format = function () {
|
|||
this.setText(text);
|
||||
};
|
||||
|
||||
/**
|
||||
* Repair the code in the text editor
|
||||
*/
|
||||
textmode.repair = function () {
|
||||
var text = this.getText();
|
||||
var sanitizedText = util.sanitize(text);
|
||||
this.setText(sanitizedText);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set focus to the formatter
|
||||
*/
|
||||
|
@ -405,7 +548,6 @@ textmode.setText = function(jsonText) {
|
|||
|
||||
this.options.onChange = originalOnChange;
|
||||
}
|
||||
|
||||
// validate JSON schema
|
||||
this.validate();
|
||||
};
|
||||
|
@ -445,7 +587,7 @@ textmode.validate = function () {
|
|||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
if (errors.length > 0) {
|
||||
// limit the number of displayed errors
|
||||
var limit = errors.length > MAX_ERRORS;
|
||||
if (limit) {
|
||||
|
|
|
@ -5,6 +5,7 @@ var Highlighter = require('./Highlighter');
|
|||
var History = require('./History');
|
||||
var SearchBox = require('./SearchBox');
|
||||
var ContextMenu = require('./ContextMenu');
|
||||
var TreePath = require('./TreePath');
|
||||
var Node = require('./Node');
|
||||
var ModeSwitcher = require('./ModeSwitcher');
|
||||
var util = require('./util');
|
||||
|
@ -113,7 +114,8 @@ treemode._setOptions = function (options) {
|
|||
name: undefined, // field name of root node
|
||||
schema: null,
|
||||
schemaRefs: null,
|
||||
autocomplete: null
|
||||
autocomplete: null,
|
||||
navigationBar : true
|
||||
};
|
||||
|
||||
// copy all options
|
||||
|
@ -757,6 +759,17 @@ treemode._createFrame = function () {
|
|||
if (this.options.search) {
|
||||
this.searchBox = new SearchBox(this, this.menu);
|
||||
}
|
||||
|
||||
if(this.options.navigationBar) {
|
||||
// create second menu row for treepath
|
||||
this.navBar = document.createElement('div');
|
||||
this.navBar.className = 'jsoneditor-navigation-bar nav-bar-empty';
|
||||
this.frame.appendChild(this.navBar);
|
||||
|
||||
this.treePath = new TreePath(this.navBar);
|
||||
this.treePath.onSectionSelected(this._onTreePathSectionSelected.bind(this));
|
||||
this.treePath.onContextMenuItemSelected(this._onTreePathMenuItemSelected.bind(this));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -810,6 +823,10 @@ treemode._onEvent = function (event) {
|
|||
|
||||
var node = Node.getNodeFromTarget(event.target);
|
||||
|
||||
if (this.options && this.options.navigationBar && node && (event.type == 'keydown' || event.type == 'mousedown')) {
|
||||
this._updateTreePath(node.getNodePath());
|
||||
}
|
||||
|
||||
if (node && node.selected) {
|
||||
if (event.type == 'click') {
|
||||
if (event.target == node.dom.menu) {
|
||||
|
@ -850,6 +867,73 @@ treemode._onEvent = function (event) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update TreePath components
|
||||
* @param {Array<Node>} pathNodes list of nodes in path from root to selection
|
||||
* @private
|
||||
*/
|
||||
treemode._updateTreePath = function (pathNodes) {
|
||||
if (pathNodes && pathNodes.length) {
|
||||
util.removeClassName(this.navBar, 'nav-bar-empty');
|
||||
|
||||
var pathObjs = [];
|
||||
pathNodes.forEach(function (node) {
|
||||
var pathObj = {
|
||||
name: getName(node),
|
||||
node: node,
|
||||
children: []
|
||||
}
|
||||
if (node.childs && node.childs.length) {
|
||||
node.childs.forEach(function (childNode) {
|
||||
pathObj.children.push({
|
||||
name: getName(childNode),
|
||||
node: childNode
|
||||
});
|
||||
});
|
||||
}
|
||||
pathObjs.push(pathObj);
|
||||
});
|
||||
this.treePath.setPath(pathObjs);
|
||||
} else {
|
||||
util.addClassName(this.navBar, 'nav-bar-empty');
|
||||
}
|
||||
|
||||
function getName(node) {
|
||||
return node.field || (isNaN(node.index) ? node.type : node.index);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for tree path section selection - focus the selected node in the tree
|
||||
* @param {Object} pathObj path object that was represents the selected section node
|
||||
* @private
|
||||
*/
|
||||
treemode._onTreePathSectionSelected = function (pathObj) {
|
||||
if(pathObj && pathObj.node) {
|
||||
pathObj.node.expandTo();
|
||||
pathObj.node.focus();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for tree path menu item selection - rebuild the path accrding to the new selection and focus the selected node in the tree
|
||||
* @param {Object} pathObj path object that was represents the parent section node
|
||||
* @param {String} selection selected section child
|
||||
* @private
|
||||
*/
|
||||
treemode._onTreePathMenuItemSelected = function (pathObj, selection) {
|
||||
if(pathObj && pathObj.children.length) {
|
||||
var selectionObj = pathObj.children.find(function (obj) {
|
||||
return obj.name === selection;
|
||||
});
|
||||
if(selectionObj && selectionObj.node) {
|
||||
this._updateTreePath(selectionObj.node.getNodePath());
|
||||
selectionObj.node.expandTo();
|
||||
selectionObj.node.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
treemode._startDragDistance = function (event) {
|
||||
this.dragDistanceEvent = {
|
||||
initialTarget: event.target,
|
||||
|
@ -1162,6 +1246,9 @@ treemode._onKeyDown = function (event) {
|
|||
treemode._createTable = function () {
|
||||
var contentOuter = document.createElement('div');
|
||||
contentOuter.className = 'jsoneditor-outer';
|
||||
if(this.options.navigationBar) {
|
||||
util.addClassName(contentOuter, 'has-nav-bar');
|
||||
}
|
||||
this.contentOuter = contentOuter;
|
||||
|
||||
this.content = document.createElement('div');
|
||||
|
|
114
src/js/util.js
114
src/js/util.js
|
@ -50,6 +50,15 @@ exports.sanitize = function (jsString) {
|
|||
'\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); }
|
||||
|
@ -88,11 +97,11 @@ exports.sanitize = function (jsString) {
|
|||
}
|
||||
|
||||
// parse single or double quoted string
|
||||
function parseString(quote) {
|
||||
function parseString(endQuote) {
|
||||
chars.push('"');
|
||||
i++;
|
||||
var c = curr();
|
||||
while (i < jsString.length && c !== quote) {
|
||||
while (i < jsString.length && c !== endQuote) {
|
||||
if (c === '"' && prev() !== '\\') {
|
||||
// unescaped double quote, escape it
|
||||
chars.push('\\"');
|
||||
|
@ -118,7 +127,7 @@ exports.sanitize = function (jsString) {
|
|||
i++;
|
||||
c = curr();
|
||||
}
|
||||
if (c === quote) {
|
||||
if (c === endQuote) {
|
||||
chars.push('"');
|
||||
i++;
|
||||
}
|
||||
|
@ -154,8 +163,25 @@ exports.sanitize = function (jsString) {
|
|||
else if (c === '/' && next() === '/') {
|
||||
skipComment();
|
||||
}
|
||||
else if (c === '\'' || c === '"') {
|
||||
parseString(c);
|
||||
else if (c === '\u00A0' || (c >= '\u2000' && c <= '\u200A') || c === '\u202F' || c === '\u205F' || c === '\u3000') {
|
||||
// special white spaces (like non breaking space)
|
||||
chars.push(' ')
|
||||
i++
|
||||
}
|
||||
else if (c === quote) {
|
||||
parseString(quote);
|
||||
}
|
||||
else if (c === quoteDbl) {
|
||||
parseString(quoteDbl);
|
||||
}
|
||||
else if (c === graveAccent) {
|
||||
parseString(acuteAccent);
|
||||
}
|
||||
else if (c === quoteLeft) {
|
||||
parseString(quoteRight);
|
||||
}
|
||||
else if (c === quoteDblLeft) {
|
||||
parseString(quoteDblRight);
|
||||
}
|
||||
else if (/[a-zA-Z_$]/.test(c) && ['{', ','].indexOf(lastNonWhitespace()) !== -1) {
|
||||
// an unquoted object key (like a in '{a:2}')
|
||||
|
@ -790,10 +816,64 @@ exports.textDiff = function textDiff(oldText, newText) {
|
|||
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 start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;
|
||||
|
||||
if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
|
||||
start = el.selectionStart;
|
||||
end = 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 start and end 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) {
|
||||
start = end = len;
|
||||
} else {
|
||||
start = -textInputRange.moveStart("character", -len);
|
||||
start += normalizedValue.slice(0, start).split("\n").length - 1;
|
||||
|
||||
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
|
||||
end = len;
|
||||
} else {
|
||||
end = -textInputRange.moveEnd("character", -len);
|
||||
end += normalizedValue.slice(0, end).split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
start: start,
|
||||
end: end
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (typeof Element !== 'undefined') {
|
||||
// Polyfill for array remove
|
||||
(function (arr) {
|
||||
arr.forEach(function (item) {
|
||||
(function () {
|
||||
function polyfill (item) {
|
||||
if (item.hasOwnProperty('remove')) {
|
||||
return;
|
||||
}
|
||||
|
@ -806,8 +886,12 @@ if (typeof Element !== 'undefined') {
|
|||
this.parentNode.removeChild(this);
|
||||
}
|
||||
});
|
||||
});
|
||||
})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
|
||||
}
|
||||
|
||||
if (typeof Element !== 'undefined') { polyfill(Element.prototype); }
|
||||
if (typeof CharacterData !== 'undefined') { polyfill(CharacterData.prototype); }
|
||||
if (typeof DocumentType !== 'undefined') { polyfill(DocumentType.prototype); }
|
||||
})();
|
||||
}
|
||||
|
||||
|
||||
|
@ -817,4 +901,16 @@ if (!String.prototype.startsWith) {
|
|||
position = position || 0;
|
||||
return this.substr(position, searchString.length) === searchString;
|
||||
};
|
||||
}
|
||||
|
||||
// Polyfill for Array.find
|
||||
if (!Array.prototype.find) {
|
||||
Array.prototype.find = function(callback) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
var element = this[i];
|
||||
if ( callback.call(this, element, i, this) ) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,8 @@ describe('util', function () {
|
|||
|
||||
it('should replace JavaScript with JSON', function () {
|
||||
assert.equal(util.sanitize('{a:2}'), '{"a":2}');
|
||||
assert.equal(util.sanitize('{a: 2}'), '{"a": 2}');
|
||||
assert.equal(util.sanitize('{\n a: 2\n}'), '{\n "a": 2\n}');
|
||||
assert.equal(util.sanitize('{\'a\':2}'), '{"a":2}');
|
||||
assert.equal(util.sanitize('{a:\'foo\'}'), '{"a":"foo"}');
|
||||
assert.equal(util.sanitize('{a:\'foo\',b:\'bar\'}'), '{"a":"foo","b":"bar"}');
|
||||
|
@ -30,6 +32,11 @@ describe('util', function () {
|
|||
assert.equal(util.sanitize('"foo\\\'bar"'), '"foo\'bar"');
|
||||
});
|
||||
|
||||
it('should replace special white characters', function () {
|
||||
assert.equal(util.sanitize('{"a":\u00a0"foo\u00a0bar"}'), '{"a": "foo\u00a0bar"}');
|
||||
assert.equal(util.sanitize('{"a":\u2009"foo"}'), '{"a": "foo"}');
|
||||
});
|
||||
|
||||
it('should escape unescaped control characters', function () {
|
||||
assert.equal(util.sanitize('"hello\bworld"'), '"hello\\bworld"')
|
||||
assert.equal(util.sanitize('"hello\fworld"'), '"hello\\fworld"')
|
||||
|
@ -39,6 +46,12 @@ describe('util', function () {
|
|||
assert.equal(util.sanitize('{"value\n": "dc=hcm,dc=com"}'), '{"value\\n": "dc=hcm,dc=com"}')
|
||||
})
|
||||
|
||||
it('should replace left/right quotes', function () {
|
||||
assert.equal(util.sanitize('\u2018foo\u2019'), '"foo"')
|
||||
assert.equal(util.sanitize('\u201Cfoo\u201D'), '"foo"')
|
||||
assert.equal(util.sanitize('\u0060foo\u00B4'), '"foo"')
|
||||
})
|
||||
|
||||
it('remove comments', function () {
|
||||
assert.equal(util.sanitize('/* foo */ {}'), ' {}');
|
||||
assert.equal(util.sanitize('/* foo */ {}'), ' {}');
|
||||
|
|
Loading…
Reference in New Issue