Changed to new build system, removed flow

This commit is contained in:
jos 2017-12-26 20:40:31 +01:00
parent b011b196e1
commit 29fed7099f
120 changed files with 25979 additions and 30949 deletions

22
LICENSE
View File

@ -1,21 +1,7 @@
The MIT License (MIT)
Copyright 2015-2017 Jos de Jong
Copyright (c) 2011-2016 Jos de Jong
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -164,3 +164,8 @@ jsoneditor:
```
npm run watch:test
```
## License
MIT

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,12 @@
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-scripts": "0.8.4"
"react-scripts": "1.0.17"
},
"dependencies": {
"react": "15.4.1",
"react-dom": "15.4.1"
"jsoneditor": "file:../..",
"react": "16.2.0",
"react-dom": "16.2.0"
},
"scripts": {
"start": "react-scripts start",

View File

@ -7,7 +7,7 @@ import './App.css'
//
// import JSONEditor from 'jsoneditor/react'
//
import JSONEditor from '../../../react'
import JSONEditor from 'jsoneditor/lib/components/JSONEditor'
const json = {
@ -21,7 +21,7 @@ const json = {
class App extends Component {
state = {
text: JSON.stringify(json)
json
}
render() {
@ -30,7 +30,7 @@ class App extends Component {
<JSONEditor
mode="tree"
modes={['text', 'code', 'tree', 'form', 'view']}
text={this.state.text}
json={this.state.json}
onChange={this.onChange}
onChangeText={this.onChangeText}
/>

View File

@ -1,268 +0,0 @@
const fs = require('fs')
const gulp = require('gulp')
const gulpMultiProcess = require('gulp-multi-process')
const gutil = require('gulp-util')
const shell = require('gulp-shell')
const mkdirp = require('mkdirp')
const babel = require('gulp-babel')
const webpack = require('webpack')
const browserSync = require('browser-sync').create()
const WATCH = 'watch'
const WATCHING = process.argv[2] === WATCH
if (WATCHING) {
gutil.log('Watching src/*.')
gutil.log('The bundle ./dist/jsoneditor.js will be updated automatically ')
gutil.log('on changes in the source code this bundle will not be minified.')
gutil.log('Also, ./dist/minimalist code is not updated on changes.')
}
const NAME = 'jsoneditor.js'
const NAME_MINIMALIST = 'jsoneditor-minimalist.js'
const NAME_REACT = 'jsoneditor-react.js'
const NAME_REACT_MINIMALIST = 'jsoneditor-react-minimalist.js'
const ENTRY = './src/index.js'
const ENTRY_REACT = './src/components/JSONEditor.js'
const HEADER = './src/header.js'
const DIST = './dist'
const LIB = './lib'
const EMPTY = __dirname + '/src/utils/empty.js'
// generate banner with today's date and correct version
function createBanner() {
const today = gutil.date(new Date(), 'yyyy-mm-dd') // today, formatted as yyyy-mm-dd
const version = require('./package.json').version // math.js version
return String(fs.readFileSync(HEADER))
.replace('@@date', today)
.replace('@@version', version)
}
const bannerPlugin = new webpack.BannerPlugin(createBanner(), {
entryOnly: true,
raw: true
})
const minifyPlugin = new webpack.optimize.UglifyJsPlugin()
const excludeAcePlugin = new webpack.NormalModuleReplacementPlugin(new RegExp('/assets/ace$'), EMPTY)
const excludeAjvPlugin = new webpack.NormalModuleReplacementPlugin(new RegExp('^ajv$'), EMPTY)
const productionEnvPlugin = new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify('production')
}
})
const loaders = [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
{ test: /\.json$/, loader: 'json' },
{ test: /\.less$/, loaders: '!style!css!less!' },
{ test: /\.svg$/, loader: 'svg-url-loader' }
]
// TODO: see if preact can give the same sort of errors and warnings as react does, if so switch to preact for development too
const resolve = {
'alias': {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
}
// create a single instance of the compiler to allow caching
const compiler = webpack({
entry: ENTRY,
devtool: 'source-map',
debug: true,
cache: true,
bail: true,
output: {
library: 'jsoneditor',
libraryTarget: 'umd',
path: DIST,
filename: NAME
},
plugins: WATCHING
? [bannerPlugin]
: [bannerPlugin, productionEnvPlugin, minifyPlugin],
module: {
loaders
},
resolve: WATCHING ? null : resolve
})
// create a single instance of the compiler to allow caching
const compilerMinimalist = webpack({
entry: ENTRY,
devtool: 'source-map',
debug: true,
cache: true,
output: {
library: 'jsoneditor',
libraryTarget: 'umd',
path: DIST,
filename: NAME_MINIMALIST
},
plugins: [
bannerPlugin,
productionEnvPlugin,
excludeAcePlugin,
excludeAjvPlugin,
minifyPlugin
],
module: {
loaders
},
resolve: WATCHING ? null : resolve
})
const externals = {
'react': 'commonjs react'
}
// FIXME: get the react bundles working
// create a single instance of the compiler to allow caching
const compilerReact = webpack({
entry: ENTRY_REACT,
devtool: 'source-map',
debug: true,
cache: true,
bail: true,
output: {
library: 'JSONEditor',
libraryTarget: 'umd',
path: DIST,
filename: NAME_REACT
},
plugins: [
bannerPlugin,
productionEnvPlugin,
minifyPlugin
],
module: {
loaders
},
externals
})
// FIXME: get the react bundles working
// create a single instance of the compiler to allow caching
const compilerReactMinimalist = webpack({
entry: ENTRY_REACT,
devtool: 'source-map',
debug: true,
cache: true,
bail: true,
output: {
path: DIST,
filename: NAME_REACT_MINIMALIST
},
plugins: [
bannerPlugin,
productionEnvPlugin,
excludeAcePlugin,
excludeAjvPlugin,
minifyPlugin
],
module: {
loaders
},
externals
})
function handleCompilerCallback (err, stats) {
if (err) {
gutil.log(err.toString())
}
if (stats && stats.compilation && stats.compilation.errors) {
// output soft errors
stats.compilation.errors.forEach(function (err) {
gutil.log(err.toString())
})
}
}
function createBundleTask (compiler) {
return function (done) {
// update the banner contents (has a date in it which should stay up to date)
bannerPlugin.banner = createBanner()
compiler.run(function (err, stats) {
handleCompilerCallback(err, stats)
done()
})
}
}
// make dist folder
gulp.task('mkdir', function () {
mkdirp.sync(DIST)
mkdirp.sync(LIB)
})
// bundle javascript
gulp.task('bundle', ['mkdir'], createBundleTask(compiler))
// bundle minimalist version of javascript
gulp.task('bundle-minimalist', ['mkdir'], createBundleTask(compilerMinimalist))
// compile the source code into es5 code
gulp.task('compile-es5-lib', ['mkdir'], function () {
// TODO: compile *.less too
return gulp
.src([
'src/**/*.js',
'!src/flow/**/*.js',
'!src/resources/**/*.js'
])
.pipe(babel())
.pipe(gulp.dest(LIB));
})
// bundle react version
// TODO: remove bundle-react again? (use ./lib instead)
gulp.task('bundle-react', ['mkdir'], createBundleTask(compilerReact))
// bundle react minimalist version
// TODO: remove bundle-react-minimalist again? (use ./lib instead)
gulp.task('bundle-react-minimalist', ['mkdir'], createBundleTask(compilerReactMinimalist))
// TODO: zip file using archiver
const pkg = 'jsoneditor-' + require('./package.json').version + '.zip'
gulp.task('zip', shell.task([
'zip ' + pkg + ' ' + 'README.md LICENSE HISTORY.md index.html src dist docs examples -r '
]))
// execute all tasks and reload the browser afterwards
gulp.task('bundle-and-reload', ['bundle'], function (done) {
browserSync.reload();
done();
});
// The watch task (to automatically rebuild when the source code changes)
// Does only generate jsoneditor.js and jsoneditor.css, and copy the image
// Does NOT minify the code and does NOT generate the minimalist version
gulp.task(WATCH, ['bundle'], function() {
browserSync.init({
open: 'local',
server: '.',
startPath: '/src/develop.html',
minify: false
})
gulp.watch('src/**/*', ['bundle-and-reload'])
})
// The default task (called when you run `gulp`)
gulp.task('default', function(done) {
return gulpMultiProcess([
'bundle',
'bundle-minimalist',
'compile-es5-lib'
], done);
})

View File

@ -1 +0,0 @@
module.exports = require('./dist/jsoneditor')

12297
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,66 +17,56 @@
"url": "https://github.com/josdejong/jsoneditor.git"
},
"bugs": "https://github.com/josdejong/jsoneditor/issues",
"scripts": {
"start": "gulp watch",
"build": "gulp",
"flow": "flow; test $? -eq 0 -o $? -eq 2",
"test": "ava --verbose",
"test-eson": "ava --verbose test/eson.test.js",
"test-patch": "ava --verbose test/patchEson.test.js",
"test-actions": "ava --verbose test/actions.test.js",
"watch:test": "ava --verbose --watch"
},
"private": false,
"dependencies": {
"ajv": "4.10.4",
"brace": "0.9.1",
"console.table": "0.9.1",
"ajv": "5.5.2",
"brace": "0.11.0",
"javascript-natural-sort": "0.7.1",
"lodash": "4.17.4",
"prop-types": "15.5.10",
"react-hammerjs": "0.5.0"
"prop-types": "15.6.0",
"react-hammerjs": "1.0.1"
},
"peerDependencies": {
"react": "^16.0.0",
"react-dom": "^16.0.0"
},
"devDependencies": {
"ava": "0.17.0",
"babel-core": "6.23.1",
"babel-loader": "6.2.10",
"babel-plugin-transform-flow-strip-types": "6.21.0",
"babel-preset-es2015": "6.24.1",
"babel-preset-stage-2": "6.18.0",
"babel-preset-stage-3": "6.17.0",
"browser-sync": "2.18.6",
"css-loader": "0.26.1",
"deep-map": "1.5.0",
"flow-bin": "0.37.4",
"graceful-fs": "4.1.11",
"gulp": "3.9.1",
"gulp-babel": "6.1.2",
"gulp-multi-process": "0.0.4",
"gulp-shell": "0.5.2",
"gulp-util": "3.0.8",
"json-loader": "0.5.4",
"less": "2.7.2",
"less-loader": "2.2.3",
"mkdirp": "0.5.1",
"preact": "7.1.0",
"preact-compat": "3.9.4",
"react": "15.4.1",
"react-dom": "15.4.1",
"style-loader": "0.13.1",
"svg-url-loader": "1.1.0",
"webpack": "1.14.0"
"babel-cli": "6.26.0",
"babel-plugin-external-helpers": "6.22.0",
"babel-plugin-transform-class-properties": "6.24.1",
"babel-plugin-transform-object-rest-spread": "6.26.0",
"babel-plugin-transform-react-jsx": "6.24.1",
"babel-preset-env": "1.6.1",
"console.table": "0.9.1",
"cpy-cli": "1.0.1",
"css-loader": "0.28.7",
"node-sass-chokidar": "0.0.3",
"npm-run-all": "4.1.2",
"preact": "8.2.7",
"preact-compat": "3.17.0",
"react": "16.2.0",
"react-dom": "16.2.0",
"react-scripts": "1.0.17",
"rollup": "0.53.0",
"rollup-plugin-babel": "3.0.3",
"rollup-plugin-commonjs": "8.2.6",
"rollup-plugin-node-resolve": "3.0.0",
"rollup-plugin-postcss": "0.5.5",
"svg-url-loader": "2.3.1",
"webpack": "3.8.1"
},
"ava": {
"files": [
"test/**/*.test.js"
],
"source": [
"./src/**/*"
],
"require": [
"babel-register"
],
"concurrency": 4,
"babel": "inherit"
"scripts": {
"start": "npm-run-all -p watch-css start-js",
"build-css": "node-sass-chokidar src/ -o src/",
"watch-css": "npm run build-css && node-sass-chokidar src/ -o src/ --watch --recursive",
"start-js": "react-scripts start",
"build-js": "react-scripts build",
"copy-css-lib": "cpy '**/*.css' '**/*.svg' '../../lib' --cwd='src/jsoneditor' --parents",
"build-js-lib": "babel src/jsoneditor --out-dir lib --ignore spec.js,test.js",
"build-js-bundle": "webpack --config webpack.config.js",
"build-js-minimalist": "webpack --config webpack.config.minimalist.js",
"build": "npm-run-all build-css copy-css-lib build-js-lib build-js-bundle build-js-minimalist",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

40
public/index.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>JSONEditor demo</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

15
public/manifest.json Normal file
View File

@ -0,0 +1,15 @@
{
"short_name": "JSONEditor",
"name": "JSONEditor demo",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#3883fa",
"background_color": "#ffffff"
}

1
react.js vendored
View File

@ -1 +0,0 @@
module.exports = require('./lib/components/JSONEditor')

View File

@ -1,144 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function(acequire, exports, module) {
exports.isDark = false;
exports.cssClass = "ace-jsoneditor";
exports.cssText = ".ace-jsoneditor .ace_gutter {\
background: #ebebeb;\
color: #333\
}\
\
.ace-jsoneditor.ace_editor {\
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;\
line-height: 1.3;\
}\
.ace-jsoneditor .ace_print-margin {\
width: 1px;\
background: #e8e8e8\
}\
.ace-jsoneditor .ace_scroller {\
background-color: #FFFFFF\
}\
.ace-jsoneditor .ace_text-layer {\
color: gray\
}\
.ace-jsoneditor .ace_variable {\
color: #1a1a1a\
}\
.ace-jsoneditor .ace_cursor {\
border-left: 2px solid #000000\
}\
.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {\
border-left: 0px;\
border-bottom: 1px solid #000000\
}\
.ace-jsoneditor .ace_marker-layer .ace_selection {\
background: lightgray\
}\
.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {\
box-shadow: 0 0 3px 0px #FFFFFF;\
border-radius: 2px\
}\
.ace-jsoneditor .ace_marker-layer .ace_step {\
background: rgb(255, 255, 0)\
}\
.ace-jsoneditor .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #BFBFBF\
}\
.ace-jsoneditor .ace_marker-layer .ace_active-line {\
background: #FFFBD1\
}\
.ace-jsoneditor .ace_gutter-active-line {\
background-color : #dcdcdc\
}\
.ace-jsoneditor .ace_marker-layer .ace_selected-word {\
border: 1px solid lightgray\
}\
.ace-jsoneditor .ace_invisible {\
color: #BFBFBF\
}\
.ace-jsoneditor .ace_keyword,\
.ace-jsoneditor .ace_meta,\
.ace-jsoneditor .ace_support.ace_constant.ace_property-value {\
color: #AF956F\
}\
.ace-jsoneditor .ace_keyword.ace_operator {\
color: #484848\
}\
.ace-jsoneditor .ace_keyword.ace_other.ace_unit {\
color: #96DC5F\
}\
.ace-jsoneditor .ace_constant.ace_language {\
color: darkorange\
}\
.ace-jsoneditor .ace_constant.ace_numeric {\
color: red\
}\
.ace-jsoneditor .ace_constant.ace_character.ace_entity {\
color: #BF78CC\
}\
.ace-jsoneditor .ace_invalid {\
color: #FFFFFF;\
background-color: #FF002A;\
}\
.ace-jsoneditor .ace_fold {\
background-color: #AF956F;\
border-color: #000000\
}\
.ace-jsoneditor .ace_storage,\
.ace-jsoneditor .ace_support.ace_class,\
.ace-jsoneditor .ace_support.ace_function,\
.ace-jsoneditor .ace_support.ace_other,\
.ace-jsoneditor .ace_support.ace_type {\
color: #C52727\
}\
.ace-jsoneditor .ace_string {\
color: green\
}\
.ace-jsoneditor .ace_comment {\
color: #BCC8BA\
}\
.ace-jsoneditor .ace_entity.ace_name.ace_tag,\
.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {\
color: #606060\
}\
.ace-jsoneditor .ace_markup.ace_underline {\
text-decoration: underline\
}\
.ace-jsoneditor .ace_indent-guide {\
background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y\
}";
var dom = acequire("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});

13
src/demo/Demo.css Normal file
View File

@ -0,0 +1,13 @@
body, input, select {
font-family: sans-serif;
font-size: 11pt; }
.demo .menu {
margin: 20px 0; }
.demo .menu button, .demo .menu label {
margin-right: 10px; }
.demo .contents {
height: 400px;
width: 100%;
max-width: 800px; }

191
src/demo/Demo.js Normal file
View File

@ -0,0 +1,191 @@
import React, { Component } from 'react'
import JSONEditor from '../jsoneditor/index.react'
import { setIn } from '../jsoneditor/utils/immutabilityHelpers'
import { largeJson } from './resources/largeJson'
import './Demo.css'
const schema = {
"title": "Example Schema",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"gender": {
"enum": ["male", "female"]
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0
}
},
"required": ["firstName", "lastName"]
}
const json = {
'array': [1, 2, 3],
'emptyArray': [],
'emptyObject': {},
'firstName': null,
'boolean': true,
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd', 'e': [{"first": true}, {"second": true}]},
'string': 'Hello World',
'unicode': 'A unicode character: \u260E',
'url': 'http://jsoneditoronline.org'
}
function expandAll (path) {
return true
}
class App extends Component {
constructor (props) {
super(props)
this.state = {
logging: false,
options: {
json,
schema: null,
name: 'myObject',
onPatch: this.handlePatch,
onPatchText: this.handlePatchText,
onChange: this.handleChange,
onChangeText: this.handleChangeText,
onChangeMode: this.handleChangeMode,
onError: this.handleError,
mode: 'tree',
modes: ['text', 'code', 'tree', 'form', 'view'],
keyBindings: {
compact: ['Ctrl+\\', 'Command+\\', 'Ctrl+Alt+1', 'Command+Option+1'],
format: ['Ctrl+Shift+\\', 'Command+Shift+\\', 'Ctrl+Alt+2', 'Command+Option+2'],
duplicate: ['Ctrl+D', 'Ctrl+Shift+D', 'Command+D', 'Command+Shift+D']
},
indentation: 4,
escapeUnicode: true,
history: true,
search: true,
expand: expandAll
}
}
}
render() {
return <div className="demo">
<div className="menu">
<button onClick={this.handleSetJson}>Set JSON</button>
<button onClick={this.handleGetJson}>Get JSON</button>
<label>mode:
<select value={this.state.options.mode} onChange={this.handleSetMode}>
<option value="text">text</option>
<option value="code">code</option>
<option value="tree">tree</option>
<option value="form">form</option>
<option value="view">view</option>
</select>
</label>
<label>
<input type="checkbox"
value={this.state.options.schema !== null}
onChange={this.handleToggleJSONSchema} /> JSON Schema
</label>
<label>
<input type="checkbox"
value={this.state.logging}
onChange={this.handleToggleLogging} /> Log events
</label>
</div>
<div className="contents">
<JSONEditor {...this.state.options} />
</div>
</div>
}
handleSetJson = () => {
this.setState({
options: setIn(this.state.options, ['json'], largeJson)
})
}
handleGetJson = () => {
// FIXME: get updating json in the state working
const json = this.state.options.json
alert(JSON.stringify(json, null, 2))
}
handleSetMode = (event) => {
const mode = event.target.value
this.setState({
options: setIn(this.state.options, ['mode'], mode)
})
}
handleToggleLogging = (event) => {
const logging = event.target.checked
this.setState({ logging })
}
handleToggleJSONSchema = (event) => {
const s = event.target.checked ? schema : null
this.setState({
options: setIn(this.state.options, ['schema'], s)
})
}
handleChange = (json) => {
this.log('onChange json=', json)
// FIXME: update the json in the state (after JSONEditor neatly updates it instead of generating new json every time
// this.setState({
// options: setIn(this.state.options, ['json'], json)
// })
}
handleChangeText = (text) => {
this.log('onChangeText', text)
}
handlePatch = (patch, revert) => {
this.log('onPatch patch=', patch, ', revert=', revert)
window.patch = patch
window.revert = revert
}
handlePatchText = (patch, revert) => {
// FIXME: implement onPatchText
this.log('onPatchText patch=', patch, ', revert=', revert)
}
handleChangeMode = (mode, prevMode) => {
this.log('switched mode from', prevMode, 'to', mode)
this.setState({
options: setIn(this.state.options, ['mode'], mode)
})
}
handleError = (err) => {
console.error(err)
alert(err)
}
log (...args) {
if (this.state.logging) {
console.log(...args)
}
}
}
export default App

22
src/demo/Demo.scss Normal file
View File

@ -0,0 +1,22 @@
body, input, select {
font-family: sans-serif;
font-size: 11pt;
}
.demo {
.menu {
margin: 20px 0;
button, label {
margin-right: 10px;
}
}
.contents {
height: 400px;
width: 100%;
max-width : 800px;
}
}

10
src/demo/Demo.test.js Normal file
View File

@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Demo from './Demo';
test('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<Demo />, div);
});

View File

@ -1,4 +1,4 @@
const largeJson = {
export const largeJson = {
"version": "1.0",
"encoding": "UTF-8",
"feed": {

View File

@ -2,12 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Develop JSONEditor</title>
<title>JSONEditor vanilla demo</title>
<!-- For IE and Edge -->
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.min.js"></script>-->
<script src="../dist/jsoneditor.js"></script>
<script src="../../dist/jsoneditor.js"></script>
<script src="./resources/largeJson.js"></script>
@ -16,7 +16,7 @@
font-family: sans-serif;
font-size: 11pt;
}
#container {
#editor {
height: 400px;
width: 100%;
max-width : 800px;
@ -41,7 +41,7 @@
</label>
<label>
<input type="checkbox" id="logEvents" > Log events
<input type="checkbox" id="logEvents" /> Log events
</label>
</p>
<div id="container"></div>
@ -53,7 +53,7 @@
const error = console.error
return function(exception) {
if ((exception + '').indexOf('Warning: A component is `contentEditable`') != 0) {
if ((exception + '').indexOf('Warning: A component is `contentEditable`') !== 0) {
error.apply(console, arguments)
}
}

View File

@ -1,2 +0,0 @@
// @flow
declare export default string

View File

@ -1,277 +1,6 @@
import React, { createElement as h, Component } from 'react'
import ReactDOM, { render, unmountComponentAtNode} from 'react-dom'
import JSONEditor from './components/JSONEditor'
import CodeMode from './components/CodeMode'
import TextMode from './components/TextMode'
import TreeMode from './components/TreeMode'
import { compileJSONPointer, parseJSONPointer } from './eson'
import React from 'react';
import ReactDOM from 'react-dom';
import '!style!css!less!./jsoneditor.less'
import Demo from './demo/Demo';
const modes = {
code: CodeMode,
form: TreeMode,
text: TextMode,
tree: TreeMode,
view: TreeMode
}
/**
* Create a new json editor
* @param {HTMLElement} container
* @param {Options} options
* @return {Object}
* @constructor
*/
function jsoneditor (container, options = {}) {
if (arguments.length > 2) {
throw new Error ('Passing JSON via the constructor has been deprecated. ' +
'Please pass JSON via editor.set(json).')
}
const editor = {
isJSONEditor: true,
_container: container,
_options: options,
_schema: null,
_modes: modes,
_mode: null,
_component: null
}
/**
* Set JSON object in editor
* @param {Object | Array | string | number | boolean | null} json JSON data
* @param {SetOptions} [options]
*/
editor.set = function (json, options = {}) {
// TODO: remove options from editor.set, move them to global options instead
editor._component.set(json, options)
}
/**
* Get JSON from the editor
* @returns {Object | Array | string | number | boolean | null} json
*/
editor.get = function () {
return editor._component.get()
}
/**
* Set a string containing a JSON document
* @param {string} text
*/
editor.setText = function (text) {
editor._component.setText(text)
}
/**
* Get the JSON document as text
* @return {string} text
*/
editor.getText = function () {
return editor._component.getText()
}
/**
* Format the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.format = function () {
const formatted = TextMode.format(editor._component.getText(), TextMode.getIndentation(this.props))
editor._component.setText(formatted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Compact the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.compact = function () {
const compacted = TextMode.compact(editor._component.getText())
editor._component.setText(compacted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Set a JSON schema for validation of the JSON object.
* To remove the schema, call JSONEditor.setSchema(null)
* @param {Object | null} schema
*/
editor.setSchema = function (schema) {
editor._schema = schema || null
editor._component.setSchema(schema)
}
/**
* Expand one or multiple objects or arrays.
*
* Example usage:
*
* // expand one item at a specific path
* editor.expand(['foo', 1, 'bar'])
*
* // expand all items nested at a maximum depth of 2
* editor.expand(function (path) {
* return path.length <= 2
* })
*
* @param {Path | function (path: Path) : boolean} callback
*/
editor.expand = function (callback) {
editor._component.expand(callback)
}
/**
* Collapse one or multiple objects or arrays
*
* Example usage:
*
* // collapse one item at a specific path
* editor.collapse(['foo', 1, 'bar'])
*
* // collapse all items nested deeper than 2
* editor.collapse(function (path) {
* return path.length > 2
* })
*
* @param {Path | function (path: Path) : boolean} callback
*/
editor.collapse = function (callback) {
editor._component.collapse(callback)
}
/**
* Apply a JSONPatch to the current JSON document
* @param {Array} actions JSONPatch actions
* @return {Array} Returns a JSONPatch to revert the applied patch
*/
editor.patch = function (actions) {
return editor._component.patch(actions)
}
/**
* Change the mode of the editor
* @param {'tree' | 'text'} mode
*/
editor.setMode = function (mode) {
// TODO: strongly simplify .setMode, no error handling or logic here
if (mode === editor._mode) {
// mode stays the same. do nothing
return
}
let success = false
let initialChildCount = editor._container.children.length
let component = null
try {
// find the constructor for the selected mode
const constructor = editor._modes[mode]
if (!constructor) {
throw new Error('Unknown mode "' + mode + '". ' +
'Choose from: ' + Object.keys(modes).join(', '))
}
function handleChangeMode (mode) {
// we execute editor.setMode on the next tick, after the click event
// has been finished. This is a workaround for preact which does not
// neatly replace a rendered app whilst the event is still being handled.
setTimeout(() => {
const prevMode = editor._mode
editor.setMode(mode)
if (editor._options.onChangeMode) {
editor._options.onChangeMode(mode, prevMode)
}
})
}
function handleError (err) {
if (editor._options && editor._options.onError) {
editor._options.onError(err)
}
else {
console.error(err)
}
}
// create new component
component = render(
h(constructor, {
...options,
mode,
onChangeMode: handleChangeMode,
onError: handleError
}),
editor._container)
// apply JSON schema (if any)
try {
component.setSchema(editor._schema)
}
catch (err) {
handleError(err)
}
// set JSON (this can throw an error)
const text = editor._component ? editor._component.getText() : '{}'
component.setText(text)
// when setText didn't fail, we will reach this point
success = true
}
catch (err) {
console.error(err)
}
finally {
if (success) {
editor._mode = mode
editor._component = component
}
else {
// TODO: fall back to text mode when loading code mode failed?
// remove the just created component if an error occurred during construction
// (for example when construction or setText failed)
const childCount = editor._container.children.length
if (childCount !== initialChildCount) {
editor._container.removeChild(editor._container.lastChild)
}
}
}
}
/**
* Remove the editor from the DOM and clean up workers
*/
editor.destroy = function () {
unmountComponentAtNode(editor._container)
}
const mode = options && options.mode || (options.modes && options.modes[0]) || 'tree';
editor.setMode(mode)
return editor
}
// expose util functions
jsoneditor.utils = {
compileJSONPointer,
parseJSONPointer
}
// expose React component
jsoneditor.JSONEditor = JSONEditor
// expose React itself
jsoneditor.React = React
jsoneditor.ReactDOM = ReactDOM
module.exports = jsoneditor
ReactDOM.render(<Demo />, document.getElementById('root'));

View File

@ -6,10 +6,10 @@ import {
compileJSONPointer, esonToJson, findNextProp,
pathsFromSelection, findRootPath, findSelectionIndices
} from './eson'
import { cloneWithSymbols, getIn, setIn } from './utils/immutabilityHelpers'
import { getIn } from './utils/immutabilityHelpers'
import { findUniqueName } from './utils/stringUtils'
import { isObject, stringConvert } from './utils/typeUtils'
import { compareAsc, compareDesc, strictShallowEqual } from './utils/arrayUtils'
import { compareAsc, compareDesc } from './utils/arrayUtils'
/**
@ -138,7 +138,7 @@ export function duplicate (eson, selection) {
*
* @param {ESON} eson
* @param {Path} path
* @param {Array.<{name?: string, value: JSONType, type?: ESONType}>} values
* @param {Array.<{name?: string, value: JSON, type?: ESONType}>} values
* @return {Array}
*/
export function insertBefore (eson, path, values) { // TODO: find a better name and define datastructure for values
@ -146,7 +146,7 @@ export function insertBefore (eson, path, values) { // TODO: find a better name
const parent = getIn(eson, parentPath)
if (parent[META].type === 'Array') {
const startIndex = parseInt(last(path))
const startIndex = parseInt(last(path), 10)
return values.map((entry, offset) => ({
op: 'add',
path: compileJSONPointer(parentPath.concat(startIndex + offset)),

View File

@ -0,0 +1,81 @@
'use strict'
import { sort } from './actions'
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
import {esonToJson, expandOne, jsonToEson, META} from './eson'
import {patchEson} from './patchEson'
// TODO: test changeValue
// TODO: test changeProperty
// TODO: test changeType (or cleanup the function)
// TODO: test duplicate
// TODO: test insertBefore
// TODO: test replace
// TODO: test append
// TODO: test remove
// TODO: test removeAll
it('sort root Array', () => {
const eson = jsonToEson([1,3,2])
assertDeepEqualEson(patchEson(eson, sort(eson, [])).data, jsonToEson([1,2,3]))
assertDeepEqualEson(patchEson(eson, sort(eson, [], 'asc')).data, jsonToEson([1,2,3]))
assertDeepEqualEson(patchEson(eson, sort(eson, [], 'desc')).data, jsonToEson([3,2,1]))
})
it('sort nested Array', () => {
const eson = jsonToEson({arr: [4,1,8,5,3,9,2,7,6]})
const actual = patchEson(eson, sort(eson, ['arr'])).data
const expected = jsonToEson({arr: [1,2,3,4,5,6,7,8,9]})
assertDeepEqualEson(actual, expected)
})
it('sort nested Array reverse order', () => {
// no order provided -> order ascending, but if nothing changes, order descending
const eson = jsonToEson({arr: [1,2,3,4,5,6,7,8,9]})
const actual = patchEson(eson, sort(eson, ['arr'])).data
const expected = jsonToEson({arr: [9,8,7,6,5,4,3,2,1]})
assertDeepEqualEson(actual, expected)
// id's and META should be the same
expect(actual.arr[META].id).toEqual(eson.arr[META].id)
expect(actual.arr[7][META].id).toEqual(eson.arr[1][META].id)
})
it('sort root Object', () => {
const eson = jsonToEson({c: 2, b: 3, a:4})
expect(patchEson(eson, sort(eson, [])).data[META].props).toEqual(['a', 'b', 'c'])
expect(patchEson(eson, sort(eson, [], 'asc')).data[META].props).toEqual(['a', 'b', 'c'])
expect(patchEson(eson, sort(eson, [], 'desc')).data[META].props).toEqual(['c', 'b', 'a'])
})
it('sort nested Object', () => {
const eson = jsonToEson({obj: {c: 2, b: 3, a:4}})
eson.obj[META].expanded = true
eson.obj.c[META].expanded = true
const actual = patchEson(eson, sort(eson, ['obj'])).data
// should keep META data
expect(actual.obj[META].props).toEqual(['a', 'b', 'c'])
expect(actual.obj[META].expanded).toEqual(true)
expect(actual.obj.c[META].expanded).toEqual(true)
expect(actual.obj[META].id).toEqual(eson.obj[META].id)
expect(actual.obj.a[META].id).toEqual(eson.obj.a[META].id)
expect(actual.obj.b[META].id).toEqual(eson.obj.b[META].id)
expect(actual.obj.c[META].id).toEqual(eson.obj.c[META].id)
// asc, desc
expect(patchEson(eson, sort(eson, ['obj'])).data.obj[META].props).toEqual(['a', 'b', 'c'])
expect(patchEson(eson, sort(eson, ['obj'], 'asc')).data.obj[META].props).toEqual(['a', 'b', 'c'])
expect(patchEson(eson, sort(eson, ['obj'], 'desc')).data.obj[META].props).toEqual(['c', 'b', 'a'])
})
it('sort nested Object (larger)', () => {
const eson = jsonToEson({obj: {h:1, c:1, e:1, d:1, g:1, b:1, a:1, f:1}})
const actual = patchEson(eson, sort(eson, ['obj'])).data
expect(actual.obj[META].props).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
})

View File

@ -0,0 +1,144 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
window.ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function(acequire, exports, module) {
exports.isDark = false
exports.cssClass = 'ace-jsoneditor'
exports.cssText = `.ace-jsoneditor .ace_gutter {
background: #ebebeb;
color: #333
}
.ace-jsoneditor.ace_editor {
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
line-height: 1.3;
}
.ace-jsoneditor .ace_print-margin {
width: 1px;
background: #e8e8e8
}
.ace-jsoneditor .ace_scroller {
background-color: #FFFFFF
}
.ace-jsoneditor .ace_text-layer {
color: gray
}
.ace-jsoneditor .ace_variable {
color: #1a1a1a
}
.ace-jsoneditor .ace_cursor {
border-left: 2px solid #000000
}
.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {
border-left: 0px;
border-bottom: 1px solid #000000
}
.ace-jsoneditor .ace_marker-layer .ace_selection {
background: lightgray
}
.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {
box-shadow: 0 0 3px 0px #FFFFFF;
border-radius: 2px
}
.ace-jsoneditor .ace_marker-layer .ace_step {
background: rgb(255, 255, 0)
}
.ace-jsoneditor .ace_marker-layer .ace_bracket {
margin: -1px 0 0 -1px;
border: 1px solid #BFBFBF
}
.ace-jsoneditor .ace_marker-layer .ace_active-line {
background: #FFFBD1
}
.ace-jsoneditor .ace_gutter-active-line {
background-color : #dcdcdc
}
.ace-jsoneditor .ace_marker-layer .ace_selected-word {
border: 1px solid lightgray
}
.ace-jsoneditor .ace_invisible {
color: #BFBFBF
}
.ace-jsoneditor .ace_keyword,
.ace-jsoneditor .ace_meta,
.ace-jsoneditor .ace_support.ace_constant.ace_property-value {
color: #AF956F
}
.ace-jsoneditor .ace_keyword.ace_operator {
color: #484848
}
.ace-jsoneditor .ace_keyword.ace_other.ace_unit {
color: #96DC5F
}
.ace-jsoneditor .ace_constant.ace_language {
color: darkorange
}
.ace-jsoneditor .ace_constant.ace_numeric {
color: red
}
.ace-jsoneditor .ace_constant.ace_character.ace_entity {
color: #BF78CC
}
.ace-jsoneditor .ace_invalid {
color: #FFFFFF;
background-color: #FF002A;
}
.ace-jsoneditor .ace_fold {
background-color: #AF956F;
border-color: #000000
}
.ace-jsoneditor .ace_storage,
.ace-jsoneditor .ace_support.ace_class,
.ace-jsoneditor .ace_support.ace_function,
.ace-jsoneditor .ace_support.ace_other,
.ace-jsoneditor .ace_support.ace_type {
color: #C52727
}
.ace-jsoneditor .ace_string {
color: green
}
.ace-jsoneditor .ace_comment {
color: #BCC8BA
}
.ace-jsoneditor .ace_entity.ace_name.ace_tag,
.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {
color: #606060
}
.ace-jsoneditor .ace_markup.ace_underline {
text-decoration: underline
}
.ace-jsoneditor .ace_indent-guide {
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==") right repeat-y
}`
var dom = acequire('../lib/dom')
dom.importCssString(exports.cssText, exports.cssClass)
})

View File

@ -13,7 +13,7 @@ var parser = (function(){
var $0 = $$.length - 1;
switch (yystate) {
case 1: // replace escaped characters with actual character
this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
this.$ = yytext.replace(/\\(\\|")/g, "$1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
@ -30,8 +30,8 @@ var parser = (function(){
break;
case 5:this.$ = false;
break;
case 6:return this.$ = $$[$0-1];
break;
case 6:
return this.$ = $$[$0-1];
case 13:this.$ = {};
break;
case 14:this.$ = $$[$0-1];
@ -50,6 +50,8 @@ var parser = (function(){
break;
case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
break;
default:
break;
}
},
table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],
@ -75,7 +77,7 @@ var parser = (function(){
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
if (typeof this.lexer.yylloc == 'undefined')
if (typeof this.lexer.yylloc === 'undefined')
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
@ -99,7 +101,7 @@ var parser = (function(){
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
var symbol, preErrorSymbol, state, action, r, yyval={},p,len,newState, expected;
while (true) {
// retreive state number from top of stack
state = stack[stack.length-1];
@ -115,60 +117,60 @@ var parser = (function(){
}
// handle parse error
_handle_error:
if (typeof action === 'undefined' || !action.length || !action[0]) {
if (!recovering) {
// Report error
expected = [];
for (p in table[state]) if (this.terminals_[p] && p > 2) {
expected.push("'"+this.terminals_[p]+"'");
}
var errStr = '';
if (this.lexer.showPosition) {
errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
(symbol == 1 /*EOF*/ ? "end of input" :
("'"+(this.terminals_[symbol] || symbol)+"'"));
}
this.parseError(errStr,
{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
if (typeof action === 'undefined' || !action.length || !action[0]) {
// just recovered from another error
if (recovering == 3) {
if (symbol == EOF) {
throw new Error(errStr || 'Parsing halted.');
}
// discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
symbol = lex();
}
// try to recover from error
while (1) {
// check for error recovery rule in this state
if ((TERROR.toString()) in table[state]) {
break;
}
if (state == 0) {
throw new Error(errStr || 'Parsing halted.');
}
popStack(1);
state = stack[stack.length-1];
}
preErrorSymbol = symbol; // save the lookahead token
symbol = TERROR; // insert generic error symbol as new lookahead
state = stack[stack.length-1];
action = table[state] && table[state][TERROR];
recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
if (!recovering) {
// Report error
expected = [];
for (p in table[state]) if (this.terminals_[p] && p > 2) {
expected.push("'"+this.terminals_[p]+"'");
}
var errStr = '';
if (this.lexer.showPosition) {
errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
(symbol === 1 /*EOF*/ ? "end of input" :
("'"+(this.terminals_[symbol] || symbol)+"'"));
}
this.parseError(errStr,
{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
// just recovered from another error
if (recovering === 3) {
if (symbol === EOF) {
throw new Error(errStr || 'Parsing halted.');
}
// discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
symbol = lex();
}
// try to recover from error
while (1) {
// check for error recovery rule in this state
if ((TERROR.toString()) in table[state]) {
break;
}
if (state === 0) {
throw new Error(errStr || 'Parsing halted.');
}
popStack(1);
state = stack[stack.length-1];
}
preErrorSymbol = symbol; // save the lookahead token
symbol = TERROR; // insert generic error symbol as new lookahead
state = stack[stack.length-1];
action = table[state] && table[state][TERROR];
recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
}
// this shouldn't happen, unless resolve defaults are off
if (action[0] instanceof Array && action.length > 1) {
@ -235,11 +237,11 @@ var parser = (function(){
case 3: // accept
return true;
default:
return true;
}
}
return true;
}};
/* Jison generated lexer */
var lexer = (function(){
@ -308,7 +310,6 @@ var parser = (function(){
match,
tempMatch,
index,
col,
lines;
if (!this._more) {
this.yytext = '';
@ -374,35 +375,22 @@ var parser = (function(){
lexer.options = {};
lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 0: break; /* skip whitespace */
case 1:return 6
break;
case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
break;
case 3:return 17
break;
case 4:return 18
break;
case 5:return 23
break;
case 6:return 24
break;
case 7:return 22
break;
case 8:return 21
break;
case 9:return 10
break;
case 10:return 11
break;
case 11:return 8
break;
case 12:return 14
break;
case 13:return 'INVALID'
default:
break;
}
};

View File

@ -153,6 +153,8 @@ const jumper = () => {
element = document.querySelector(target)
stop = top(element)
break
default:
}
// resolve scroll distance, accounting for offset
@ -169,6 +171,8 @@ const jumper = () => {
case 'function':
duration = options.duration(distance)
break
default:
}
// start the loop if we're not already scrolling

View File

@ -1,7 +1,6 @@
// @flow
import { createElement as h, Component } from 'react'
import ace from '../assets/ace'
import PropTypes from 'prop-types'
/**
* Usage:
@ -14,6 +13,12 @@ import ace from '../assets/ace'
*
*/
export default class Ace extends Component {
static propTypes = {
value: PropTypes.string.isRequired,
indentation: PropTypes.number
}
aceEditor = null
settingValue = false // Used to prevent Ace from emitting onChange event whilst we're setting a value programmatically
@ -73,14 +78,15 @@ export default class Ace extends Component {
}
}
componentWillReceiveProps (nextProps: {value: string, indentation?: number}) {
componentWillReceiveProps (nextProps) {
if (this.aceEditor && nextProps.value !== this.aceEditor.getValue()) {
this.settingValue = true
this.aceEditor.setValue(nextProps.value, -1)
this.settingValue = false
}
if (this.aceEditor && nextProps.indentation != undefined) {
if (this.aceEditor &&
(typeof nextProps.indentation === 'number' || typeof nextProps.indentation === 'string')) {
this.aceEditor.getSession().setTabSize(this.props.indentation)
}

View File

@ -1,6 +1,4 @@
// @flow
import { createElement as h, Component } from 'react'
import { createElement as h } from 'react'
import TextMode from './TextMode'
import Ace from './Ace'
@ -30,13 +28,12 @@ import Ace from './Ace'
*
*/
export default class CodeMode extends TextMode {
constructor (props: {options: {onLoadAce: Function, indentation: number}}) {
super(props)
this.state = {
text: '{}',
compiledSchema: null
}
// TODO: work out propTypes
state = {
text: '{}',
compiledSchema: null
}
render () {

View File

@ -1,12 +1,19 @@
// @flow
import { createElement as h, Component } from 'react'
import PropTypes from 'prop-types'
import { createElement as h, PureComponent } from 'react'
import CodeMode from './CodeMode'
import TextMode from './TextMode'
import TreeMode from './TreeMode'
export default class JSONEditor extends Component {
import './jsoneditor.css'
const DEFAULT_MODE = 'tree'
export default class JSONEditor extends PureComponent {
// TODO: work out prop types
// static propTypes = {
// ...
// }
static modeConstructors = {
code: CodeMode,
form: TreeMode,
@ -15,18 +22,14 @@ export default class JSONEditor extends Component {
view: TreeMode
}
state = {
mode: 'tree'
}
render () {
const mode = this.state.mode // We use mode from state, not from props!
const mode = this.props.mode || DEFAULT_MODE // We use mode from state, not from props!
const ModeConstructor = JSONEditor.modeConstructors[mode]
if (!ModeConstructor) {
// TODO: show an on screen error instead of throwing an error?
throw new Error('Unknown mode "' + mode + '". ' +
'Choose from: ' + Object.keys(this.props.modes).join(', '))
'Choose from: ' + Object.keys(this.props.modes).join(', ')) // FIXME: this.props.modes may be undefined
}
return h(ModeConstructor, {
@ -37,19 +40,7 @@ export default class JSONEditor extends Component {
})
}
componentWillMount () {
if (this.props.mode) {
this.setState({ mode: this.props.mode })
}
}
componentWillReceiveProps (nextProps: {mode?: string}) {
if (nextProps.mode !== this.props.mode) {
this.setState({ mode: nextProps.mode })
}
}
handleError = (err: Error) => {
handleError = (err) => {
if (this.props.onError) {
this.props.onError(err)
}
@ -58,17 +49,11 @@ export default class JSONEditor extends Component {
}
}
handleChangeMode = (mode: string) => {
const prevMode = this.state.mode
this.setState({ mode })
handleChangeMode = (mode) => {
console.log('changeMode', mode, this.props.onChangeMode)
if (this.props.onChangeMode) {
this.props.onChangeMode(mode, prevMode)
this.props.onChangeMode(mode, this.props.mode)
}
}
}
JSONEditor.propTypes = {
mode: PropTypes.string
}

View File

@ -1,5 +1,3 @@
// @flow weak
import { createElement as h, PureComponent } from 'react'
import initial from 'lodash/initial'
@ -10,8 +8,6 @@ import { getInnerText, insideRect, findParentWithAttribute } from '../utils/domU
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
import { compileJSONPointer, META, SELECTED, SELECTED_END, SELECTED_AFTER, SELECTED_BEFORE } from '../eson'
import type { ESON, SearchResultStatus, Path } from '../types'
// TODO: rename SELECTED, SELECTED_END, etc to AREA_*? It's used for both selection and hovering
const SELECTED_CLASS_NAMES = {
[SELECTED]: ' jsoneditor-selected',
@ -254,7 +250,15 @@ export default class JSONNode extends PureComponent {
}
// TODO: simplify the method renderProperty
renderProperty (prop?: String, index?: number, eson: ESON, options: {escapeUnicode: boolean, isPropertyEditable: (Path) => boolean}) {
/**
* Render a property field of a JSONNode
* @param {string} [prop]
* @param {string} [index]
* @param {ESON} eson
* @param {{escapeUnicode: boolean, isPropertyEditable: function(Path) : boolean}} options
*/
renderProperty (prop, index, eson, options) {
const isIndex = typeof index === 'number'
const isProp = typeof prop === 'string'
@ -281,6 +285,7 @@ export default class JSONNode extends PureComponent {
key: 'property',
className: 'jsoneditor-property' + emptyClassName + searchClassName,
contentEditable: 'true',
suppressContentEditableWarning: true,
spellCheck: 'false',
onBlur: this.handleChangeProperty
}, escapedPropName)
@ -311,6 +316,7 @@ export default class JSONNode extends PureComponent {
className: JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
JSONNode.getSearchResultClass(searchResult),
contentEditable: 'true',
suppressContentEditableWarning: true,
spellCheck: 'false',
onBlur: this.handleChangeValue,
onInput: this.updateValueStyling,
@ -420,8 +426,9 @@ export default class JSONNode extends PureComponent {
/**
* Get the css style given a search result type
* @param {SearchResultStatus} [searchResultStatus]
*/
static getSearchResultClass (searchResultStatus?: SearchResultStatus) {
static getSearchResultClass (searchResultStatus) {
if (searchResultStatus === 'active') {
return ' jsoneditor-highlight-active'
}

View File

@ -1,9 +1,5 @@
// @flow
import JSONNode from './JSONNode'
import type { ESON, Path } from '../types'
/**
* JSONNodeForm
*
@ -21,11 +17,17 @@ export default class JSONNodeForm extends JSONNode {
return null
}
// render a readonly property
renderProperty (prop?: String, index?: number, eson: ESON, options: {escapeUnicode: boolean, isPropertyEditable: (Path) => boolean}) {
/**
* Render a property field of a JSONNode
* @param {string} [prop]
* @param {string} [index]
* @param {ESON} eson
* @param {{escapeUnicode: boolean, isPropertyEditable: function(Path) : boolean}} options
*/
renderProperty (prop, index, eson, options) {
const formOptions = Object.assign({}, options, { isPropertyEditable })
return JSONNode.prototype.renderProperty.call(this, prop, index, data, formOptions)
return JSONNode.prototype.renderProperty.call(this, prop, index, eson, formOptions)
}
}

View File

@ -1,5 +1,3 @@
// @flow weak
import { createElement as h, Component } from 'react'
import Ajv from 'ajv'
import { parseJSON } from '../utils/jsonUtils'
@ -45,22 +43,17 @@ const AJV_OPTIONS = {
*
*/
export default class TextMode extends Component {
state: Object
state = {
text: '{}',
compiledSchema: null
}
keyDownActions = {
'format': (event) => this.handleCompact(),
'compact': (event) => this.handleFormat()
}
constructor (props) {
super(props)
this.state = {
text: '{}',
compiledSchema: null
}
}
componentWillMount () {
this.applyProps(this.props, {})
}
@ -222,7 +215,7 @@ export default class TextMode extends Component {
*/
setSchema (schema) {
if (schema) {
const ajv = this.props.ajv || Ajv && Ajv(AJV_OPTIONS)
const ajv = this.props.ajv || (Ajv && Ajv(AJV_OPTIONS))
if (!ajv) {
throw new Error('Cannot validate JSON: ajv not available. ' +
@ -242,9 +235,11 @@ export default class TextMode extends Component {
/**
* Get the configured indentation. When not configured, returns the default value 2
* @param {{indentation?: number}} props
* @return {number}
*/
static getIndentation (props?: {indentation?: number}) : number {
return props && props.indentation || 2
static getIndentation (props) {
return (props && props.indentation) || 2
}
static format (text, indentation) {
@ -308,8 +303,9 @@ export default class TextMode extends Component {
/**
* Apply new text to the state, and emit an onChangeText event if there is a change
* @param {string} text
*/
handleChangeText = (text: string) => {
handleChangeText = (text) => {
if (this.props.onChangeText && text !== this.state.text) {
const appliedText = this.setText(text)
this.props.onChangeText(appliedText)
@ -364,8 +360,10 @@ export default class TextMode extends Component {
/**
* Set a string containing a JSON document
* @param {string} text
* @return {string}
*/
setText (text: string) : string {
setText (text) {
const normalizedText = this.props.escapeUnicode
? escapeUnicodeChars(text)
: text

View File

@ -1,5 +1,3 @@
// @flow weak
import { createElement as h, Component } from 'react'
import isEqual from 'lodash/isEqual'
import reverse from 'lodash/reverse'
@ -8,7 +6,7 @@ import Hammer from 'react-hammerjs'
import jump from '../assets/jump.js/src/jump'
import Ajv from 'ajv'
import { getIn, setIn, updateIn } from '../utils/immutabilityHelpers'
import { getIn, updateIn } from '../utils/immutabilityHelpers'
import { parseJSON } from '../utils/jsonUtils'
import { enrichSchemaError } from '../utils/schemaUtils'
import {
@ -36,11 +34,6 @@ import {
import { createFindKeyBinding } from '../utils/keyBindings'
import { KEY_BINDINGS } from '../constants'
import type {
ESON, ESONPatch, Selection, ESONPointer,
Path
} from '../types'
const AJV_OPTIONS = {
allErrors: true,
verbose: true,
@ -52,19 +45,21 @@ const SEARCH_DEBOUNCE = 300 // milliseconds
const SCROLL_DURATION = 400 // milliseconds
export default class TreeMode extends Component {
id: number
state: Object
id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
// TODO: define propTypes
keyDownActions = null
constructor (props) {
super(props)
const json = this.props.json || {}
const expandCallback = this.props.expand || TreeMode.expandRoot
const eson = expand(jsonToEson(json), expandCallback)
// const json = this.props.json || {}
// const expandCallback = this.props.expand || TreeMode.expandRoot
// const eson = expand(jsonToEson(json), expandCallback)
this.id = Math.round(Math.random() * 1e5) // TODO: create a uuid here?
const json = {}
const eson = jsonToEson(json)
this.keyDownActions = {
'up': this.moveUp,
@ -88,9 +83,10 @@ export default class TreeMode extends Component {
json,
eson,
history: [eson],
history: [],
historyIndex: 0,
// TODO: use an event emitter instead? (like with vue.js)
events: {
onChangeProperty: this.handleChangeProperty,
onChangeValue: this.handleChangeValue,
@ -157,11 +153,17 @@ export default class TreeMode extends Component {
}
// Apply json
if (nextProps.json !== currentProps.json) {
if (nextProps.json !== this.state.json) {
// FIXME: merge meta data from existing eson
// FIXME: keep state as is
const expandCallback = this.props.expand || TreeMode.expandRoot
const json = nextProps.json
const eson = expand(jsonToEson(json), expandCallback)
this.setState({
json: nextProps.json,
eson: jsonToEson(nextProps.json) // FIXME: how to handle expand?
json,
eson
})
// TODO: cleanup
// this.patch([{
@ -541,11 +543,20 @@ export default class TreeMode extends Component {
})
}
/**
* Handle sorting a path
* @param {Path} path
* @param {string | null} [order]
*/
handleSort = (path, order = null) => {
this.handlePatch(sort(this.state.eson, path, order))
}
handleSelect = (selection: Selection) => {
/**
* Set selection
* @param {Selection} selection
*/
handleSelect = (selection) => {
this.setState({ selection })
}
@ -710,7 +721,12 @@ export default class TreeMode extends Component {
}
}
findDataPathFromElement (element: Element) : Path | null {
/**
* Find JSON path from an HTML element
* @param {Element} element
* @return {Path | null}
*/
findDataPathFromElement (element) {
const base = findBaseNode(element)
const attr = base && base.getAttribute && base.getAttribute('data-path')
@ -718,14 +734,25 @@ export default class TreeMode extends Component {
return attr ? parseJSONPointer(attr.replace(/\/-$/, '')) : null
}
findESONPointerFromElement (element: Element) : ESONPointer {
/**
* Find ESON pointer from an HTML element
* @param {Element} element
* @return {ESONPointer | null}
*/
findESONPointerFromElement (element) {
const path = this.findDataPathFromElement(element)
const area = element && element.getAttribute && element.getAttribute('data-area') || null
const area = (element && element.getAttribute && element.getAttribute('data-area')) || null
return path ? { path, area } : null
}
selectionFromESONPointer (pointer: ESONPointer) : Selection {
/**
* Get selection from an ESON pointer
* @param {ESONPointer} pointer
* @return {Selection}
*/
selectionFromESONPointer (pointer) {
// FIXME: does pointer have .area === 'after' ? if so adjust type defs
if (pointer.area === 'after') {
return {after: pointer.path}
}
@ -755,8 +782,11 @@ export default class TreeMode extends Component {
/**
* Emit an onChange event when there is a listener for it.
* @private
* @param {ESONPatch} patch
* @param {ESONPatch} revert
* @param {ESON} eson
*/
emitOnChange (patch: ESONPatch, revert: ESONPatch, eson: ESON) {
emitOnChange (patch, revert, eson) {
if (this.props.onPatch) {
this.props.onPatch(patch, revert)
}
@ -939,7 +969,7 @@ export default class TreeMode extends Component {
// TODO: deduplicate this function, it's also implemented in TextMode
setSchema (schema) {
if (schema) {
const ajv = this.props.ajv || Ajv && Ajv(AJV_OPTIONS)
const ajv = (this.props.ajv) || (Ajv && Ajv(AJV_OPTIONS))
if (!ajv) {
throw new Error('Cannot validate JSON: ajv not available. ' +

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -0,0 +1,670 @@
/* schema error popover */
.jsoneditor-schema-error {
position: relative;
/*@-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; }*/
/*}*/ }
.jsoneditor-schema-error .jsoneditor-popover {
background-color: #4c4c4c;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
color: #fff;
display: none;
padding: 7px 10px;
position: absolute;
width: 200px;
z-index: 4; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
bottom: 32px;
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:before {
border-bottom: 7px solid #4c4c4c;
top: -7px; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
border-left: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
right: -14px;
left: inherit;
margin-left: inherit;
margin-top: -10px;
position: absolute; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
border-right: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -10px;
position: absolute; }
.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; }
@-webkit-keyframes fade-in {
from {
opacity: 0; }
to {
opacity: 1; } }
@-moz-keyframes fade-in {
from {
opacity: 0; }
to {
opacity: 1; } }
@-ms-keyframes fade-in {
.jsoneditor-schema-error from {
opacity: 0; }
.jsoneditor-schema-error to {
opacity: 1; } }
.jsoneditor {
border: 1px solid #3883fa;
width: 100%;
height: 100%;
display: inline-flex;
flex-direction: column;
line-height: normal; }
.jsoneditor-menu {
width: 100%;
box-sizing: border-box;
color: white;
background-color: #3883fa;
flex: 0 0 auto; }
.jsoneditor-menu button {
width: 26px;
height: 26px;
margin: 2px;
padding: 0;
border-radius: 2px;
border: 1px solid transparent;
background: transparent url("img/jsoneditor-icons.svg");
color: white;
opacity: 0.8;
font-family: arial, sans-serif;
font-size: 10pt; }
.jsoneditor-menu button:hover {
background-color: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.4); }
.jsoneditor-menu button:focus,
.jsoneditor-menu button:active {
background-color: rgba(255, 255, 255, 0.3); }
.jsoneditor-menu button:disabled {
opacity: 0.5; }
.jsoneditor-menu .jsoneditor-vertical-menu-separator {
width: 8px;
display: inline-block; }
.jsoneditor-menu button.jsoneditor-expand-all {
background-position: 0 -120px; }
.jsoneditor-menu button.jsoneditor-collapse-all {
background-position: 0 -96px; }
.jsoneditor-menu button.jsoneditor-undo {
background-position: -24px -96px; }
.jsoneditor-menu button.jsoneditor-undo:disabled {
background-position: -24px -120px; }
.jsoneditor-menu button.jsoneditor-redo {
background-position: -48px -96px; }
.jsoneditor-menu button.jsoneditor-redo:disabled {
background-position: -48px -120px; }
.jsoneditor-menu button.jsoneditor-compact {
background-position: -72px -96px; }
.jsoneditor-menu button.jsoneditor-format {
background-position: -72px -120px; }
.jsoneditor-contents {
width: 100%;
height: 100%;
min-height: 150px;
overflow: hidden;
padding: 0;
margin: 0;
flex: 1 1 auto; }
.jsoneditor-tree-contents {
padding: 2px 0;
overflow: auto; }
.jsoneditor-node {
position: relative;
font: 14px Arial;
display: inline-flex;
flex-direction: row; }
.jsoneditor-node > div {
flex: 0 0 auto; }
div.jsoneditor-list {
list-style-type: none;
padding-left: 20px;
margin: 0;
font-size: 0; }
/* no left padding for the root div element */
.jsoneditor-contents > div.jsoneditor-list {
padding-left: 2px;
padding-bottom: 24px; }
.jsoneditor-property,
.jsoneditor-value,
.jsoneditor-readonly,
.jsoneditor-separator {
line-height: 20px;
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
font-size: 10pt; }
.jsoneditor-property,
.jsoneditor-value,
.jsoneditor-readonly {
min-width: 24px;
word-break: normal;
padding: 0 5px;
color: #1A1A1A;
outline: none; }
.jsoneditor-button-container {
font-size: 0; }
.jsoneditor-property,
.jsoneditor-value {
border-radius: 1px;
flex: 1 1 auto !important; }
.jsoneditor-property p,
.jsoneditor-value p {
margin: 0;
padding: 0; }
.jsoneditor-property:focus,
.jsoneditor-value:focus {
box-shadow: 0 0 3px 1px #008fd5;
z-index: 1; }
.jsoneditor-property:hover,
.jsoneditor-value:hover {
background-color: rgba(0, 0, 0, 0.05); }
.jsoneditor-mode-form .jsoneditor-property:hover {
background-color: inherit; }
.jsoneditor-mode-view .jsoneditor-property:hover,
.jsoneditor-mode-view .jsoneditor-value:hover {
background-color: inherit; }
.jsoneditor-separator {
color: #808080; }
.jsoneditor-readonly {
color: #808080; }
.jsoneditor-readonly:focus,
.jsoneditor-readonly:hover {
border-color: transparent;
background-color: inherit; }
.jsoneditor-value.jsoneditor-string {
color: #008000; }
.jsoneditor-value.jsoneditor-object,
.jsoneditor-value.jsoneditor-array {
min-width: 16px;
color: #808080; }
.jsoneditor-value.jsoneditor-number {
color: #ee422e; }
.jsoneditor-value.jsoneditor-boolean {
color: #ff8c00; }
.jsoneditor-value.jsoneditor-null {
color: #004ED0; }
.jsoneditor-value.jsoneditor-invalid {
color: #000000; }
div.jsoneditor-value.jsoneditor-url {
color: green;
text-decoration: underline; }
div.jsoneditor-empty {
border: 1px dotted lightgray;
border-radius: 2px;
padding: 0 5px;
line-height: 17px; }
div.jsoneditor-empty::after,
div.jsoneditor-empty::after {
pointer-events: none;
color: lightgray;
font-size: 8pt; }
div.jsoneditor-property.jsoneditor-empty::after {
content: 'prop'; }
div.jsoneditor-value.jsoneditor-empty::after {
content: 'value'; }
.jsoneditor-highlight {
background-color: yellow; }
.jsoneditor-highlight:hover {
background-color: #f0f000; }
.jsoneditor-highlight-active {
background-color: #ffd700; }
.jsoneditor-highlight-active:hover {
background-color: #f3cd00; }
.jsoneditor-button-placeholder {
width: 20px;
padding: 0;
margin: 0;
line-height: 20px; }
button.jsoneditor-button {
position: relative;
width: 20px;
height: 20px;
padding: 0;
margin: 0;
border: none;
cursor: pointer;
background: transparent url("img/jsoneditor-icons.svg"); }
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; }
/* FIXME: change icons from size 24x24 to 20x20 */
button.jsoneditor-button.jsoneditor-collapsed {
background-position: -2px -50px; }
button.jsoneditor-button.jsoneditor-expanded {
background-position: -2px -74px; }
button.jsoneditor-button.jsoneditor-drag {
background-position: -74px -74px;
cursor: move; }
button.jsoneditor-button.jsoneditor-actionmenu {
background-position: -50px -74px; }
button.jsoneditor-button.jsoneditor-actionmenu:hover,
button.jsoneditor-button.jsoneditor-actionmenu:focus,
button.jsoneditor-button.jsoneditor-actionmenu.jsoneditor-visible {
background-position: -50px -50px; }
/******************************* Action Menu **********************************/
div.jsoneditor-actionmenu {
position: absolute;
box-sizing: border-box;
z-index: 99999;
top: 20px;
left: 18px;
/* 20px - 2px where 2px half the difference between 24x24 icons of the menu and the 20x20 icons of the editor */
background: white;
border: 1px solid #d3d3d3;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3); }
div.jsoneditor-actionmenu.jsoneditor-actionmenu-top {
top: auto;
bottom: 20px; }
div.jsoneditor-modemenu.jsoneditor-modemenu {
top: 26px;
left: 0; }
div.jsoneditor-menu-item {
line-height: 0;
font-size: 0; }
button.jsoneditor-menu-button {
width: 136px;
height: 24px;
padding: 0;
margin: 0;
line-height: 24px;
background: transparent;
border: transparent;
display: inline-block;
box-sizing: border-box;
cursor: pointer;
color: #4d4d4d;
font-size: 10pt;
font-family: arial, sans-serif;
text-align: left; }
button.jsoneditor-menu-button:hover,
button.jsoneditor-menu-button:focus {
color: #1A1A1A;
background-color: #f5f5f5;
outline: none; }
button.jsoneditor-menu-button.jsoneditor-selected {
color: white;
background-color: #ee422e; }
button.jsoneditor-menu-default {
width: 104px;
/* 136px - 32px */ }
button.jsoneditor-menu-expand {
width: 32px;
float: right;
border-left: 1px solid #e5e5e5; }
span.jsoneditor-icon {
float: left;
width: 24px;
height: 24px;
border: none;
padding: 0;
margin: 0;
background-image: url("img/jsoneditor-icons.svg"); }
span.jsoneditor-icon.jsoneditor-icon-expand {
float: right;
width: 24px;
margin: 0 4px;
background-position: 0 -72px !important;
opacity: 0.4; }
div.jsoneditor-menu-item button.jsoneditor-menu-button:hover span.jsoneditor-icon-expand,
div.jsoneditor-menu-item button:focus span.jsoneditor-icon-expand {
opacity: 1; }
span.jsoneditor-text {
display: inline-block;
line-height: 24px; }
div.jsoneditor-menu-separator {
height: 0;
border-top: 1px solid #e5e5e5;
padding-top: 5px;
margin-top: 5px; }
div.jsoneditor-menu-panel-right {
float: right;
max-width: 100%; }
button.jsoneditor-remove span.jsoneditor-icon {
background-position: -24px -24px; }
button.jsoneditor-remove:hover span.jsoneditor-icon,
button.jsoneditor-remove:focus span.jsoneditor-icon {
background-position: -24px 0; }
button.jsoneditor-insert span.jsoneditor-icon {
background-position: 0 -24px; }
button.jsoneditor-insert:hover span.jsoneditor-icon,
button.jsoneditor-insert:focus span.jsoneditor-icon {
background-position: 0 0; }
button.jsoneditor-duplicate span.jsoneditor-icon {
background-position: -48px -24px; }
button.jsoneditor-duplicate:hover span.jsoneditor-icon,
button.jsoneditor-duplicate:focus span.jsoneditor-icon {
background-position: -48px 0; }
button.jsoneditor-sort-asc span.jsoneditor-icon {
background-position: -168px -24px; }
button.jsoneditor-sort-asc:hover span.jsoneditor-icon,
button.jsoneditor-sort-asc:focus span.jsoneditor-icon {
background-position: -168px 0; }
button.jsoneditor-sort-desc span.jsoneditor-icon {
background-position: -192px -24px; }
button.jsoneditor-sort-desc:hover span.jsoneditor-icon,
button.jsoneditor-sort-desc:focus span.jsoneditor-icon {
background-position: -192px 0; }
div.jsoneditor-submenu {
visibility: hidden;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out;
box-shadow: inset 0 10px 10px -10px rgba(128, 128, 128, 0.5), inset 0 -10px 10px -10px rgba(128, 128, 128, 0.5); }
div.jsoneditor-submenu.jsoneditor-expanded {
visibility: visible;
max-height: 104px;
/* 4 * 24px + 2 * 5px */
/* FIXME: shouldn't rely on max-height equal to 4 items, should be flexible */ }
div.jsoneditor-submenu.jsoneditor-collapsing {
visibility: visible;
max-height: 0; }
div.jsoneditor-submenu button {
padding-left: 24px; }
div.jsoneditor-submenu div.jsoneditor-menu-item:first-child {
margin-top: 5px; }
div.jsoneditor-submenu div.jsoneditor-menu-item:last-child {
margin-bottom: 5px; }
button.jsoneditor-type-string span.jsoneditor-icon {
background-position: -144px -24px; }
button.jsoneditor-type-string:hover span.jsoneditor-icon,
button.jsoneditor-type-string:focus span.jsoneditor-icon,
button.jsoneditor-type-string.jsoneditor-selected span.jsoneditor-icon {
background-position: -144px 0; }
button.jsoneditor-type-value span.jsoneditor-icon {
background-position: -120px -24px; }
button.jsoneditor-type-value:hover span.jsoneditor-icon,
button.jsoneditor-type-value:focus span.jsoneditor-icon,
button.jsoneditor-type-value.jsoneditor-selected span.jsoneditor-icon {
background-position: -120px 0; }
button.jsoneditor-type-Object span.jsoneditor-icon {
background-position: -72px -24px; }
button.jsoneditor-type-Object:hover span.jsoneditor-icon,
button.jsoneditor-type-Object:focus span.jsoneditor-icon,
button.jsoneditor-type-Object.jsoneditor-selected span.jsoneditor-icon {
background-position: -72px 0; }
button.jsoneditor-type-Array span.jsoneditor-icon {
background-position: -96px -24px; }
button.jsoneditor-type-Array:hover span.jsoneditor-icon,
button.jsoneditor-type-Array:focus span.jsoneditor-icon,
button.jsoneditor-type-Array.jsoneditor-selected span.jsoneditor-icon {
background-position: -96px 0; }
/******************************* Floatting Menu **********************************/
div.jsoneditor-node-container {
position: relative;
transition: background-color 100ms ease-in; }
div.jsoneditor-node-container.jsoneditor-selected {
background-color: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover {
background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area {
background-color: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area {
border: 1px dashed gray;
background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-hover.jsoneditor-hover-insert-area.jsoneditor-selected-insert-area > div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area {
background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area.jsoneditor-hover {
background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area.jsoneditor-hover.jsoneditor-hover-insert-area {
background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected.jsoneditor-selected-insert-area > div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background: #ffed99; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover {
background-color: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area {
background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-selected div.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background: #ffdb80; }
div.jsoneditor-node-container.jsoneditor-hover {
background-color: #f2f2f2; }
div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area {
background-color: inherit; }
div.jsoneditor-node-container.jsoneditor-hover.jsoneditor-hover-insert-area > div.jsoneditor-insert-area {
border: 1px dashed gray;
background-color: #f2f2f2; }
div.jsoneditor-node-container div.jsoneditor-insert-area {
position: absolute;
width: 100%;
height: 8px;
left: 0;
top: -4px;
border: 1px transparent;
box-sizing: border-box;
z-index: 1; }
div.jsoneditor-node-container div.jsoneditor-floating-menu {
position: absolute;
bottom: 100%;
right: 0;
z-index: 999;
margin: 10px;
white-space: nowrap;
border-radius: 5px;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.24); }
div.jsoneditor-node-container div.jsoneditor-floating-menu:after {
content: '';
position: absolute;
top: 100%;
left: 35px;
margin-left: -10px;
width: 0;
height: 0;
border-top: solid 10px #4d4d4d;
border-left: solid 10px transparent;
border-right: solid 10px transparent; }
div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item {
color: #fff;
background: #4d4d4d;
border: none;
border-right: 1px solid #676767;
padding: 10px;
cursor: pointer;
outline: none; }
div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item:focus, div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item:hover {
background: #676767; }
div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px; }
div.jsoneditor-node-container div.jsoneditor-floating-menu button.jsoneditor-floating-menu-item:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
border-right: none; }
/******************************* **********************************/
div.jsoneditor-modes {
position: relative;
display: inline-block;
vertical-align: top; }
div.jsoneditor-modes button {
background: none;
width: auto;
padding: 2px 6px; }
div.jsoneditor-modes button.jsoneditor-type-modes {
width: 120px;
height: auto;
padding: 2px 6px;
border-radius: 0;
opacity: 1; }
div.jsoneditor-modes button.jsoneditor-type-modes:hover {
border: none; }
textarea.jsoneditor-text {
width: 100%;
height: 100%;
min-height: 150px;
margin: 0;
box-sizing: border-box;
outline-width: 0;
border: none;
background-color: #fff;
resize: none;
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
font-size: 10pt;
color: #1A1A1A; }
div.jsoneditor-code {
width: 100%;
height: 100%;
min-height: 150px; }
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
.jsoneditor-errors {
width: 100%;
background-color: #ffef8b;
border-top: 1px solid #ffd700; }
.jsoneditor-errors table {
border-collapse: collapse;
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
font-size: 10pt; }
.jsoneditor-errors table td {
padding: 3px 6px;
vertical-align: middle; }
.jsoneditor-errors table td code {
display: block;
white-space: pre-wrap; }
.jsoneditor-schema-error {
outline: none;
border: none;
width: 20px;
height: 20px;
padding: 0;
margin: 0 4px;
background: url("img/jsoneditor-icons.svg") -171px -49px; }

View File

@ -1,19 +1,21 @@
@import url('./popover.less');
@import './popover.scss';
@fontFamily: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
@fontSize: 10pt;
@black: #1A1A1A;
@contentsMinHeight: 150px;
@theme-color: #3883fa;
@floating-menu-background: #4d4d4d;
@floating-menu-color: #fff;
// @selectedColor: #e5e5e5;
@selectedColor: #ffed99;
@hoverColor: #f2f2f2;
@hoverAndSelectedColor: #ffdb80;
$fontFamily: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
$fontSize: 10pt;
$black: #1A1A1A;
$contentsMinHeight: 150px;
$theme-color: #3883fa; // TODO: create a central file with the theme colors
$floating-menu-background: #4d4d4d;
$floating-menu-color: #fff;
// $selectedColor: #e5e5e5;
$selectedColor: #ffed99;
$hoverColor: #f2f2f2;
$hoverAndSelectedColor: #ffdb80;
// TODO: split this scss file into separate files per React component
.jsoneditor {
border: 1px solid @theme-color;
border: 1px solid $theme-color;
width: 100%;
height: 100%;
@ -28,7 +30,7 @@
box-sizing: border-box;
color: white;
background-color: @theme-color;
background-color: $theme-color;
flex: 0 0 auto;
button {
@ -94,7 +96,7 @@
.jsoneditor-contents {
width: 100%;
height: 100%;
min-height: @contentsMinHeight;
min-height: $contentsMinHeight;
overflow: hidden;
padding: 0;
@ -139,19 +141,19 @@ div.jsoneditor-list {
.jsoneditor-separator {
line-height: 20px;
font-family: @fontFamily;
font-size: @fontSize;
font-family: $fontFamily;
font-size: $fontSize;
}
.jsoneditor-property,
.jsoneditor-value,
.jsoneditor-readonly {
min-width: 24px;
word-break: break-word;
word-break: normal;
padding: 0 5px;
color: @black;
color: $black;
outline: none;
}
@ -387,7 +389,7 @@ button.jsoneditor-menu-button {
button.jsoneditor-menu-button:hover,
button.jsoneditor-menu-button:focus {
color: @black;
color: $black;
background-color: #f5f5f5;
outline: none;
}
@ -568,23 +570,23 @@ div.jsoneditor-node-container {
// TODO: can the hover/select css be simplified?
&.jsoneditor-selected {
background-color: @selectedColor;
background-color: $selectedColor;
&.jsoneditor-hover {
background-color: @hoverAndSelectedColor;
background-color: $hoverAndSelectedColor;
&.jsoneditor-hover-insert-area {
background-color: @selectedColor;
background-color: $selectedColor;
> div.jsoneditor-insert-area {
border: 1px dashed gray;
background-color: @hoverColor;
background-color: $hoverColor;
}
&.jsoneditor-selected-insert-area {
> div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background-color: @hoverAndSelectedColor;
background-color: $hoverAndSelectedColor;
}
}
}
@ -594,7 +596,7 @@ div.jsoneditor-node-container {
background-color: inherit;
&.jsoneditor-hover {
background-color: @hoverColor;
background-color: $hoverColor;
&.jsoneditor-hover-insert-area {
background-color: inherit;
@ -603,46 +605,46 @@ div.jsoneditor-node-container {
> div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background: @selectedColor;
background: $selectedColor;
}
}
// hovering nested elements
div.jsoneditor-hover {
background-color: @hoverAndSelectedColor;
background-color: $hoverAndSelectedColor;
&.jsoneditor-hover-insert-area {
background-color: inherit;
> div.jsoneditor-insert-area {
border: 1px dashed #f4af41;
background: @hoverAndSelectedColor;
background: $hoverAndSelectedColor;
}
}
}
}
&.jsoneditor-hover {
background-color: @hoverColor;
background-color: $hoverColor;
&.jsoneditor-hover-insert-area {
background-color: inherit;
> div.jsoneditor-insert-area {
border: 1px dashed gray;
background-color: @hoverColor;
background-color: $hoverColor;
}
}
}
div.jsoneditor-insert-area {
@height: 8px;
$height: 8px;
position: absolute;
width: 100%;
height: @height;
height: $height;
left: 0;
top: -@height/2;
top: -$height/2;
border: 1px transparent;
box-sizing: border-box;
z-index: 1; // must be on top of next node, it overlaps a bit
@ -667,23 +669,23 @@ div.jsoneditor-node-container {
margin-left: -10px;
width: 0;
height: 0;
border-top: solid 10px @floating-menu-background;
border-top: solid 10px $floating-menu-background;
border-left: solid 10px transparent;
border-right: solid 10px transparent;
}
button.jsoneditor-floating-menu-item {
color: @floating-menu-color;
background: @floating-menu-background;
color: $floating-menu-color;
background: $floating-menu-background;
border: none;
border-right: 1px solid lighten(@floating-menu-background, 10%);
border-right: 1px solid lighten($floating-menu-background, 10%);
padding: 10px;
cursor: pointer;
outline: none;
&:focus,
&:hover {
background: lighten(@floating-menu-background, 10%);
background: lighten($floating-menu-background, 10%);
}
&:first-child {
@ -728,7 +730,7 @@ div.jsoneditor-modes {
textarea.jsoneditor-text {
width: 100%;
height: 100%;
min-height: @contentsMinHeight;
min-height: $contentsMinHeight;
margin: 0;
box-sizing: border-box;
@ -737,15 +739,15 @@ textarea.jsoneditor-text {
background-color: #fff;
resize: none;
font-family: @fontFamily;
font-size: @fontSize;
color: @black;
font-family: $fontFamily;
font-size: $fontSize;
color: $black;
}
div.jsoneditor-code {
width: 100%;
height: 100%;
min-height: @contentsMinHeight;
min-height: $contentsMinHeight;
}
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
@ -758,8 +760,8 @@ div.jsoneditor-code {
table {
border-collapse: collapse;
font-family: @fontFamily;
font-size: @fontSize;
font-family: $fontFamily;
font-size: $fontSize;
td {
padding: 3px 6px;

View File

@ -1,7 +1,4 @@
// @flow weak
import { createElement as h, PureComponent } from 'react'
import { keyComboFromEvent } from '../../utils/keyBindings'
const MENU_CLASS_NAME = 'jsoneditor-floating-menu'
const MENU_ITEM_CLASS_NAME = 'jsoneditor-floating-menu-item'

View File

@ -0,0 +1,57 @@
div.jsoneditor-search {
font-family: arial, sans-serif;
font-size: 10pt; }
div.jsoneditor-search div.jsoneditor-results {
display: inline-block;
margin-right: 5px; }
div.jsoneditor-search form.jsoneditor-search-box {
display: inline-flex;
position: relative;
max-width: 100%;
background-color: white;
border: 2px solid #3883fa;
box-sizing: border-box; }
div.jsoneditor-search form.jsoneditor-search-box::before {
position: absolute;
display: inline-block;
width: 22px;
height: 100%;
background: transparent url("../img/jsoneditor-icons.svg") -97px -71px;
content: ''; }
div.jsoneditor-search form.jsoneditor-search-box input.jsoneditor-search-text {
display: inline-block;
position: relative;
border: none;
outline: none;
width: 120px;
max-width: 100%;
height: 22px;
line-height: 22px;
padding: 2px 2px 2px 22px;
background: transparent; }
div.jsoneditor-search form.jsoneditor-search-box input[type=button] {
display: inline-block;
position: relative;
width: 16px;
height: 100%;
line-height: 22px;
margin: 2px 0;
padding: 0;
border: none;
background: transparent url("../img/jsoneditor-icons.svg");
opacity: 0.8;
font-family: arial, sans-serif;
font-size: 10pt; }
div.jsoneditor-search form.jsoneditor-search-box input[type=button]:hover {
background-color: transparent; }
div.jsoneditor-search form.jsoneditor-search-box input.jsoneditor-search-next {
cursor: pointer;
background-position: -124px -73px; }
div.jsoneditor-search form.jsoneditor-search-box input.jsoneditor-search-next:hover {
background-position: -124px -49px; }
div.jsoneditor-search form.jsoneditor-search-box input.jsoneditor-search-previous {
cursor: pointer;
background-position: -148px -73px;
margin-right: 2px; }
div.jsoneditor-search form.jsoneditor-search-box input.jsoneditor-search-previous:hover {
background-position: -148px -49px; }

View File

@ -1,17 +1,11 @@
// @flow weak
import { createElement as h, Component } from 'react'
import PropTypes from 'prop-types'
import { keyComboFromEvent } from '../../utils/keyBindings'
import { findEditorContainer, setSelection } from '../utils/domSelector'
import '!style!css!less!./Search.less'
import './Search.css'
export default class Search extends Component {
state: {
text: string
}
constructor (props) {
super (props)

View File

@ -1,4 +1,4 @@
@theme-color: #3883fa;
$theme-color: #3883fa;
div.jsoneditor-search {
font-family: arial, sans-serif;
@ -15,17 +15,17 @@ div.jsoneditor-search {
max-width: 100%;
background-color: white;
border: 2px solid @theme-color;
border: 2px solid $theme-color;
box-sizing: border-box;
@search-icon-width: 22px;
$search-icon-width: 22px;
&::before {
position: absolute;
display: inline-block;
width: @search-icon-width;
width: $search-icon-width;
height: 100%;
background: transparent url('../../img/jsoneditor-icons.svg') -97px -71px;
background: transparent url('../img/jsoneditor-icons.svg') -97px -71px;
content: '';
}
@ -39,7 +39,7 @@ div.jsoneditor-search {
max-width: 100%;
height: 22px;
line-height: 22px;
padding: 2px 2px 2px @search-icon-width;
padding: 2px 2px 2px $search-icon-width;
background: transparent;
}
@ -53,7 +53,7 @@ div.jsoneditor-search {
margin: 2px 0;
padding: 0;
border: none;
background: transparent url('../../img/jsoneditor-icons.svg');
background: transparent url('../img/jsoneditor-icons.svg');
opacity: 0.8;
font-family: arial, sans-serif;

View File

@ -0,0 +1,96 @@
/* schema error popover */
.jsoneditor-schema-error {
position: relative;
/*@-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; }*/
/*}*/ }
.jsoneditor-schema-error .jsoneditor-popover {
background-color: #4c4c4c;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.4);
color: #fff;
display: none;
padding: 7px 10px;
position: absolute;
width: 200px;
z-index: 4; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
bottom: 32px;
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:before {
border-bottom: 7px solid #4c4c4c;
top: -7px; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
border-left: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
right: -14px;
left: inherit;
margin-left: inherit;
margin-top: -10px;
position: absolute; }
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
border-right: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -10px;
position: absolute; }
.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; }
@-webkit-keyframes fade-in {
from {
opacity: 0; }
to {
opacity: 1; } }
@-moz-keyframes fade-in {
from {
opacity: 0; }
to {
opacity: 1; } }
@-ms-keyframes fade-in {
.jsoneditor-schema-error from {
opacity: 0; }
.jsoneditor-schema-error to {
opacity: 1; } }

View File

@ -64,7 +64,7 @@
right: -14px;
left: inherit;
margin-left: inherit;
margin-top: -7px;
margin-top: -10px;
position: absolute;
}
@ -76,7 +76,7 @@
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -7px;
margin-top: -10px;
position: absolute;
}

View File

@ -1,5 +1,3 @@
// @flow weak
/**
* This file contains functions to act on a ESON object.
* All functions are pure and don't mutate the ESON.
@ -14,12 +12,6 @@ import times from 'lodash/times'
import initial from 'lodash/initial'
import last from 'lodash/last'
import type {
ESON, ESONPointer, Selection,
Path,
JSONType
} from './types'
export const SELECTED = 1
export const SELECTED_END = 2
export const SELECTED_BEFORE = 3
@ -38,8 +30,8 @@ export function expandAll (path) {
/**
*
* @param {JSONType} json
* @param {JSONPath} path
* @param {JSON} json
* @param {Path} path
* @return {ESON}
*/
export function jsonToEson (json, path = []) {
@ -69,7 +61,7 @@ export function jsonToEson (json, path = []) {
* @param {ESON} eson
* @return {Object | Array | string | number | boolean | null} json
*/
export function esonToJson (eson: ESON) {
export function esonToJson (eson) {
switch (eson[META].type) {
case 'Array':
return eson.map(item => esonToJson(item))
@ -154,6 +146,7 @@ export function updatePaths(eson, path = []) {
* @return {ESON}
*/
export function expand (eson, filterCallback, expanded = true) {
// TODO: adjust expand to have a filterCallback which can return true, false, or undefined. In the latter case, the expanded state is left as is.
return transform(eson, function (value, path) {
return ((value[META].type === 'Array' || value[META].type === 'Object') && filterCallback(path))
? expandOne(value, [], expanded)
@ -215,7 +208,7 @@ export function applyErrors (eson, errors = []) {
* Cleanup meta data from an eson object
* @param {ESON} eson Object to be cleaned up
* @param {String} field Field name, for example 'error' or 'selected'
* @param {String[] | JSONPath[]} [ignorePaths=[]] An optional array with paths to be ignored
* @param {Path[]} [ignorePaths=[]] An optional array with paths to be ignored
* @return {ESON}
*/
export function cleanupMetaData(eson, field, ignorePaths = []) {
@ -395,8 +388,8 @@ export function applySelection (eson, selection) {
return updatedObj
}
else { // root[META].type === 'Array'
const startIndex = parseInt(start)
const endIndex = parseInt(end)
const startIndex = parseInt(start, 10)
const endIndex = parseInt(end, 10)
const minIndex = Math.min(startIndex, endIndex)
const maxIndex = Math.max(startIndex, endIndex) + 1 // include max index itself
@ -434,8 +427,8 @@ export function findSelectionIndices (root, rootPath, selection) {
const end = (selection.after || selection.before || selection.end)[rootPath.length]
// if no object we assume it's an Array
const startIndex = root[META].type === 'Object' ? root[META].props.indexOf(start) : parseInt(start)
const endIndex = root[META].type === 'Object' ? root[META].props.indexOf(end) : parseInt(end)
const startIndex = root[META].type === 'Object' ? root[META].props.indexOf(start) : parseInt(start, 10)
const endIndex = root[META].type === 'Object' ? root[META].props.indexOf(end) : parseInt(end, 10)
const minIndex = Math.min(startIndex, endIndex)
const maxIndex = Math.max(startIndex, endIndex) +
@ -446,8 +439,11 @@ export function findSelectionIndices (root, rootPath, selection) {
/**
* Get the JSON paths from a selection, sorted from first to last
* @param {ESON} eson
* @param {Selection} selection
* @return {Path[]}
*/
export function pathsFromSelection (eson, selection: Selection): Path[] {
export function pathsFromSelection (eson, selection) {
// find the parent node shared by both start and end of the selection
const rootPath = findRootPath(selection)
const root = getIn(eson, rootPath)
@ -468,7 +464,7 @@ export function pathsFromSelection (eson, selection: Selection): Path[] {
* @param {Path[]} paths
* @return {Array.<{name: string, value: JSONType}>}
*/
export function contentsFromPaths (data: ESON, paths: Path[]) {
export function contentsFromPaths (data, paths) {
return paths.map(path => {
return {
name: last(path),
@ -508,8 +504,11 @@ export function findRootPath(selection) {
/**
* Find the common path of two paths.
* For example findCommonRoot(['arr', '1', 'name'], ['arr', '1', 'address', 'contact']) returns ['arr', '1']
* @param {Path} path1
* @param {Path} path2
* @return {Path}
*/
function findSharedPath (path1: Path, path2: Path): Path {
function findSharedPath (path1, path2) {
let i = 0;
while (i < path1.length && path1[i] === path2[i]) {
i++;
@ -536,7 +535,7 @@ export function pathExists (eson, path) {
if (Array.isArray(eson)) {
// index of an array
return pathExists(eson[parseInt(path[0])], path.slice(1))
return pathExists(eson[parseInt(path[0], 10)], path.slice(1))
}
else { // Object
// object property. find the index of this property
@ -583,9 +582,9 @@ export function findNextProp (parent, prop) {
* Parse a JSON Pointer
* WARNING: this is not a complete implementation
* @param {string} pointer
* @return {Array}
* @return {Path}
*/
export function parseJSONPointer (pointer: string) {
export function parseJSONPointer (pointer) {
const path = pointer.split('/')
path.shift() // remove the first empty entry
@ -598,7 +597,7 @@ export function parseJSONPointer (pointer: string) {
* @param {Path} path
* @return {string}
*/
export function compileJSONPointer (path: Path) {
export function compileJSONPointer (path) {
return path
.map(p => '/' + String(p).replace(/~/g, '~0').replace(/\//g, '~1'))
.join('')
@ -612,7 +611,7 @@ export function compileJSONPointer (path: Path) {
* @param {String} search
* @return {boolean} Returns true if `search` is found in `text`
*/
export function containsCaseInsensitive (text: string, search: string): boolean {
export function containsCaseInsensitive (text, search) {
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
}
@ -620,7 +619,7 @@ export function containsCaseInsensitive (text: string, search: string): boolean
* Get a new "unique" id. Id's are created from an incremental counter.
* @return {number}
*/
export function createId () : number {
export function createId () {
_id++
return _id
}

View File

@ -1,8 +1,7 @@
'use strict'
import { readFileSync } from 'fs'
import test from 'ava'
import { setIn, getIn, deleteIn } from '../src/utils/immutabilityHelpers'
import { setIn, getIn, deleteIn } from './utils/immutabilityHelpers'
import {
META,
esonToJson, pathExists, transform,
@ -12,17 +11,17 @@ import {
previousSearchResult,
applySelection, pathsFromSelection,
SELECTED, SELECTED_END
} from '../src/eson'
} from './eson'
import 'console.table'
import repeat from 'lodash/repeat'
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
test('jsonToEson', t => {
assertDeepEqualEson(t, jsonToEson(1), {[META]: {id: '[ID]', path: [], type: 'value', value: 1}})
assertDeepEqualEson(t, jsonToEson("foo"), {[META]: {id: '[ID]', path: [], type: 'value', value: "foo"}})
assertDeepEqualEson(t, jsonToEson(null), {[META]: {id: '[ID]', path: [], type: 'value', value: null}})
assertDeepEqualEson(t, jsonToEson(false), {[META]: {id: '[ID]', path: [], type: 'value', value: false}})
assertDeepEqualEson(t, jsonToEson({a:1, b: 2}), {
test('jsonToEson', () => {
assertDeepEqualEson(jsonToEson(1), {[META]: {id: '[ID]', path: [], type: 'value', value: 1}})
assertDeepEqualEson(jsonToEson("foo"), {[META]: {id: '[ID]', path: [], type: 'value', value: "foo"}})
assertDeepEqualEson(jsonToEson(null), {[META]: {id: '[ID]', path: [], type: 'value', value: null}})
assertDeepEqualEson(jsonToEson(false), {[META]: {id: '[ID]', path: [], type: 'value', value: false}})
assertDeepEqualEson(jsonToEson({a:1, b: 2}), {
[META]: {id: '[ID]', path: [], type: 'Object', props: ['a', 'b']},
a: {[META]: {id: '[ID]', path: ['a'], type: 'value', value: 1}},
b: {[META]: {id: '[ID]', path: ['b'], type: 'value', value: 2}}
@ -34,10 +33,10 @@ test('jsonToEson', t => {
{[META]: {id: '[ID]', path: ['1'], type: 'value', value: 2}}
]
expected[META] = {id: '[ID]', path: [], type: 'Array'}
assertDeepEqualEson(t, actual, expected)
assertDeepEqualEson(actual, expected)
})
test('esonToJson', t => {
test('esonToJson', () => {
const json = {
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -47,10 +46,10 @@ test('esonToJson', t => {
"bool": false
}
const eson = jsonToEson(json)
t.deepEqual(esonToJson(eson), json)
expect(esonToJson(eson)).toEqual(json)
})
test('expand a single path', t => {
test('expand a single path', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -62,15 +61,15 @@ test('expand a single path', t => {
const path = ['obj', 'arr', 2]
const collapsed = expandOne(eson, path, false)
t.is(collapsed.obj.arr[2][META].expanded, false)
assertDeepEqualEson(t, deleteIn(collapsed, path.concat([META, 'expanded'])), eson)
expect(collapsed.obj.arr[2][META].expanded).toEqual(false)
assertDeepEqualEson(deleteIn(collapsed, path.concat([META, 'expanded'])), eson)
const expanded = expandOne(eson, path, true)
t.is(expanded.obj.arr[2][META].expanded, true)
assertDeepEqualEson(t, deleteIn(expanded, path.concat([META, 'expanded'])), eson)
expect(expanded.obj.arr[2][META].expanded).toEqual(true)
assertDeepEqualEson(deleteIn(expanded, path.concat([META, 'expanded'])), eson)
})
test('expand all objects/arrays on a path', t => {
test('expand all objects/arrays on a path', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -83,16 +82,16 @@ test('expand all objects/arrays on a path', t => {
const path = ['obj', 'arr', 2]
const collapsed = expandPath(eson, path, false)
t.is(collapsed[META].expanded, false)
t.is(collapsed.obj[META].expanded, false)
t.is(collapsed.obj.arr[META].expanded, false)
t.is(collapsed.obj.arr[2][META].expanded, false)
expect(collapsed[META].expanded).toEqual(false)
expect(collapsed.obj[META].expanded).toEqual(false)
expect(collapsed.obj.arr[META].expanded).toEqual(false)
expect(collapsed.obj.arr[2][META].expanded).toEqual(false)
const expanded = expandPath(eson, path, true)
t.is(expanded[META].expanded, true)
t.is(expanded.obj[META].expanded, true)
t.is(expanded.obj.arr[META].expanded, true)
t.is(expanded.obj.arr[2][META].expanded, true)
expect(expanded[META].expanded).toEqual(true)
expect(expanded.obj[META].expanded).toEqual(true)
expect(expanded.obj.arr[META].expanded).toEqual(true)
expect(expanded.obj.arr[2][META].expanded).toEqual(true)
let orig = expanded
orig = deleteIn(orig, [].concat([META, 'expanded']))
@ -100,10 +99,10 @@ test('expand all objects/arrays on a path', t => {
orig = deleteIn(orig, ['obj', 'arr'].concat([META, 'expanded']))
orig = deleteIn(orig, ['obj', 'arr', 2].concat([META, 'expanded']))
assertDeepEqualEson(t, orig, eson)
assertDeepEqualEson(orig, eson)
})
test('expand a callback', t => {
test('expand a callback', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -118,19 +117,19 @@ test('expand a callback', t => {
}
const expandedValue = false
const collapsed = expand(eson, filterCallback, expandedValue)
t.is(collapsed[META].expanded, undefined)
t.is(collapsed.obj[META].expanded, expandedValue)
t.is(collapsed.obj.arr[META].expanded, expandedValue)
t.is(collapsed.obj.arr[2][META].expanded, expandedValue)
expect(collapsed[META].expanded).toEqual(undefined)
expect(collapsed.obj[META].expanded).toEqual(expandedValue)
expect(collapsed.obj.arr[META].expanded).toEqual(expandedValue)
expect(collapsed.obj.arr[2][META].expanded).toEqual(expandedValue)
let orig = collapsed
orig = deleteIn(orig, ['obj'].concat([META, 'expanded']))
orig = deleteIn(orig, ['obj', 'arr'].concat([META, 'expanded']))
orig = deleteIn(orig, ['obj', 'arr', 2].concat([META, 'expanded']))
assertDeepEqualEson(t, orig, eson)
assertDeepEqualEson(orig, eson)
})
test('expand a callback should not change the object when nothing happens', t => {
test('expand a callback should not change the object when nothing happens', () => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
function callback (path) {
return false
@ -138,39 +137,39 @@ test('expand a callback should not change the object when nothing happens', t =>
const expanded = false
const collapsed = expand(eson, callback, expanded)
t.is(collapsed, eson)
expect(collapsed).toBe(eson)
})
test('transform (no change)', t => {
test('transform (no change)', () => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
const updated = transform(eson, (value, path) => value)
assertDeepEqualEson(t, updated, eson)
t.is(updated, eson)
assertDeepEqualEson(updated, eson)
expect(updated).toBe(eson)
})
test('transform (change based on value)', t => {
test('transform (change based on value)', () => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
const updated = transform(eson,
(value, path) => value[META].value === 2 ? jsonToEson(20, path) : value)
const expected = jsonToEson({a: [1,20,3], b: {c: 4}})
assertDeepEqualEson(t, updated, expected)
t.is(updated.b, eson.b) // should not have replaced b
assertDeepEqualEson(updated, expected)
expect(updated.b).toBe(eson.b) // should not have replaced b
})
test('transform (change based on path)', t => {
test('transform (change based on path)', () => {
const eson = jsonToEson({a: [1,2,3], b: {c: 4}})
const updated = transform(eson,
(value, path) => path.join('.') === 'a.1' ? jsonToEson(20, path) : value)
const expected = jsonToEson({a: [1,20,3], b: {c: 4}})
assertDeepEqualEson(t, updated, expected)
t.is(updated.b, eson.b) // should not have replaced b
assertDeepEqualEson(updated, expected)
expect(updated.b).toBe(eson.b) // should not have replaced b
})
test('pathExists', t => {
test('pathExists', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -180,29 +179,29 @@ test('pathExists', t => {
"bool": false
})
t.is(pathExists(eson, ['obj', 'arr', 2, 'first']), true)
t.is(pathExists(eson, ['obj', 'foo']), false)
t.is(pathExists(eson, ['obj', 'foo', 'bar']), false)
t.is(pathExists(eson, []), true)
expect(pathExists(eson, ['obj', 'arr', 2, 'first'])).toEqual(true)
expect(pathExists(eson, ['obj', 'foo'])).toEqual(false)
expect(pathExists(eson, ['obj', 'foo', 'bar'])).toEqual(false)
expect(pathExists(eson, [])).toEqual(true)
})
test('parseJSONPointer', t => {
t.deepEqual(parseJSONPointer('/obj/a'), ['obj', 'a'])
t.deepEqual(parseJSONPointer('/arr/-'), ['arr', '-'])
t.deepEqual(parseJSONPointer('/foo/~1~0 ~0~1'), ['foo', '/~ ~/'])
t.deepEqual(parseJSONPointer('/obj'), ['obj'])
t.deepEqual(parseJSONPointer('/'), [''])
t.deepEqual(parseJSONPointer(''), [])
test('parseJSONPointer', () => {
expect(parseJSONPointer('/obj/a')).toEqual(['obj', 'a'])
expect(parseJSONPointer('/arr/-')).toEqual(['arr', '-'])
expect(parseJSONPointer('/foo/~1~0 ~0~1')).toEqual(['foo', '/~ ~/'])
expect(parseJSONPointer('/obj')).toEqual(['obj'])
expect(parseJSONPointer('/')).toEqual([''])
expect(parseJSONPointer('')).toEqual([])
})
test('compileJSONPointer', t => {
t.deepEqual(compileJSONPointer(['foo', 'bar']), '/foo/bar')
t.deepEqual(compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1')
t.deepEqual(compileJSONPointer(['']), '/')
t.deepEqual(compileJSONPointer([]), '')
test('compileJSONPointer', () => {
expect(compileJSONPointer(['foo', 'bar'])).toEqual('/foo/bar')
expect(compileJSONPointer(['foo', '/~ ~/'])).toEqual('/foo/~1~0 ~0~1')
expect(compileJSONPointer([''])).toEqual('/')
expect(compileJSONPointer([])).toEqual('')
})
test('add and remove errors', t => {
test('add and remove errors', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -222,19 +221,19 @@ test('add and remove errors', t => {
let expected = eson
expected = setIn(expected, ['obj', 'arr', '2', 'last', META, 'error'], jsonSchemaErrors[0])
expected = setIn(expected, ['nill', META, 'error'], jsonSchemaErrors[1])
assertDeepEqualEson(t, actual1, expected)
assertDeepEqualEson(actual1, expected)
// re-applying the same errors should not change eson
const actual2 = applyErrors(actual1, jsonSchemaErrors)
t.is(actual2, actual1)
expect(actual2).toBe(actual1)
// clear errors
const actual3 = applyErrors(actual2, [])
assertDeepEqualEson(t, actual3, eson)
t.is(actual3.str, eson.str) // shouldn't have touched values not affected by the errors
assertDeepEqualEson(actual3, eson)
expect(actual3.str).toEqual(eson.str) // shouldn't have touched values not affected by the errors
})
test('search', t => {
test('search', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -248,7 +247,7 @@ test('search', t => {
const matches = result.searchResult.matches
const active = result.searchResult.active
t.deepEqual(matches, [
expect(matches).toEqual([
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
{path: ['str'], area: 'value'},
{path: ['nill'], area: 'property'},
@ -256,7 +255,7 @@ test('search', t => {
{path: ['bool'], area: 'property'},
{path: ['bool'], area: 'value'}
])
t.deepEqual(active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
expect(active).toEqual({path: ['obj', 'arr', '2', 'last'], area: 'property'})
let expected = esonWithSearch
expected = setIn(expected, ['obj', 'arr', '2', 'last', META, 'searchProperty'], 'active')
@ -266,10 +265,10 @@ test('search', t => {
expected = setIn(expected, ['bool', META, 'searchProperty'], 'normal')
expected = setIn(expected, ['bool', META, 'searchValue'], 'normal')
assertDeepEqualEson(t, esonWithSearch, expected)
assertDeepEqualEson(esonWithSearch, expected)
})
test('nextSearchResult', t => {
test('nextSearchResult', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -280,37 +279,37 @@ test('nextSearchResult', t => {
})
const first = search(eson, 'A')
t.deepEqual(first.searchResult.matches, [
expect(first.searchResult.matches).toEqual([
{path: ['obj', 'arr'], area: 'property'},
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
{path: ['bool'], area: 'value'}
])
t.deepEqual(first.searchResult.active, {path: ['obj', 'arr'], area: 'property'})
t.is(getIn(first.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
t.is(getIn(first.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(first.eson, ['bool', META, 'searchValue']), 'normal')
expect(first.searchResult.active).toEqual({path: ['obj', 'arr'], area: 'property'})
expect(getIn(first.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('active')
expect(getIn(first.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(first.eson, ['bool', META, 'searchValue'])).toEqual('normal')
const second = nextSearchResult(first.eson, first.searchResult)
t.deepEqual(second.searchResult.active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
t.is(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'active')
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
expect(second.searchResult.active).toEqual({path: ['obj', 'arr', '2', 'last'], area: 'property'})
expect(getIn(second.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('normal')
expect(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('active')
expect(getIn(second.eson, ['bool', META, 'searchValue'])).toEqual('normal')
const third = nextSearchResult(second.eson, second.searchResult)
t.deepEqual(third.searchResult.active, {path: ['bool'], area: 'value'})
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
t.is(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
expect(third.searchResult.active).toEqual({path: ['bool'], area: 'value'})
expect(getIn(third.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('normal')
expect(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(third.eson, ['bool', META, 'searchValue'])).toEqual('active')
const wrappedAround = nextSearchResult(third.eson, third.searchResult)
t.deepEqual(wrappedAround.searchResult.active, {path: ['obj', 'arr'], area: 'property'})
t.is(getIn(wrappedAround.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
t.is(getIn(wrappedAround.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(wrappedAround.eson, ['bool', META, 'searchValue']), 'normal')
expect(wrappedAround.searchResult.active).toEqual({path: ['obj', 'arr'], area: 'property'})
expect(getIn(wrappedAround.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('active')
expect(getIn(wrappedAround.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(wrappedAround.eson, ['bool', META, 'searchValue'])).toEqual('normal')
})
test('previousSearchResult', t => {
test('previousSearchResult', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -321,37 +320,37 @@ test('previousSearchResult', t => {
})
const init = search(eson, 'A')
t.deepEqual(init.searchResult.matches, [
expect(init.searchResult.matches).toEqual([
{path: ['obj', 'arr'], area: 'property'},
{path: ['obj', 'arr', '2', 'last'], area: 'property'},
{path: ['bool'], area: 'value'}
])
t.deepEqual(init.searchResult.active, {path: ['obj', 'arr'], area: 'property'})
t.is(getIn(init.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
t.is(getIn(init.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(init.eson, ['bool', META, 'searchValue']), 'normal')
expect(init.searchResult.active).toEqual({path: ['obj', 'arr'], area: 'property'})
expect(getIn(init.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('active')
expect(getIn(init.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(init.eson, ['bool', META, 'searchValue'])).toEqual('normal')
const third = previousSearchResult(init.eson, init.searchResult)
t.deepEqual(third.searchResult.active, {path: ['bool'], area: 'value'})
t.is(getIn(third.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
t.is(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(third.eson, ['bool', META, 'searchValue']), 'active')
expect(third.searchResult.active).toEqual({path: ['bool'], area: 'value'})
expect(getIn(third.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('normal')
expect(getIn(third.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(third.eson, ['bool', META, 'searchValue'])).toEqual('active')
const second = previousSearchResult(third.eson, third.searchResult)
t.deepEqual(second.searchResult.active, {path: ['obj', 'arr', '2', 'last'], area: 'property'})
t.is(getIn(second.eson, ['obj', 'arr', META, 'searchProperty']), 'normal')
t.is(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'active')
t.is(getIn(second.eson, ['bool', META, 'searchValue']), 'normal')
expect(second.searchResult.active).toEqual({path: ['obj', 'arr', '2', 'last'], area: 'property'})
expect(getIn(second.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('normal')
expect(getIn(second.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('active')
expect(getIn(second.eson, ['bool', META, 'searchValue'])).toEqual('normal')
const first = previousSearchResult(second.eson, second.searchResult)
t.deepEqual(first.searchResult.active, {path: ['obj', 'arr'], area: 'property'})
t.is(getIn(first.eson, ['obj', 'arr', META, 'searchProperty']), 'active')
t.is(getIn(first.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty']), 'normal')
t.is(getIn(first.eson, ['bool', META, 'searchValue']), 'normal')
expect(first.searchResult.active).toEqual({path: ['obj', 'arr'], area: 'property'})
expect(getIn(first.eson, ['obj', 'arr', META, 'searchProperty'])).toEqual('active')
expect(getIn(first.eson, ['obj', 'arr', '2', 'last', META, 'searchProperty'])).toEqual('normal')
expect(getIn(first.eson, ['bool', META, 'searchValue'])).toEqual('normal')
})
test('selection (object)', t => {
test('selection (object)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -371,7 +370,7 @@ test('selection (object)', t => {
expected = setIn(expected, ['obj', META, 'selected'], SELECTED)
expected = setIn(expected, ['str', META, 'selected'], SELECTED)
expected = setIn(expected, ['nill', META, 'selected'], SELECTED_END)
assertDeepEqualEson(t, actual, expected)
assertDeepEqualEson(actual, expected)
// test whether old selection results are cleaned up
const selection2 = {
@ -382,10 +381,10 @@ test('selection (object)', t => {
let expected2 = eson
expected2 = setIn(expected2, ['nill', META, 'selected'], SELECTED)
expected2 = setIn(expected2, ['bool', META, 'selected'], SELECTED_END)
assertDeepEqualEson(t, actual2, expected2)
assertDeepEqualEson(actual2, expected2)
})
test('selection (array)', t => {
test('selection (array)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -405,10 +404,10 @@ test('selection (array)', t => {
expected = setIn(expected, ['obj', 'arr', '0', META, 'selected'], SELECTED_END)
expected = setIn(expected, ['obj', 'arr', '1', META, 'selected'], SELECTED)
assertDeepEqualEson(t, actual, expected)
assertDeepEqualEson(actual, expected)
})
test('selection (value)', t => {
test('selection (value)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -424,10 +423,10 @@ test('selection (value)', t => {
const actual = applySelection(eson, selection)
const expected = setIn(eson, ['obj', 'arr', '2', 'first', META, 'selected'], SELECTED_END)
assertDeepEqualEson(t, actual, expected)
assertDeepEqualEson(actual, expected)
})
test('selection (node)', t => {
test('selection (node)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -443,10 +442,10 @@ test('selection (node)', t => {
const actual = applySelection(eson, selection)
const expected = setIn(eson, ['obj', 'arr', META, 'selected'], SELECTED_END)
assertDeepEqualEson(t, actual, expected)
assertDeepEqualEson(actual, expected)
})
test('pathsFromSelection (object)', t => {
test('pathsFromSelection (object)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -460,14 +459,14 @@ test('pathsFromSelection (object)', t => {
end: ['nill']
}
t.deepEqual(pathsFromSelection(eson, selection), [
expect(pathsFromSelection(eson, selection)).toEqual([
['obj'],
['str'],
['nill']
])
})
test('pathsFromSelection (array)', t => {
test('pathsFromSelection (array)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -481,13 +480,13 @@ test('pathsFromSelection (array)', t => {
end: ['obj', 'arr', '0'] // note the "wrong" order of start and end
}
t.deepEqual(pathsFromSelection(eson, selection), [
expect(pathsFromSelection(eson, selection)).toEqual([
['obj', 'arr', '0'],
['obj', 'arr', '1']
])
})
test('pathsFromSelection (value)', t => {
test('pathsFromSelection (value)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -501,12 +500,12 @@ test('pathsFromSelection (value)', t => {
end: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(eson, selection), [
expect(pathsFromSelection(eson, selection)).toEqual([
['obj', 'arr', '2', 'first'],
])
})
test('pathsFromSelection (before)', t => {
test('pathsFromSelection (before)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -519,10 +518,10 @@ test('pathsFromSelection (before)', t => {
before: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(eson, selection), [])
expect(pathsFromSelection(eson, selection)).toEqual([])
})
test('pathsFromSelection (after)', t => {
test('pathsFromSelection (after)', () => {
const eson = jsonToEson({
"obj": {
"arr": [1,2, {"first":3,"last":4}]
@ -535,7 +534,7 @@ test('pathsFromSelection (after)', t => {
after: ['obj', 'arr', '2', 'first']
}
t.deepEqual(pathsFromSelection(eson, selection), [])
expect(pathsFromSelection(eson, selection)).toEqual([])
})
// helper function to print JSON in the console

View File

@ -0,0 +1,7 @@
import _JSONEditor from './components/JSONEditor'
export const JSONEditor = _JSONEditor
export default JSONEditor
// TODO: export util functions like immutability helpers

View File

@ -0,0 +1,275 @@
import React, { createElement as h, Component } from 'react'
import * as ReactDOM from 'react-dom'
import JSONEditor from './components/JSONEditor'
import CodeMode from './components/CodeMode'
import TextMode from './components/TextMode'
import TreeMode from './components/TreeMode'
import { compileJSONPointer, parseJSONPointer } from './eson'
const modes = {
code: CodeMode,
form: TreeMode,
text: TextMode,
tree: TreeMode,
view: TreeMode
}
/**
* Create a new json editor
* @param {HTMLElement} container
* @param {Options} options
* @return {Object}
* @constructor
*/
function jsoneditor (container, options = {}) {
if (arguments.length > 2) {
throw new Error ('Passing JSON via the constructor has been deprecated. ' +
'Please pass JSON via editor.set(json).')
}
const editor = {
isJSONEditor: true,
_container: container,
_options: options,
_schema: null,
_modes: modes,
_mode: null,
_component: null
}
/**
* Set JSON object in editor
* @param {Object | Array | string | number | boolean | null} json JSON data
* @param {Options} [options]
*/
editor.set = function (json, options = {}) {
// TODO: remove options from editor.set, move them to global options instead
editor._component.set(json, options)
}
/**
* Get JSON from the editor
* @returns {Object | Array | string | number | boolean | null} json
*/
editor.get = function () {
return editor._component.get()
}
/**
* Set a string containing a JSON document
* @param {string} text
*/
editor.setText = function (text) {
editor._component.setText(text)
}
/**
* Get the JSON document as text
* @return {string} text
*/
editor.getText = function () {
return editor._component.getText()
}
/**
* Format the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.format = function () {
const formatted = TextMode.format(editor._component.getText(), TextMode.getIndentation(this.props))
editor._component.setText(formatted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Compact the json.
* Only applicable for mode 'text' and 'code' (in other modes nothing will
* happen)
*/
editor.compact = function () {
const compacted = TextMode.compact(editor._component.getText())
editor._component.setText(compacted)
// TODO: test whether this doesn't destroy the current state
}
/**
* Set a JSON schema for validation of the JSON object.
* To remove the schema, call JSONEditor.setSchema(null)
* @param {Object | null} schema
*/
editor.setSchema = function (schema) {
editor._schema = schema || null
editor._component.setSchema(schema)
}
/**
* Expand one or multiple objects or arrays.
*
* Example usage:
*
* // expand one item at a specific path
* editor.expand(['foo', 1, 'bar'])
*
* // expand all items nested at a maximum depth of 2
* editor.expand(function (path) {
* return path.length <= 2
* })
*
* @param {Path | function (path: Path) : boolean} callback
*/
editor.expand = function (callback) {
editor._component.expand(callback)
}
/**
* Collapse one or multiple objects or arrays
*
* Example usage:
*
* // collapse one item at a specific path
* editor.collapse(['foo', 1, 'bar'])
*
* // collapse all items nested deeper than 2
* editor.collapse(function (path) {
* return path.length > 2
* })
*
* @param {Path | function (path: Path) : boolean} callback
*/
editor.collapse = function (callback) {
editor._component.collapse(callback)
}
/**
* Apply a JSONPatch to the current JSON document
* @param {Array} actions JSONPatch actions
* @return {Array} Returns a JSONPatch to revert the applied patch
*/
editor.patch = function (actions) {
return editor._component.patch(actions)
}
/**
* Change the mode of the editor
* @param {'tree' | 'text'} mode
*/
editor.setMode = function (mode) {
// TODO: strongly simplify .setMode, no error handling or logic here
if (mode === editor._mode) {
// mode stays the same. do nothing
return
}
let success = false
let initialChildCount = editor._container.children.length
let component = null
try {
// find the constructor for the selected mode
const constructor = editor._modes[mode]
if (!constructor) {
throw new Error('Unknown mode "' + mode + '". ' +
'Choose from: ' + Object.keys(modes).join(', '))
}
function handleChangeMode (mode) {
// we execute editor.setMode on the next tick, after the click event
// has been finished. This is a workaround for preact which does not
// neatly replace a rendered app whilst the event is still being handled.
setTimeout(() => {
const prevMode = editor._mode
editor.setMode(mode)
if (editor._options.onChangeMode) {
editor._options.onChangeMode(mode, prevMode)
}
})
}
function handleError (err) {
if (editor._options && editor._options.onError) {
editor._options.onError(err)
}
else {
console.error(err)
}
}
// create new component
component = ReactDOM.render(
h(constructor, {
...options,
mode,
onChangeMode: handleChangeMode,
onError: handleError
}),
editor._container)
// apply JSON schema (if any)
try {
component.setSchema(editor._schema)
}
catch (err) {
handleError(err)
}
// set JSON (this can throw an error)
const text = editor._component ? editor._component.getText() : '{}'
component.setText(text)
// when setText didn't fail, we will reach this point
success = true
}
catch (err) {
console.error(err)
}
finally {
if (success) {
editor._mode = mode
editor._component = component
}
else {
// TODO: fall back to text mode when loading code mode failed?
// remove the just created component if an error occurred during construction
// (for example when construction or setText failed)
const childCount = editor._container.children.length
if (childCount !== initialChildCount) {
editor._container.removeChild(editor._container.lastChild)
}
}
}
}
/**
* Remove the editor from the DOM and clean up workers
*/
editor.destroy = function () {
ReactDOM.unmountComponentAtNode(editor._container)
}
const mode = options && options.mode || (options.modes && options.modes[0]) || 'tree';
editor.setMode(mode)
return editor
}
// expose util functions
jsoneditor.utils = {
compileJSONPointer,
parseJSONPointer
}
// expose React component
jsoneditor.JSONEditor = JSONEditor
// expose React itself
jsoneditor.React = React
jsoneditor.ReactDOM = ReactDOM
module.exports = jsoneditor

View File

@ -2,7 +2,6 @@ import isEqual from 'lodash/isEqual'
import initial from 'lodash/initial'
import last from 'lodash/last'
import type { ESON, Path, ESONPatch } from './types'
import {
setIn, updateIn, getIn, deleteIn, insertAt,
cloneWithSymbols

View File

@ -1,10 +1,11 @@
'use strict'
import { readFileSync } from 'fs'
import test from 'ava'
import { META, jsonToEson, esonToJson } from '../src/eson'
import { patchEson } from '../src/patchEson'
import { META, jsonToEson, esonToJson } from './eson'
import { patchEson } from './patchEson'
import { assertDeepEqualEson } from './utils/assertDeepEqualEson'
test('jsonpatch add', t => {
test('jsonpatch add', () => {
const json = {
arr: [1,2,3],
obj: {a : 2}
@ -19,16 +20,16 @@ test('jsonpatch add', t => {
const patchedData = result.data
const revert = result.revert
assertDeepEqualEson(t, patchedData, jsonToEson({
assertDeepEqualEson(patchedData, jsonToEson({
arr: [1,2,3],
obj: {a : 2, b: {foo: 'bar'}}
}))
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'remove', path: '/obj/b'}
])
})
test('jsonpatch add: insert in matrix', t => {
test('jsonpatch add: insert in matrix', () => {
const json = {
arr: [1,2,3],
obj: {a : 2}
@ -43,16 +44,16 @@ test('jsonpatch add: insert in matrix', t => {
const patchedData = result.data
const revert = result.revert
assertDeepEqualEson(t, patchedData, jsonToEson({
assertDeepEqualEson(patchedData, jsonToEson({
arr: [1,4,2,3],
obj: {a : 2}
}))
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'remove', path: '/arr/1'}
])
})
test('jsonpatch add: append to matrix', t => {
test('jsonpatch add: append to matrix', () => {
const json = {
arr: [1,2,3],
obj: {a : 2}
@ -67,16 +68,16 @@ test('jsonpatch add: append to matrix', t => {
const patchedData = result.data
const revert = result.revert
assertDeepEqualEson(t, patchedData, jsonToEson({
assertDeepEqualEson(patchedData, jsonToEson({
arr: [1,2,3,4],
obj: {a : 2}
}))
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'remove', path: '/arr/3'}
])
})
test('jsonpatch remove', t => {
test('jsonpatch remove', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -93,11 +94,11 @@ test('jsonpatch remove', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
assertDeepEqualEson(t, patchedData, jsonToEson({
assertDeepEqualEson(patchedData, jsonToEson({
arr: [1,3],
obj: {}
}))
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'add', path: '/arr/1', value: 2, meta: {type: 'value'}},
{op: 'add', path: '/obj/a', value: 4, meta: {type: 'value', before: null}}
])
@ -109,11 +110,11 @@ test('jsonpatch remove', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, patch)
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual(patch)
})
test('jsonpatch replace', t => {
test('jsonpatch replace', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -130,11 +131,11 @@ test('jsonpatch replace', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
assertDeepEqualEson(t, patchedData, jsonToEson({
assertDeepEqualEson(patchedData, jsonToEson({
arr: [1,200,3],
obj: {a: 400}
}))
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'replace', path: '/arr/1', value: 2, meta: {type: 'value'}},
{op: 'replace', path: '/obj/a', value: 4, meta: {type: 'value'}}
])
@ -146,14 +147,14 @@ test('jsonpatch replace', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, [
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual([
{op: 'replace', path: '/obj/a', value: 400, meta: {type: 'value'}},
{op: 'replace', path: '/arr/1', value: 200, meta: {type: 'value'}}
])
})
test('jsonpatch replace (keep ids intact)', t => {
test('jsonpatch replace (keep ids intact)', () => {
const json = { value: 42 }
const patch = [
{op: 'replace', path: '/value', value: 100}
@ -165,10 +166,10 @@ test('jsonpatch replace (keep ids intact)', t => {
const patchedData = patchEson(data, patch).data
const patchedValueId = patchedData.value[META].id
t.is(patchedValueId, valueId)
expect(patchedValueId).toEqual(valueId)
})
test('jsonpatch copy', t => {
test('jsonpatch copy', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -184,11 +185,11 @@ test('jsonpatch copy', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
t.deepEqual(patchedJson, {
expect(patchedJson).toEqual({
arr: [1, 2, {a:4}, 3],
obj: {a: 4}
})
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'remove', path: '/arr/2'}
])
@ -199,13 +200,13 @@ test('jsonpatch copy', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, [
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual([
{op: 'add', path: '/arr/2', value: {a: 4}, meta: {type: 'Object'}}
])
})
test('jsonpatch copy (keeps the same ids)', t => {
test('jsonpatch copy (keeps the same ids)', () => {
const json = { foo: { bar: 42 } }
const patch = [
{op: 'copy', from: '/foo', path: '/copied'}
@ -221,16 +222,16 @@ test('jsonpatch copy (keeps the same ids)', t => {
const copiedId = patchedData.copied[META].id
const patchedCopiedBarId = patchedData.copied.bar[META].id
t.is(patchedFooId, fooId, 'same foo id')
t.is(patchedBarId, barId, 'same bar id')
expect(patchedFooId).toEqual(fooId)
expect(patchedBarId).toEqual(barId)
t.not(copiedId, fooId, 'different id of property copied')
expect(copiedId).not.toEqual(fooId)
// The id's of the copied childs are the same, that's okish, they will not bite each other
t.is(patchedCopiedBarId, patchedBarId, 'same copied bar id')
expect(patchedCopiedBarId).toEqual(patchedBarId)
})
test('jsonpatch move', t => {
test('jsonpatch move', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -246,11 +247,11 @@ test('jsonpatch move', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
t.is(result.error, null)
t.deepEqual(patchedJson, {
expect(result.error).toEqual(null)
expect(patchedJson).toEqual({
arr: [1, 2, {a:4}, 3]
})
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'move', from: '/arr/2', path: '/obj'}
])
@ -261,11 +262,11 @@ test('jsonpatch move', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, patch)
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual(patch)
})
test('jsonpatch move before', t => {
test('jsonpatch move before', () => {
const json = {
arr: [1,2,3],
obj: {a : 4},
@ -282,12 +283,12 @@ test('jsonpatch move before', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
t.is(result.error, null)
t.deepEqual(patchedJson, {
expect(result.error).toEqual(null)
expect(patchedJson).toEqual({
arr: [1, 2, {a:4}, 3],
zzz: 'zzz'
})
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'move', from: '/arr/2', path: '/obj', meta: {before: 'zzz'}}
])
@ -298,11 +299,11 @@ test('jsonpatch move before', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, patch)
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual(patch)
})
test('jsonpatch move and replace', t => {
test('jsonpatch move and replace', () => {
const json = { a: 2, b: 3 }
const patch = [
@ -317,11 +318,11 @@ test('jsonpatch move and replace', t => {
const patchedJson = esonToJson(patchedData)
// id of the replaced B must be kept intact
t.is(patchedData.b[META].id, data.b[META].id)
expect(patchedData.b[META].id).toEqual(data.b[META].id)
assertDeepEqualEson(t, patchedData, jsonToEson({b: 2}))
t.deepEqual(patchedJson, { b : 2 })
t.deepEqual(revert, [
assertDeepEqualEson(patchedData, jsonToEson({b: 2}))
expect(patchedJson).toEqual({ b : 2 })
expect(revert).toEqual([
{op:'move', from: '/b', path: '/a'},
{op:'add', path:'/b', value: 3, meta: {type: 'value', before: 'b'}}
])
@ -333,14 +334,14 @@ test('jsonpatch move and replace', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, [
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual([
{op: 'remove', path: '/b'},
{op: 'move', from: '/a', path: '/b'}
])
})
test('jsonpatch move and replace (nested)', t => {
test('jsonpatch move and replace (nested)', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -356,10 +357,10 @@ test('jsonpatch move and replace (nested)', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
t.deepEqual(patchedJson, {
expect(patchedJson).toEqual({
arr: {a:4}
})
t.deepEqual(revert, [
expect(revert).toEqual([
{op:'move', from: '/arr', path: '/obj'},
{op:'add', path:'/arr', value: [1,2,3], meta: {type: 'Array'}}
])
@ -371,14 +372,14 @@ test('jsonpatch move and replace (nested)', t => {
const revert2 = result2.revert
const patchedJson2 = esonToJson(patchedData2)
t.deepEqual(patchedJson2, json)
t.deepEqual(revert2, [
expect(patchedJson2).toEqual(json)
expect(revert2).toEqual([
{op: 'remove', path: '/arr'},
{op: 'move', from: '/obj', path: '/arr'}
])
})
test('jsonpatch move (keep id intact)', t => {
test('jsonpatch move (keep id intact)', () => {
const json = { value: 42 }
const patch = [
{op: 'move', from: '/value', path: '/moved'}
@ -390,10 +391,10 @@ test('jsonpatch move (keep id intact)', t => {
const patchedData = patchEson(data, patch).data
const patchedValueId = patchedData.moved[META].id
t.is(patchedValueId, valueId)
expect(patchedValueId).toEqual(valueId)
})
test('jsonpatch move and replace (keep ids intact)', t => {
test('jsonpatch move and replace (keep ids intact)', () => {
const json = { a: 2, b: 3 }
const patch = [
{op: 'move', from: '/a', path: '/b'}
@ -402,15 +403,15 @@ test('jsonpatch move and replace (keep ids intact)', t => {
const data = jsonToEson(json)
const bId = data.b[META].id
t.deepEqual(data[META].props, ['a', 'b'])
expect(data[META].props).toEqual(['a', 'b'])
const patchedData = patchEson(data, patch).data
t.is(patchedData.b[META].id, bId)
t.deepEqual(patchedData[META].props, ['b'])
expect(patchedData.b[META].id).toEqual(bId)
expect(patchedData[META].props).toEqual(['b'])
})
test('jsonpatch test (ok)', t => {
test('jsonpatch test (ok)', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -427,18 +428,18 @@ test('jsonpatch test (ok)', t => {
const revert = result.revert
const patchedJson = esonToJson(patchedData)
t.deepEqual(patchedJson, {
expect(patchedJson).toEqual({
arr: [1,2,3],
obj: {a : 4},
added: 'ok'
})
t.deepEqual(revert, [
expect(revert).toEqual([
{op: 'remove', path: '/added'}
])
})
test('jsonpatch test (fail: path not found)', t => {
test('jsonpatch test (fail: path not found)', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -456,15 +457,15 @@ test('jsonpatch test (fail: path not found)', t => {
const patchedJson = esonToJson(patchedData)
// patch shouldn't be applied
t.deepEqual(patchedJson, {
expect(patchedJson).toEqual({
arr: [1,2,3],
obj: {a : 4}
})
t.deepEqual(revert, [])
t.is(result.error.toString(), 'Error: Test failed, path not found')
expect(revert).toEqual([])
expect(result.error.toString()).toEqual('Error: Test failed, path not found')
})
test('jsonpatch test (fail: value not equal)', t => {
test('jsonpatch test (fail: value not equal)', () => {
const json = {
arr: [1,2,3],
obj: {a : 4}
@ -482,12 +483,12 @@ test('jsonpatch test (fail: value not equal)', t => {
const patchedJson = esonToJson(patchedData)
// patch shouldn't be applied
t.deepEqual(patchedJson, {
expect(patchedJson).toEqual({
arr: [1,2,3],
obj: {a : 4}
})
t.deepEqual(revert, [])
t.is(result.error.toString(), 'Error: Test failed, value differs')
expect(revert).toEqual([])
expect(result.error.toString()).toEqual('Error: Test failed, value differs')
})
// helper function to print JSON in the console

104
src/jsoneditor/types.js Normal file
View File

@ -0,0 +1,104 @@
/** JSDoc type definitions */
/**
* @typedef {{} | [] | string | number | boolean | null} JSON
*/
/**
* @typedef {{
* name: string?,
* mode: 'code' | 'form' | 'text' | 'tree' | 'view'?,
* modes: string[]?,
* history: boolean?,
* indentation: number | string?,
* onChange: function (patch: ESONPatch, revert: ESONPatch)?,
* onChangeText: function ()?,
* onChangeMode: function (mode: string, prevMode: string)?,
* onError: function (err: Error)?,
* isPropertyEditable: function (Path)?
* isValueEditable: function (Path)?,
* escapeUnicode: boolean?,
* expand: function(path: Path) : boolean?,
* ajv: Object?,
* ace: Object?
* }} Options
*/
/**
* @typedef {string[]} Path
*/
/**
* @typedef {{
* start?: Path,
* end?: Path,
* before?: Path,
* after?: Path,
* }} Selection
*/
/**
* @typedef {{matches: ESONPointer[], active: ESONPointer, text: String}} SearchResult
*/
/**
* @typedef {'value' | 'property'} ESONPointerArea
*/
/**
* @typedef {{
* path: Path,
* area?: ESONPointerArea
* }} ESONPointer
*/
/**
* @typedef {'normal' | 'active'} SearchResultStatus
*/
/**
* @typedef {'Object' | 'Array' | 'value' | 'string'} ESONType
*/
/**
* @typedef {{
* op: 'add' | 'remove' | 'replace' | 'copy' | 'move' | 'test',
* path: string,
* from?: string,
* value?: *,
* meta?: ESONPatchOptions
* }} ESONPatchAction
*/
/**
* @typedef {ESONPatchAction[]} ESONPatch
*/
/**
* @typedef {{
* id: string,
* path: Path,
* type: ESONType,
* before?: string
* props?: string[],
* expanded?: boolean,
* selected?: boolean,
* searchProperty?: SearchResultStatus,
* searchValue?: SearchResultStatus
* }} ESONPatchOptions
*
* // TODO: describe search results and selection
*/
/**
* @typedef {Object | Array} ESON
*/
/**
* TODO: change type of dataPath to Path? ESONPointer.path is an array, JSONSchemaError.path is a string -> make this consistent
*
* @typedef {{
* dataPath: string,
* message: string
* }} JSONSchemaError
*/

View File

@ -0,0 +1,39 @@
import {META} from "../eson"
import lodashTransform from "lodash/transform"
export function assertDeepEqualEson (actual, expected, path = [], ignoreIds = true) {
if (expected === undefined) {
throw new Error('Argument "expected" is undefined')
}
// console.log('assertDeepEqualEson', actual, expected)
const actualMeta = ignoreIds ? normalizeMetaIds(actual[META]) : actual[META]
const expectedMeta = ignoreIds ? normalizeMetaIds(expected[META]) : expected[META]
expect(actualMeta).toEqual(expectedMeta) // `Meta data not equal, path=[${path.join(', ')}], actual[META]=${JSON.stringify(actualMeta)}, expected[META]=${JSON.stringify(expectedMeta)}`
if (actualMeta.type === 'Array') {
expect(actual.length).toEqual(expected.length) // 'Actual lengths of arrays should be equal, path=[${path.join(\', \')}]'
actual.forEach((item, index) => assertDeepEqualEson(actual[index], expected[index], path.concat(index)), ignoreIds)
}
else if (actualMeta.type === 'Object') {
expect(Object.keys(actual).sort()).toEqual(Object.keys(expected).sort()) // 'Actual properties should be equal, path=[${path.join(\', \')}]'
actualMeta.props.forEach(key => assertDeepEqualEson(actual[key], expected[key], path.concat(key)), ignoreIds)
}
else { // actual[META].type === 'value'
expect(Object.keys(actual)).toEqual([]) // 'Value should not contain additional properties, path=[${path.join(\', \')}]'
}
}
function normalizeMetaIds (meta) {
return lodashTransform(meta, (result, value, key) => {
if (key === 'id') {
result[key] = '[ID]'
}
else {
result[key] = value
}
}, {})
}

View File

@ -188,7 +188,7 @@ export function getInternetExplorerVersion() {
if (navigator.appName === 'Microsoft Internet Explorer')
{
const ua = navigator.userAgent
const re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})")
const re = new RegExp("MSIE ([0-9]{1,}[.0-9]{0,})")
if (re.exec(ua) !== null) {
rv = parseFloat( RegExp.$1 )
}

View File

@ -1,6 +1,4 @@
'use strict';
import { isObjectOrArray, isObject } from './typeUtils'
import { isObjectOrArray } from './typeUtils'
/**
* Immutability helpers

View File

@ -1,8 +1,6 @@
import test from 'ava';
import { getIn, setIn, updateIn, deleteIn, insertAt, transform } from '../src/utils/immutabilityHelpers'
import { getIn, setIn, updateIn, deleteIn, insertAt } from './immutabilityHelpers'
test('getIn', t => {
test('getIn', () => {
const obj = {
a: {
b: {
@ -19,13 +17,13 @@ test('getIn', t => {
]
}
t.deepEqual(getIn(obj, ['a', 'b']), {c: 2})
t.is(getIn(obj, ['e', '1', 'f']), 5)
t.is(getIn(obj, ['e', '999', 'f']), undefined)
t.is(getIn(obj, ['non', 'existing', 'path']), undefined)
expect(getIn(obj, ['a', 'b'])).toEqual({c: 2})
expect(getIn(obj, ['e', '1', 'f'])).toEqual(5)
expect(getIn(obj, ['e', '999', 'f'])).toBeUndefined()
expect(getIn(obj, ['non', 'existing', 'path'])).toBeUndefined()
})
test('setIn basic', t => {
test('setIn basic', () => {
const obj = {
a: {
b: {
@ -36,7 +34,7 @@ test('setIn basic', t => {
}
const updated = setIn(obj, ['a', 'b', 'c'], 4)
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: {
c: 4
@ -46,7 +44,7 @@ test('setIn basic', t => {
})
// original should be unchanged
t.deepEqual (obj, {
expect(obj).toEqual({
a: {
b: {
c: 2
@ -55,25 +53,25 @@ test('setIn basic', t => {
d: 3
})
t.truthy (obj !== updated)
expect(obj).not.toBe(updated)
})
test('setIn non existing path', t => {
test('setIn non existing path', () => {
const obj = {}
t.throws(() => setIn(obj, ['a', 'b', 'c'], 4), /Path does not exist/)
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/)
})
test('setIn replace value with object should throw an exception', t => {
test('setIn replace value with object should throw an exception', () => {
const obj = {
a: 42,
d: 3
}
t.throws(() => setIn(obj, ['a', 'b', 'c'], 4), /Path does not exist/)
expect(() => setIn(obj, ['a', 'b', 'c'], 4)).toThrow(/Path does not exist/)
})
test('setIn replace value inside nested array', t => {
test('setIn replace value inside nested array', () => {
const obj = {
a: [
1,
@ -88,7 +86,7 @@ test('setIn replace value inside nested array', t => {
const updated = setIn(obj, ['a', '2', 'c'], 8)
t.deepEqual (updated, {
expect(updated).toEqual({
a: [
1,
2,
@ -101,23 +99,23 @@ test('setIn replace value inside nested array', t => {
})
})
test('setIn identical value should return the original object', t => {
test('setIn identical value should return the original object', () => {
const obj = {a:1, b:2}
const updated = setIn(obj, ['b'], 2)
t.is(updated, obj) // strict equal
expect(updated).toBe(obj) // strict equal
})
test('setIn identical value should return the original object (2)', t => {
test('setIn identical value should return the original object (2)', () => {
const obj = {a:1, b: { c: 2}}
const updated = setIn(obj, ['b', 'c'], 2)
t.is(updated, obj) // strict equal
expect(updated).toBe(obj) // strict equal
})
test('updateIn', t => {
test('updateIn', () => {
const obj = {
a: {
b: {
@ -128,7 +126,7 @@ test('updateIn', t => {
}
const updated = updateIn(obj, ['a', 'b', 'c'], (value) => value + 100)
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: {
c: 102
@ -138,7 +136,7 @@ test('updateIn', t => {
})
// original should be unchanged
t.deepEqual (obj, {
expect(obj).toEqual({
a: {
b: {
c: 2
@ -147,10 +145,10 @@ test('updateIn', t => {
d: 3
})
t.truthy (obj !== updated)
expect(obj).not.toBe(updated)
})
test('updateIn (2)', t => {
test('updateIn (2)', () => {
const obj = {
a: {
b: {
@ -161,7 +159,7 @@ test('updateIn (2)', t => {
}
const updated = updateIn(obj, ['a', 'b' ], (obj) => [1,2,3])
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: [1,2,3]
},
@ -169,7 +167,7 @@ test('updateIn (2)', t => {
})
})
test('updateIn (3)', t => {
test('updateIn (3)', () => {
const obj = {
a: {
b: {
@ -180,7 +178,7 @@ test('updateIn (3)', t => {
}
const updated = updateIn(obj, ['a', 'e' ], (value) => 'foo-' + value)
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: {
c: 2
@ -191,17 +189,17 @@ test('updateIn (3)', t => {
})
})
test('updateIn return identical value should return the original object', t => {
test('updateIn return identical value should return the original object', () => {
const obj = {
a: 2,
b: 3
}
const updated = updateIn(obj, ['b' ], (value) => 3)
t.is(updated, obj)
expect(updated).toBe(obj)
})
test('deleteIn', t => {
test('deleteIn', () => {
const obj = {
a: {
b: {
@ -213,7 +211,7 @@ test('deleteIn', t => {
}
const updated = deleteIn(obj, ['a', 'b', 'c'])
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: {
d: 3
@ -223,7 +221,7 @@ test('deleteIn', t => {
})
// original should be unchanged
t.deepEqual (obj, {
expect(obj).toEqual({
a: {
b: {
c: 2,
@ -233,10 +231,10 @@ test('deleteIn', t => {
e: 4
})
t.truthy (obj !== updated)
expect(obj).not.toBe(updated)
})
test('deleteIn array', t => {
test('deleteIn array', () => {
const obj = {
a: {
b: [1, {c: 2, d: 3} , 4]
@ -245,7 +243,7 @@ test('deleteIn array', t => {
}
const updated = deleteIn(obj, ['a', 'b', '1', 'c'])
t.deepEqual (updated, {
expect(updated).toEqual({
a: {
b: [1, {d: 3} , 4]
},
@ -253,26 +251,26 @@ test('deleteIn array', t => {
})
// original should be unchanged
t.deepEqual (obj, {
expect(obj).toEqual({
a: {
b: [1, {c: 2, d: 3} , 4]
},
e: 5
})
t.truthy (obj !== updated)
expect(obj).not.toBe(updated)
})
test('deleteIn non existing path', t => {
test('deleteIn non existing path', () => {
const obj = { a: {}}
const updated = deleteIn(obj, ['a', 'b'])
t.truthy (updated === obj)
expect(updated).toBe(obj)
})
test('insertAt', t => {
test('insertAt', () => {
const obj = { a: [1,2,3]}
const updated = insertAt(obj, ['a', '2'], 8)
t.deepEqual(updated, {a: [1,2,8,3]})
expect(updated).toEqual({a: [1,2,8,3]})
})

View File

@ -78,7 +78,7 @@ function normalizeKeyCombo (combo) {
return upper
}
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0
const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0
const metaCodes = {
'Ctrl': true,

View File

@ -18,7 +18,7 @@ export function escapeHTML (text, escapeUnicode = false) {
}
htmlEscaped = htmlEscaped
.replace(/ /g, ' \u00A0') // replace double space with an nbsp and space
.replace(/ {2}/g, ' \u00A0') // replace double space with an nbsp and space
.replace(/^ /, '\u00A0') // space at start
.replace(/ $/, '\u00A0') // space at end
@ -48,8 +48,8 @@ export function escapeUnicodeChars (text) {
* @return {String} text
*/
export function unescapeHTML (escapedText) {
var json = '"' + escapeJSON(escapedText) + '"'
var htmlEscaped = parseJSON(json)
const json = '"' + escapeJSON(escapedText) + '"'
const htmlEscaped = parseJSON(json)
return htmlEscaped.replace(/\u00A0/g, ' ') // nbsp character
}
@ -65,24 +65,24 @@ export function unescapeHTML (escapedText) {
*/
export function escapeJSON (text) {
// TODO: replace with some smart regex (only when a new solution is faster!)
var escaped = ''
var i = 0
let escaped = ''
let i = 0
while (i < text.length) {
var c = text.charAt(i)
if (c == '\n') {
let c = text.charAt(i)
if (c === '\n') {
escaped += '\\n'
}
else if (c == '\\') {
else if (c === '\\') {
escaped += c
i++
c = text.charAt(i)
if (c === '' || '"\\/bfnrtu'.indexOf(c) == -1) {
if (c === '' || '"\\/bfnrtu'.indexOf(c) === -1) {
escaped += '\\' // no valid escape character
}
escaped += c
}
else if (c == '"') {
else if (c === '"') {
escaped += '\\"'
}
else {

View File

@ -0,0 +1,25 @@
import { escapeHTML, unescapeHTML, findUniqueName } from './stringUtils'
test('escapeHTML', () => {
expect(escapeHTML(' hello ')).toEqual('\u00A0\u00A0 hello \u00A0')
expect(escapeHTML('\u00A0 hello')).toEqual('\u00A0 hello')
expect(escapeHTML('hello\nworld')).toEqual('hello\\nworld')
// TODO: test escapeHTML more thoroughly
})
test('unescapeHTML', () => {
expect(unescapeHTML(' \u00A0 hello \u00A0')).toEqual(' hello ')
expect(unescapeHTML('\u00A0 hello')).toEqual(' hello')
expect(unescapeHTML('hello\\nworld')).toEqual('hello\nworld')
// TODO: test unescapeHTML more thoroughly
})
test('findUniqueName', () => {
expect(findUniqueName('other', ['a', 'b', 'c'])).toEqual('other')
expect(findUniqueName('b', ['a', 'b', 'c'])).toEqual('b (copy)')
expect(findUniqueName('b', ['a', 'b', 'c', 'b (copy)'])).toEqual('b (copy 2)')
expect(findUniqueName('b', ['a', 'b', 'c', 'b (copy)', 'b (copy 2)'])).toEqual('b (copy 3)')
})

View File

@ -1,114 +0,0 @@
// @flow
/**
*
* @typedef {{
* name: string?,
* mode: 'code' | 'form' | 'text' | 'tree' | 'view'?,
* modes: string[]?,
* history: boolean?,
* indentation: number | string?,
* onChange: function (patch: ESONPatch, revert: ESONPatch)?,
* onChangeText: function ()?,
* onChangeMode: function (mode: string, prevMode: string)?,
* onError: function (err: Error)?,
* isPropertyEditable: function (Path)?
* isValueEditable: function (Path)?,
* escapeUnicode: boolean?,
* expand: function(path: Path) : boolean?,
* ajv: Object?,
* ace: Object?
* }} Options
*
* @typedef {string[]} Path
*
* @typedef {{matches: ESONPointer[], active: ESONPointer, text: String}} SearchResult
*
*/
// FIXME: redefine all ESON related types
/**************************** GENERIC JSON TYPES ******************************/
export type JSONType = | string | number | boolean | null | JSONObjectType | JSONArrayType
export type JSONObjectType = { [key:string]: JSONType }
export type JSONArrayType = JSONType[]
/********************** TYPES FOR THE ESON OBJECT MODEL *************************/
export type SearchResultStatus = 'normal' | 'active'
export type ESONPointerArea = 'value' | 'property'
export type ESONObject = {
_meta: {
type: 'Object',
path: Path,
expanded?: boolean,
selected?: boolean,
searchProperty?: SearchResultStatus,
searchValue?: SearchResultStatus
}
}
export type ESONArray = {
_meta: {
type: 'Array',
path: Path,
length: number
expanded?: boolean,
selected?: boolean,
searchProperty?: SearchResultStatus,
searchValue?: SearchResultStatus
}
}
export type ESONValue = {
_meta: {
type: 'value' | 'string',
path: Path,
value: null | boolean | string | number,
selected?: boolean,
searchProperty?: SearchResultStatus,
searchValue?: SearchResultStatus
}
}
export type ESON = ESONObject | ESONArray | ESONValue
export type ESONType = 'Object' | 'Array' | 'value' | 'string'
export type Path = string[]
export type ESONPointer = {
path: Path,
area?: ESONPointerArea
}
export type Selection = {
start?: Path,
end?: Path,
before?: Path,
after?: Path
}
export type ESONPatchAction = {
op: string, // TODO: define allowed ops
path: string,
from?: string,
value?: any,
meta?: ESONPatchOptions
}
export type ESONPatch = ESONPatchAction[]
export type ESONPatchOptions = {
type: ESONType,
expand: (Path) => boolean
}
// TODO: ESONPointer.path is an array, JSONSchemaError.path is a string -> make this consistent
export type JSONSchemaError = {
dataPath: string, // TODO: change type to Path
message: string
}

View File

@ -1,244 +0,0 @@
/* ContextMenu - main menu */
div.jsoneditor-contextmenu-root {
position: relative;
width: 0;
height: 0;
}
div.jsoneditor-contextmenu {
position: absolute;
box-sizing: content-box;
z-index: 99999;
}
div.jsoneditor-contextmenu ul,
div.jsoneditor-contextmenu li {
box-sizing: content-box;
}
div.jsoneditor-contextmenu ul {
position: relative;
left: 0;
top: 0;
width: 124px;
background: white;
border: 1px solid #d3d3d3;
box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3);
list-style: none;
margin: 0;
padding: 0;
}
div.jsoneditor-contextmenu ul li button {
padding: 0;
margin: 0;
width: 124px;
height: 24px;
border: none;
cursor: pointer;
color: #4d4d4d;
background: transparent;
font-size: 10pt;
font-family: arial, sans-serif;
box-sizing: border-box;
line-height: 26px;
text-align: left;
}
/* Fix button padding in firefox */
div.jsoneditor-contextmenu ul li 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 {
width: 92px;
}
div.jsoneditor-contextmenu ul li button.jsoneditor-expand {
float: right;
width: 32px;
height: 24px;
border-left: 1px solid #e5e5e5;
}
div.jsoneditor-contextmenu div.jsoneditor-icon {
float: left;
width: 24px;
height: 24px;
border: none;
padding: 0;
margin: 0;
background-image: url('img/jsoneditor-icons.svg');
}
div.jsoneditor-contextmenu ul li button div.jsoneditor-expand {
float: right;
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url('img/jsoneditor-icons.svg') 0 -72px;
opacity: 0.4;
}
div.jsoneditor-contextmenu ul li button:hover div.jsoneditor-expand,
div.jsoneditor-contextmenu ul li button:focus div.jsoneditor-expand,
div.jsoneditor-contextmenu ul li.jsoneditor-selected div.jsoneditor-expand,
div.jsoneditor-contextmenu ul li button.jsoneditor-expand:hover div.jsoneditor-expand,
div.jsoneditor-contextmenu ul li button.jsoneditor-expand:focus div.jsoneditor-expand {
opacity: 1;
}
div.jsoneditor-contextmenu div.jsoneditor-separator {
height: 0;
border-top: 1px solid #e5e5e5;
padding-top: 5px;
margin-top: 5px;
}
div.jsoneditor-contextmenu button.jsoneditor-remove > div.jsoneditor-icon {
background-position: -24px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-remove:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-remove:focus > div.jsoneditor-icon {
background-position: -24px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-append > div.jsoneditor-icon {
background-position: 0 -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-append:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-append:focus > div.jsoneditor-icon {
background-position: 0 0;
}
div.jsoneditor-contextmenu button.jsoneditor-insert > div.jsoneditor-icon {
background-position: 0 -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-insert:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-insert:focus > div.jsoneditor-icon {
background-position: 0 0;
}
div.jsoneditor-contextmenu button.jsoneditor-duplicate > div.jsoneditor-icon {
background-position: -48px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-duplicate:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-duplicate:focus > div.jsoneditor-icon {
background-position: -48px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-sort-asc > div.jsoneditor-icon {
background-position: -168px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-sort-asc:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-sort-asc:focus > div.jsoneditor-icon {
background-position: -168px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-sort-desc > div.jsoneditor-icon {
background-position: -192px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-sort-desc:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-sort-desc:focus > div.jsoneditor-icon {
background-position: -192px 0;
}
/* 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.jsoneditor-selected ul {
}
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 {
background-position: -144px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-type-string:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-string:focus > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-string.jsoneditor-selected > div.jsoneditor-icon{
background-position: -144px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-type-auto > div.jsoneditor-icon {
background-position: -120px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-type-auto:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-auto:focus > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-auto.jsoneditor-selected > div.jsoneditor-icon {
background-position: -120px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-type-object > div.jsoneditor-icon {
background-position: -72px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-type-object:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-object:focus > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-object.jsoneditor-selected > div.jsoneditor-icon{
background-position: -72px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-type-array > div.jsoneditor-icon {
background-position: -96px -24px;
}
div.jsoneditor-contextmenu button.jsoneditor-type-array:hover > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-array:focus > div.jsoneditor-icon,
div.jsoneditor-contextmenu button.jsoneditor-type-array.jsoneditor-selected > div.jsoneditor-icon{
background-position: -96px 0;
}
div.jsoneditor-contextmenu button.jsoneditor-type-modes > div.jsoneditor-icon {
background-image: none;
width: 6px;
}

View File

@ -1,14 +0,0 @@
JSON Editor Icons
size: outer: 24x24 px
inner: 16x16 px
blue background: RGBA 97b0f8ff
gray background: RGBA 4d4d4dff
grey background: RGBA d3d3d3ff
red foreground: RGBA ff3300ff
green foreground: RGBA 13ae00ff
characters are based on the Arial font

View File

@ -1,893 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="216"
height="144"
id="svg4136"
version="1.1"
inkscape:version="0.91 r"
sodipodi:docname="jsoneditor-icons.svg">
<title
id="title6512">JSON Editor Icons</title>
<metadata
id="metadata4148">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>JSON Editor Icons</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs4146" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1028"
id="namedview4144"
showgrid="true"
inkscape:zoom="4"
inkscape:cx="97.217248"
inkscape:cy="59.950227"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4136"
showguides="false"
borderlayer="false"
inkscape:showpageshadow="true"
showborder="true">
<inkscape:grid
type="xygrid"
id="grid4640"
empspacing="24" />
</sodipodi:namedview>
<!-- Created with SVG-edit - http://svg-edit.googlecode.com/ -->
<g
id="g4394">
<rect
x="4"
y="4"
width="16"
height="16"
id="svg_1"
style="fill:#1aae1c;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0"
x="28.000006"
y="3.999995"
width="16"
height="16"
id="svg_1-7" />
<rect
id="rect4165"
height="16"
width="16"
y="3.999995"
x="52.000004"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
x="172.00002"
y="3.9999852"
width="16"
height="16"
id="rect4175" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
x="196"
y="3.999995"
width="16"
height="16"
id="rect4175-3" />
<g
style="stroke:none"
id="g4299">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-1"
height="1.9999986"
width="9.9999924"
y="10.999998"
x="7.0000048" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-1-1"
height="9.9999838"
width="1.9999955"
y="7.0000114"
x="11.000005" />
</g>
<g
style="stroke:none"
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
id="g4299-3">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-1-0"
height="1.9999986"
width="9.9999924"
y="10.999998"
x="7.0000048" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-1-1-9"
height="9.9999838"
width="1.9999955"
y="7.0000114"
x="11.000005" />
</g>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="55.000004"
y="7.0000048"
width="6.9999909"
height="6.9999905"
id="svg_1-7-5" />
<rect
id="rect4354"
height="6.9999905"
width="6.9999909"
y="10.00001"
x="58"
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647"
x="58.000004"
y="10.000005"
width="6.9999909"
height="6.9999905"
id="svg_1-7-5-7" />
<g
id="g4378">
<rect
id="svg_1-7-5-3"
height="1.9999965"
width="7.9999909"
y="10.999999"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="7.0000005"
width="11.999995"
height="1.9999946"
id="rect4374" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="14.999996"
width="3.9999928"
height="1.9999995"
id="rect4376" />
</g>
<g
id="g4383"
transform="matrix(1,0,0,-1,-23.999995,23.999995)">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="10.999999"
width="7.9999909"
height="1.9999965"
id="rect4385" />
<rect
id="rect4387"
height="1.9999946"
width="11.999995"
y="7.0000005"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
id="rect4389"
height="1.9999995"
width="3.9999928"
y="14.999996"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
</g>
<rect
y="3.9999199"
x="76"
height="16"
width="16"
id="rect3754-4"
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="cccccccc"
inkscape:connector-curvature="0"
id="path4351"
d="m 85.10447,6.0157384 -0.0156,1.4063 c 3.02669,-0.2402 0.33008,3.6507996 2.48438,4.5780996 -2.18694,1.0938 0.49191,4.9069 -2.45313,4.5781 l -0.0156,1.4219 c 5.70828,0.559 1.03264,-5.1005 4.70313,-5.2656 l 0,-1.4063 c -3.61303,-0.027 1.11893,-5.7069996 -4.70313,-5.3124996 z"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccccccc"
inkscape:connector-curvature="0"
id="path4351-9"
d="m 82.78125,5.9984384 0.0156,1.4063 c -3.02668,-0.2402 -0.33007,3.6506996 -2.48437,4.5780996 2.18694,1.0938 -0.49192,4.9069 2.45312,4.5781 l 0.0156,1.4219 c -5.70827,0.559 -1.03263,-5.1004 -4.70312,-5.2656 l 0,-1.4063 c 3.61303,-0.027 -1.11894,-5.7070996 4.70312,-5.3124996 z"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
y="3.9999199"
x="100"
height="16"
width="16"
id="rect3754-25"
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
<path
inkscape:connector-curvature="0"
id="path2987"
d="m 103.719,5.6719384 0,12.7187996 3.03125,0 0,-1.5313 -1.34375,0 0,-9.6249996 1.375,0 0,-1.5625 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
inkscape:connector-curvature="0"
id="path2987-1"
d="m 112.2185,5.6721984 0,12.7187996 -3.03125,0 0,-1.5313 1.34375,0 0,-9.6249996 -1.375,0 0,-1.5625 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
y="3.9999199"
x="124"
height="16"
width="16"
id="rect3754-73"
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0"
id="path3780"
d="m 126.2824,17.602938 1.78957,0 1.14143,-2.8641 5.65364,0 1.14856,2.8641 1.76565,0 -4.78687,-11.1610996 -1.91903,0 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
inkscape:connector-curvature="0"
id="path3782"
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
<rect
y="3.9999199"
x="148"
height="16"
width="16"
id="rect3754-35"
style="fill:#4c4c4c;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0"
id="path5008-2"
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0"
id="path5008-2-8"
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<rect
x="4"
y="27.999994"
width="16"
height="16"
id="rect4432"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0"
x="28.000006"
y="27.99999"
width="16"
height="16"
id="rect4434" />
<rect
id="rect4436"
height="16"
width="16"
y="27.99999"
x="52.000004"
style="fill:#d3d3d3;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
x="172.00002"
y="27.999981"
width="16"
height="16"
id="rect4446" />
<rect
style="fill:#d3d3d3;stroke:#000000;stroke-width:0"
x="196"
y="27.99999"
width="16"
height="16"
id="rect4448" />
<g
id="g4466"
style="stroke:none"
transform="translate(0,23.999995)">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="rect4468"
height="1.9999986"
width="9.9999924"
y="10.999998"
x="7.0000048" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="rect4470"
height="9.9999838"
width="1.9999955"
y="7.0000114"
x="11.000005" />
</g>
<g
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,35.999996)"
id="g4472"
style="stroke:none">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="rect4474"
height="1.9999986"
width="9.9999924"
y="10.999998"
x="7.0000048" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0"
id="rect4476"
height="9.9999838"
width="1.9999955"
y="7.0000114"
x="11.000005" />
</g>
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="55.000004"
y="31"
width="6.9999909"
height="6.9999905"
id="rect4478" />
<rect
id="rect4480"
height="6.9999905"
width="6.9999909"
y="34.000008"
x="58"
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none"
x="58.000004"
y="34.000004"
width="6.9999909"
height="6.9999905"
id="rect4482" />
<g
id="g4484"
transform="translate(0,23.999995)">
<rect
id="rect4486"
height="1.9999965"
width="7.9999909"
y="10.999999"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="7.0000005"
width="11.999995"
height="1.9999946"
id="rect4488" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="14.999996"
width="3.9999928"
height="1.9999995"
id="rect4490" />
</g>
<g
id="g4492"
transform="matrix(1,0,0,-1,-23.999995,47.99999)">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="10.999999"
width="7.9999909"
height="1.9999965"
id="rect4494" />
<rect
id="rect4496"
height="1.9999946"
width="11.999995"
y="7.0000005"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
id="rect4498"
height="1.9999995"
width="3.9999928"
y="14.999996"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
</g>
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
id="rect3754-8"
width="16"
height="16"
x="76"
y="27.99992" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 85.10448,30.015537 -0.0156,1.4063 c 3.02668,-0.2402 0.33007,3.6508 2.48438,4.5781 -2.18695,1.0938 0.49191,4.90688 -2.45313,4.57808 l -0.0156,1.4219 c 5.70827,0.559 1.03263,-5.10048 4.70313,-5.26558 l 0,-1.4063 c -3.61304,-0.027 1.11893,-5.707 -4.70313,-5.3125 z"
id="path4351-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 82.78126,29.998237 0.0156,1.4063 c -3.02668,-0.2402 -0.33008,3.6507 -2.48438,4.5781 2.18694,1.0938 -0.49191,4.90688 2.45313,4.57808 l 0.0156,1.4219 c -5.70828,0.559 -1.03264,-5.10038 -4.70313,-5.26558 l 0,-1.4063 c 3.61303,-0.027 -1.11893,-5.7071 4.70313,-5.3125 z"
id="path4351-9-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
id="rect3754-65"
width="16"
height="16"
x="100"
y="27.99992" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 103.719,29.671937 0,12.71878 3.03125,0 0,-1.5313 -1.34375,0 0,-9.62498 1.375,0 0,-1.5625 z"
id="path2987-8"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 112.2185,29.671937 0,12.71878 -3.03125,0 0,-1.5313 1.34375,0 0,-9.62498 -1.375,0 0,-1.5625 z"
id="path2987-1-9"
inkscape:connector-curvature="0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
id="rect3754-92"
width="16"
height="16"
x="124"
y="27.99992" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 126.2824,41.602917 1.78957,0 1.14143,-2.86408 5.65364,0 1.14856,2.86408 1.76565,0 -4.78687,-11.16108 -1.91902,0 z"
id="path3780-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
d="m 129.72704,37.478837 4.60852,0.01 -2.30426,-5.5498 z"
id="path3782-2"
inkscape:connector-curvature="0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none"
id="rect3754-47"
width="16"
height="16"
x="148"
y="27.99992" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 156.47656,29.891737 0,2.1797 0.46093,2.3984 1.82813,0 0.39844,-2.3984 0,-2.1797 z"
id="path5008-2-1"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 152.51562,29.890637 0,2.1797 0.46094,2.3984 1.82812,0 0.39844,-2.3984 0,-2.1797 z"
id="path5008-2-8-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<rect
id="svg_1-7-2"
height="1.9999961"
width="11.999996"
y="64"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="svg_1-7-2-2"
height="2.9999905"
width="2.9999907"
y="52"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="52"
width="2.9999907"
height="2.9999905"
id="rect4561" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="80.000008"
y="58"
width="2.9999907"
height="2.9999905"
id="rect4563" />
<rect
id="rect4565"
height="2.9999905"
width="2.9999907"
y="58"
x="85.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
id="rect4567"
height="2.9999905"
width="2.9999907"
y="64"
x="80.000008"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="85.000008"
y="64"
width="2.9999907"
height="2.9999905"
id="rect4569" />
<circle
style="opacity:1;fill:none;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4571"
cx="110.06081"
cy="57.939209"
r="4.7438836" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="116.64566"
y="-31.79752"
width="4.229713"
height="6.4053884"
id="rect4563-2"
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" />
<path
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 125,56 138.77027,56.095 132,64 Z"
id="path4613"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615"
d="M 149,64 162.77027,63.905 156,56 Z"
style="fill:#4c4c4c;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="54"
y="53"
width="11.999996"
height="1.9999961"
id="rect4638" />
<rect
id="svg_1-7-2-24"
height="1.9999957"
width="12.99999"
y="-56"
x="53"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
transform="matrix(0,1,-1,0,0,0)" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0"
x="53"
y="-66"
width="12.99999"
height="1.9999957"
id="rect4657" />
<rect
id="rect4659"
height="0.99999291"
width="11.999999"
y="57"
x="54"
style="fill:#4c4c4c;fill-opacity:0.98431373;stroke:none;stroke-width:0" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="88.000122"
width="11.999996"
height="1.9999961"
id="rect4661" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="76.000122"
width="2.9999907"
height="2.9999905"
id="rect4663" />
<rect
id="rect4665"
height="2.9999905"
width="2.9999907"
y="76.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
id="rect4667"
height="2.9999905"
width="2.9999907"
y="82.000122"
x="80.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="85.000008"
y="82.000122"
width="2.9999907"
height="2.9999905"
id="rect4669" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="80.000008"
y="88.000122"
width="2.9999907"
height="2.9999905"
id="rect4671" />
<rect
id="rect4673"
height="2.9999905"
width="2.9999907"
y="88.000122"
x="85.000008"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<circle
r="4.7438836"
cy="81.939331"
cx="110.06081"
id="circle4675"
style="opacity:1;fill:none;fill-opacity:1;stroke:#d3d3d3;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)"
id="rect4677"
height="6.4053884"
width="4.229713"
y="-14.826816"
x="133.6163"
style="fill:#d3d3d3;fill-opacity:1;stroke:#d3d3d3;stroke-width:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4679"
d="m 125,80.000005 13.77027,0.09499 L 132,87.999992 Z"
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:#d3d3d3;fill-opacity:1;fill-rule:evenodd;stroke:#d3d3d3;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 149,88.0002 162.77027,87.9052 156,80.0002 Z"
id="path4681"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<rect
id="rect4683"
height="1.9999961"
width="11.999996"
y="77.000122"
x="54"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1" />
<rect
transform="matrix(0,1,-1,0,0,0)"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="77.000122"
y="-56"
width="12.99999"
height="1.9999957"
id="rect4685" />
<rect
id="rect4687"
height="1.9999957"
width="12.99999"
y="-66"
x="77.000122"
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
transform="matrix(0,1,-1,0,0,0)" />
<rect
style="fill:#d3d3d3;fill-opacity:1;stroke:none;stroke-width:0;stroke-opacity:1"
x="54"
y="81.000122"
width="11.999999"
height="0.99999291"
id="rect4689" />
<rect
id="rect4761-1"
height="1.9999945"
width="15.99999"
y="101"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.80000007;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" />
<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" />
<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" />
<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" />
<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" />
<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" />
<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"
d="m 36.398438,100.0254 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,100.5991 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1452 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533865,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550756,0 6.710442,-2.4113 7.650391,-5.9414 0.939949,-3.5301 -0.618463,-7.2736 -3.710938,-9.0703 -1.159678,-0.6738 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138" />
<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"
d="m 59.722656,99.9629 c -1.270084,0.039 -2.541493,0.3887 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5402 -3.710937,9.0703 0.939949,3.5301 4.09768,5.9414 7.648437,5.9414 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4056 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,100 0,2 -2.4999996,0 L 12,107 l 4,-5 -2.5,0 0,-2 -3,0 z"
id="path3055-0-77" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9850574,108.015 14.0298856,-0.03"
id="path5244-5-0-5"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="opacity:0.8;fill:none;stroke:#ffffff;stroke-width:1.966;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 4.9849874,132.015 14.0298866,-0.03"
id="path5244-5-0-5-8"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<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.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;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"
d="m 36.398438,123.9629 c -0.423362,-0.013 -0.846847,0.01 -1.265626,0.062 -1.656562,0.2196 -3.244567,0.9739 -4.507812,2.2266 L 29,124.5366 l -2.324219,7.7129 7.826172,-1.9062 -1.804687,-1.9063 c 1.597702,-1.5308 4.048706,-1.8453 5.984375,-0.7207 1.971162,1.1453 2.881954,3.3975 2.308593,5.5508 -0.573361,2.1533 -2.533864,3.6953 -4.830078,3.6953 l 0,3.0742 c 3.550757,0 6.710442,-2.4093 7.650391,-5.9394 0.939949,-3.5301 -0.618463,-7.2756 -3.710938,-9.0723 -1.159678,-0.6737 -2.431087,-1.0231 -3.701171,-1.0625 z"
id="path4138-12" />
<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.4;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#4d4d4d;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"
d="m 59.722656,123.9629 c -1.270084,0.039 -2.541493,0.3888 -3.701172,1.0625 -3.092475,1.7967 -4.650886,5.5422 -3.710937,9.0723 0.939949,3.5301 4.09768,5.9394 7.648437,5.9394 l 0,-3.0742 c -2.296214,0 -4.256717,-1.542 -4.830078,-3.6953 -0.573361,-2.1533 0.337432,-4.4055 2.308594,-5.5508 1.935731,-1.1246 4.38863,-0.8102 5.986326,0.7207 l -1.806638,1.9063 7.828128,1.9062 -2.32422,-7.7129 -1.62696,1.7168 c -1.26338,-1.2531 -2.848917,-2.0088 -4.505855,-2.2285 -0.418778,-0.055 -0.842263,-0.076 -1.265625,-0.062 z"
id="path4138-1-3" />
<path
id="path6191"
d="m 10.5,116 0,-2 -2.4999996,0 L 12,109 l 4,5 -2.5,0 0,2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
d="m 10.5,129 0,-2 -2.4999996,0 L 12,122 l 4,5 -2.5,0 0,2 -3,0 z"
id="path6193" />
<path
id="path6195"
d="m 10.5,135 0,2 -2.4999996,0 L 12,142 l 4,-5 -2.5,0 0,-2 -3,0 z"
style="opacity:0.8;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.966;stroke-miterlimit:4;stroke-dasharray:none"
inkscape:connector-curvature="0" />
<path
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4500"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073242"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073242 -3.833708,2.213392 -3.8337072,2.213393 0,-4.426785 0,-4.426784 3.8337082,2.213392 z"
inkscape:transform-center-x="-1.2779026" />
<path
inkscape:transform-center-x="1.277902"
d="m -31.500004,60.073242 -3.833708,2.213392 -3.833707,2.213393 0,-4.426785 0,-4.426784 3.833707,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073242"
sodipodi:cx="-36.611614"
sodipodi:sides="3"
id="path4502"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="scale(-1,1)" />
<path
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z"
inkscape:randomized="0"
inkscape:rounded="0"
inkscape:flatsided="false"
sodipodi:arg2="1.0471976"
sodipodi:arg1="0"
sodipodi:r2="2.5558052"
sodipodi:r1="5.1116104"
sodipodi:cy="60.073212"
sodipodi:cx="11.55581"
sodipodi:sides="3"
id="path4504"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
sodipodi:type="star"
transform="matrix(0,1,-1,0,72.0074,71.7877)"
inkscape:transform-center-y="1.2779029" />
<path
inkscape:transform-center-y="-1.2779026"
transform="matrix(0,-1,-1,0,96,96)"
sodipodi:type="star"
style="fill:#4d4d4d;fill-opacity:0.90196078;stroke:#d3d3d3;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none"
id="path4506"
sodipodi:sides="3"
sodipodi:cx="11.55581"
sodipodi:cy="60.073212"
sodipodi:r1="5.1116104"
sodipodi:r2="2.5558052"
sodipodi:arg1="0"
sodipodi:arg2="1.0471976"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 16.66742,60.073212 -3.833708,2.213392 -3.8337072,2.213392 0,-4.426784 0,-4.426785 3.8337082,2.213392 z" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4615-5"
d="m 171.82574,65.174193 16.34854,0 -8.17427,-13.348454 z"
style="fill:#fbb917;fill-opacity:1;fill-rule:evenodd;stroke:#fbb917;stroke-width:1.65161395;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,55 0,6 2,0 0,-6"
id="path4300"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 179,62 0,2 2,0 0,-2"
id="path4300-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</svg>

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -1,449 +0,0 @@
div.jsoneditor {
}
div.jsoneditor-field,
div.jsoneditor-value,
div.jsoneditor-readonly {
border: 1px solid transparent;
min-height: 16px;
min-width: 32px;
padding: 2px;
margin: 1px;
word-wrap: break-word;
float: left;
}
/* adjust margin of p elements inside editable divs, needed for Opera, IE */
div.jsoneditor-field p,
div.jsoneditor-value p {
margin: 0;
}
div.jsoneditor-value {
word-break: break-word;
}
div.jsoneditor-readonly {
min-width: 16px;
color: gray;
}
div.jsoneditor-empty {
border-color: lightgray;
border-style: dashed;
border-radius: 2px;
}
div.jsoneditor-field.jsoneditor-empty::after,
div.jsoneditor-value.jsoneditor-empty::after {
pointer-events: none;
color: lightgray;
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.jsoneditor-separator {
padding: 3px 0;
vertical-align: top;
color: gray;
}
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-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-string {
color: #008000;
}
div.jsoneditor-value.jsoneditor-object,
div.jsoneditor-value.jsoneditor-array {
min-width: 16px;
color: #808080;
}
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-tree button {
width: 24px;
height: 24px;
padding: 0;
margin: 0;
border: none;
cursor: pointer;
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-collapsed {
background-position: 0 -48px;
}
div.jsoneditor-tree button.jsoneditor-expanded {
background-position: 0 -72px;
}
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 *:focus {
outline: none;
}
div.jsoneditor-tree 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 {
color: #1A1A1A;
border: 1px solid #3883fa;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
padding: 0;
line-height: 100%;
}
div.jsoneditor-tree table.jsoneditor-tree {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
margin: 0;
}
div.jsoneditor-outer {
width: 100%;
height: 100%;
margin: -35px 0 0 0;
padding: 35px 0 0 0;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
textarea.jsoneditor-text,
.ace-jsoneditor {
min-height: 150px;
}
div.jsoneditor-tree {
width: 100%;
height: 100%;
position: relative;
overflow: auto;
}
textarea.jsoneditor-text {
width: 100%;
height: 100%;
margin: 0;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
outline-width: 0;
border: none;
background-color: white;
resize: none;
}
tr.jsoneditor-highlight,
tr.jsoneditor-selected {
background-color: #e6e6e6;
}
tr.jsoneditor-selected button.jsoneditor-dragarea,
tr.jsoneditor-selected button.jsoneditor-contextmenu {
visibility: hidden;
}
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
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 {
background-position: -72px -48px;
}
div.jsoneditor tr,
div.jsoneditor th,
div.jsoneditor td {
padding: 0;
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,
div.jsoneditor th,
div.jsoneditor textarea,
.jsoneditor-schema-error {
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;
font-size: 10pt;
color: #1A1A1A;
}
/* popover */
.jsoneditor-schema-error {
cursor: default;
display: inline-block;
/*font-family: arial, sans-serif;*/
height: 24px;
line-height: 24px;
position: relative;
text-align: center;
width: 24px;
}
div.jsoneditor-tree .jsoneditor-schema-error {
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url('img/jsoneditor-icons.svg') -168px -48px;
}
.jsoneditor-schema-error .jsoneditor-popover {
background-color: #4c4c4c;
border-radius: 3px;
box-shadow: 0 0 5px rgba(0,0,0,0.4);
color: #fff;
display: none;
padding: 7px 10px;
position: absolute;
width: 200px;
z-index: 4;
}
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-above {
bottom: 32px;
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:before {
border-bottom: 7px solid #4c4c4c;
top: -7px;
}
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-left:before {
border-left: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
right: -14px;
left: inherit;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
.jsoneditor-schema-error .jsoneditor-popover.jsoneditor-right:before {
border-right: 7px solid #4c4c4c;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: '';
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
.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;
}
@-webkit-keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@-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-text-errors {
width: 100%;
border-collapse: collapse;
background-color: #ffef8b;
border-top: 1px solid #ffd700;
}
.jsoneditor .jsoneditor-text-errors td {
padding: 3px 6px;
vertical-align: middle;
}
.jsoneditor-text-errors .jsoneditor-schema-error {
border: none;
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url('img/jsoneditor-icons.svg') -168px -48px;
}

View File

@ -1,110 +0,0 @@
div.jsoneditor-menu {
width: 100%;
height: 35px;
padding: 2px;
margin: 0;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: white;
background-color: #3883fa;
border-bottom: 1px solid #3883fa;
}
div.jsoneditor-menu > button,
div.jsoneditor-menu > div.jsoneditor-modes > button {
width: 26px;
height: 26px;
margin: 2px;
padding: 0;
border-radius: 2px;
border: 1px solid transparent;
background: transparent url('img/jsoneditor-icons.svg');
color: white;
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);
}
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);
}
div.jsoneditor-menu > button:disabled,
div.jsoneditor-menu > div.jsoneditor-modes > button:disabled {
opacity: 0.5;
}
div.jsoneditor-menu > button.jsoneditor-collapse-all {
background-position: 0 -96px;
}
div.jsoneditor-menu > button.jsoneditor-expand-all {
background-position: 0 -120px;
}
div.jsoneditor-menu > button.jsoneditor-undo {
background-position: -24px -96px;
}
div.jsoneditor-menu > button.jsoneditor-undo:disabled {
background-position: -24px -120px;
}
div.jsoneditor-menu > button.jsoneditor-redo {
background-position: -48px -96px;
}
div.jsoneditor-menu > button.jsoneditor-redo:disabled {
background-position: -48px -120px;
}
div.jsoneditor-menu > button.jsoneditor-compact {
background-position: -72px -96px;
}
div.jsoneditor-menu > button.jsoneditor-format {
background-position: -72px -120px;
}
div.jsoneditor-menu > div.jsoneditor-modes {
display: inline-block;
float: left;
}
div.jsoneditor-menu > div.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 {
margin-left: 10px;
}
div.jsoneditor-menu a {
font-family: arial, sans-serif;
font-size: 10pt;
color: white;
opacity: 0.8;
vertical-align: middle;
}
div.jsoneditor-menu a:hover {
opacity: 1;
}
div.jsoneditor-menu a.jsoneditor-poweredBy {
font-size: 8pt;
position: absolute;
right: 0;
top: 0;
padding: 10px;
}

View File

@ -1,25 +0,0 @@
/* reset styling (prevent conflicts with bootstrap, materialize.css, etc.) */
div.jsoneditor input {
height: auto;
border: inherit;
}
div.jsoneditor input:focus {
border: none !important;
box-shadow: none !important;
}
div.jsoneditor table {
border-collapse: collapse;
width: auto;
}
div.jsoneditor td,
div.jsoneditor th {
padding: 0;
display: table-cell;
text-align: left;
vertical-align: inherit;
border-radius: inherit;
}

View File

@ -1,77 +0,0 @@
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;
}
table.jsoneditor-search {
position: absolute;
right: 4px;
top: 4px;
border-collapse: collapse;
border-spacing: 0;
}
table.jsoneditor-search div.jsoneditor-frame {
border: 1px solid transparent;
background-color: white;
padding: 0 2px;
margin: 0;
}
table.jsoneditor-search div.jsoneditor-frame table {
border-collapse: collapse;
}
table.jsoneditor-search input {
width: 120px;
border: none;
outline: none;
margin: 1px;
line-height: 20px;
}
table.jsoneditor-search button {
width: 16px;
height: 24px;
padding: 0;
margin: 0;
border: none;
background: url('img/jsoneditor-icons.svg');
vertical-align: top;
}
table.jsoneditor-search button:hover {
background-color: transparent;
}
table.jsoneditor-search button.jsoneditor-refresh {
width: 18px;
background-position: -99px -73px;
}
table.jsoneditor-search button.jsoneditor-next {
cursor: pointer;
background-position: -124px -73px;
}
table.jsoneditor-search button.jsoneditor-next:hover {
background-position: -124px -49px;
}
table.jsoneditor-search button.jsoneditor-previous {
cursor: pointer;
background-position: -148px -73px;
margin-right: 2px;
}
table.jsoneditor-search button.jsoneditor-previous:hover {
background-position: -148px -49px;
}

View File

@ -1,41 +0,0 @@
# Which files do I need?
Ehhh, that's quite some files in this dist folder. Which files do I need?
## Full version
If you're not sure which version to use, use the full version.
Which files are needed when using the full version?
- jsoneditor.min.js
- jsoneditor.map (optional, for debugging purposes only)
- jsoneditor.min.css
- img/jsoneditor-icons.svg
## Minimalist version
The minimalist version has excluded the following libraries:
- `ace` (via `brace`), used for the code editor.
- `ajv`, used for JSON schema validation.
This reduces the the size of the minified and gzipped JavaScript file from
about 160 kB to about 40 kB.
When to use the minimalist version?
- If you don't need the mode "code" and don't need JSON schema validation.
- Or if you want to provide `ace` and/or `ajv` yourself via the configuration
options, for example when you already use Ace in other parts of your
web application too and don't want to bundle the library twice.
Which files are needed when using the minimalist version?
- jsoneditor-minimalist.min.js
- jsoneditor-minimalist.map (optional, for debugging purposes only)
- jsoneditor.min.css
- img/jsoneditor-icons.svg

View File

@ -1,457 +0,0 @@
'use strict';
var util = require('./util');
/**
* 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');
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.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);
button.appendChild(document.createTextNode(item.text));
var buttonSubmenu;
if (item.click) {
// submenu and a button with a click handler
button.className += ' jsoneditor-default';
var buttonExpand = document.createElement('button');
domItem.buttonExpand = buttonExpand;
buttonExpand.className = 'jsoneditor-expand';
buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>';
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 = '<div class="jsoneditor-icon"></div>' + 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.<HTMLElement>} 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} [contentWindow] The DIV with with the (scrollable) contents
*/
ContextMenu.prototype.show = function (anchor, contentWindow) {
this.hide();
// determine whether to display the menu below or above the anchor
var showBelow = true;
if (contentWindow) {
var anchorRect = anchor.getBoundingClientRect();
var contentRect = contentWindow.getBoundingClientRect();
if (anchorRect.bottom + this.maxHeight < contentRect.bottom) {
// fits below -> show below
}
else if (anchorRect.top - this.maxHeight > contentRect.top) {
// fits above -> show above
showBelow = false;
}
else {
// doesn't fit above nor below -> show below
}
}
// 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.bottom = '';
}
else {
// display the menu above the anchor
this.dom.menu.style.left = '0px';
this.dom.menu.style.top = '';
this.dom.menu.style.bottom = '0px';
}
// 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) {
// hide menu on click outside of the menu
var target = event.target;
if ((target != list) && !me._isChildOf(target, list)) {
me.hide();
event.stopPropagation();
event.preventDefault();
}
});
this.eventListeners.keydown = util.addEventListener(window, 'keydown', function (event) {
me._onKeyDown(event);
});
// 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 the menu from the DOM
if (this.dom.root.parentNode) {
this.dom.root.parentNode.removeChild(this.dom.root);
if (this.onClose) {
this.onClose();
}
}
// remove all event listeners
// all event listeners are supposed to be attached to document.
for (var name in this.eventListeners) {
if (this.eventListeners.hasOwnProperty(name)) {
var fn = this.eventListeners[name];
if (fn) {
util.removeEventListener(window, name, fn);
}
delete this.eventListeners[name];
}
}
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) {
ul.style.height = (ul.childNodes.length * 24) + '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();
}
};
/**
* Test if an element is a child of a parent element.
* @param {Element} child
* @param {Element} parent
* @return {boolean} isChild
*/
ContextMenu.prototype._isChildOf = function (child, parent) {
var e = child.parentNode;
while (e) {
if (e == parent) {
return true;
}
e = e.parentNode;
}
return false;
};
module.exports = ContextMenu;

View File

@ -1,86 +0,0 @@
'use strict';
/**
* The highlighter can highlight/unhighlight a node, and
* animate the visibility of a context menu.
* @constructor Highlighter
*/
function Highlighter () {
this.locked = false;
}
/**
* Hightlight given node and its childs
* @param {Node} node
*/
Highlighter.prototype.highlight = function (node) {
if (this.locked) {
return;
}
if (this.node != node) {
// unhighlight current node
if (this.node) {
this.node.setHighlight(false);
}
// highlight new node
this.node = node;
this.node.setHighlight(true);
}
// cancel any current timeout
this._cancelUnhighlight();
};
/**
* Unhighlight currently highlighted node.
* Will be done after a delay
*/
Highlighter.prototype.unhighlight = function () {
if (this.locked) {
return;
}
var me = this;
if (this.node) {
this._cancelUnhighlight();
// do the unhighlighting after a small delay, to prevent re-highlighting
// the same node when moving from the drag-icon to the contextmenu-icon
// or vice versa.
this.unhighlightTimer = setTimeout(function () {
me.node.setHighlight(false);
me.node = undefined;
me.unhighlightTimer = undefined;
}, 0);
}
};
/**
* Cancel an unhighlight action (if before the timeout of the unhighlight action)
* @private
*/
Highlighter.prototype._cancelUnhighlight = function () {
if (this.unhighlightTimer) {
clearTimeout(this.unhighlightTimer);
this.unhighlightTimer = undefined;
}
};
/**
* Lock highlighting or unhighlighting nodes.
* methods highlight and unhighlight do not work while locked.
*/
Highlighter.prototype.lock = function () {
this.locked = true;
};
/**
* Unlock highlighting or unhighlighting nodes
*/
Highlighter.prototype.unlock = function () {
this.locked = false;
};
module.exports = Highlighter;

View File

@ -1,267 +0,0 @@
'use strict';
var util = require('./util');
/**
* @constructor History
* Store action history, enables undo and redo
* @param {JSONEditor} editor
*/
function History (editor) {
this.editor = editor;
this.history = [];
this.index = -1;
this.clear();
// map with all supported actions
this.actions = {
'editField': {
'undo': function (params) {
params.node.updateField(params.oldValue);
},
'redo': function (params) {
params.node.updateField(params.newValue);
}
},
'editValue': {
'undo': function (params) {
params.node.updateValue(params.oldValue);
},
'redo': function (params) {
params.node.updateValue(params.newValue);
}
},
'changeType': {
'undo': function (params) {
params.node.changeType(params.oldType);
},
'redo': function (params) {
params.node.changeType(params.newType);
}
},
'appendNodes': {
'undo': function (params) {
params.nodes.forEach(function (node) {
params.parent.removeChild(node);
});
},
'redo': function (params) {
params.nodes.forEach(function (node) {
params.parent.appendChild(node);
});
}
},
'insertBeforeNodes': {
'undo': function (params) {
params.nodes.forEach(function (node) {
params.parent.removeChild(node);
});
},
'redo': function (params) {
params.nodes.forEach(function (node) {
params.parent.insertBefore(node, params.beforeNode);
});
}
},
'insertAfterNodes': {
'undo': function (params) {
params.nodes.forEach(function (node) {
params.parent.removeChild(node);
});
},
'redo': function (params) {
var afterNode = params.afterNode;
params.nodes.forEach(function (node) {
params.parent.insertAfter(params.node, afterNode);
afterNode = node;
});
}
},
'removeNodes': {
'undo': function (params) {
var parent = params.parent;
var beforeNode = parent.childs[params.index] || parent.append;
params.nodes.forEach(function (node) {
parent.insertBefore(node, beforeNode);
});
},
'redo': function (params) {
params.nodes.forEach(function (node) {
params.parent.removeChild(node);
});
}
},
'duplicateNodes': {
'undo': function (params) {
params.nodes.forEach(function (node) {
params.parent.removeChild(node);
});
},
'redo': function (params) {
var afterNode = params.afterNode;
params.nodes.forEach(function (node) {
params.parent.insertAfter(node, afterNode);
afterNode = node;
});
}
},
'moveNodes': {
'undo': function (params) {
params.nodes.forEach(function (node) {
params.oldBeforeNode.parent.moveBefore(node, params.oldBeforeNode);
});
},
'redo': function (params) {
params.nodes.forEach(function (node) {
params.newBeforeNode.parent.moveBefore(node, params.newBeforeNode);
});
}
},
'sort': {
'undo': function (params) {
var node = params.node;
node.hideChilds();
node.sort = params.oldSort;
node.childs = params.oldChilds;
node.showChilds();
},
'redo': function (params) {
var node = params.node;
node.hideChilds();
node.sort = params.newSort;
node.childs = params.newChilds;
node.showChilds();
}
}
// TODO: restore the original caret position and selection with each undo
// TODO: implement history for actions "expand", "collapse", "scroll", "setDocument"
};
}
/**
* The method onChange is executed when the History is changed, and can
* be overloaded.
*/
History.prototype.onChange = function () {};
/**
* Add a new action to the history
* @param {String} action The executed action. Available actions: "editField",
* "editValue", "changeType", "appendNode",
* "removeNode", "duplicateNode", "moveNode"
* @param {Object} params Object containing parameters describing the change.
* The parameters in params depend on the action (for
* example for "editValue" the Node, old value, and new
* value are provided). params contains all information
* needed to undo or redo the action.
*/
History.prototype.add = function (action, params) {
this.index++;
this.history[this.index] = {
'action': action,
'params': params,
'timestamp': new Date()
};
// remove redo actions which are invalid now
if (this.index < this.history.length - 1) {
this.history.splice(this.index + 1, this.history.length - this.index - 1);
}
// fire onchange event
this.onChange();
};
/**
* Clear history
*/
History.prototype.clear = function () {
this.history = [];
this.index = -1;
// fire onchange event
this.onChange();
};
/**
* Check if there is an action available for undo
* @return {Boolean} canUndo
*/
History.prototype.canUndo = function () {
return (this.index >= 0);
};
/**
* Check if there is an action available for redo
* @return {Boolean} canRedo
*/
History.prototype.canRedo = function () {
return (this.index < this.history.length - 1);
};
/**
* Undo the last action
*/
History.prototype.undo = function () {
if (this.canUndo()) {
var obj = this.history[this.index];
if (obj) {
var action = this.actions[obj.action];
if (action && action.undo) {
action.undo(obj.params);
if (obj.params.oldSelection) {
this.editor.setSelection(obj.params.oldSelection);
}
}
else {
console.error(new Error('unknown action "' + obj.action + '"'));
}
}
this.index--;
// fire onchange event
this.onChange();
}
};
/**
* Redo the last action
*/
History.prototype.redo = function () {
if (this.canRedo()) {
this.index++;
var obj = this.history[this.index];
if (obj) {
var action = this.actions[obj.action];
if (action && action.redo) {
action.redo(obj.params);
if (obj.params.newSelection) {
this.editor.setSelection(obj.params.newSelection);
}
}
else {
console.error(new Error('unknown action "' + obj.action + '"'));
}
}
// fire onchange event
this.onChange();
}
};
/**
* Destroy history
*/
History.prototype.destroy = function () {
this.editor = null;
this.history = [];
this.index = -1;
};
module.exports = History;

View File

@ -1,385 +0,0 @@
'use strict';
var Ajv;
try {
Ajv = require('ajv');
}
catch (err) {
// no problem... when we need Ajv we will throw a neat exception
}
var treemode = require('./treemode');
var textmode = require('./textmode');
var util = require('./util');
/**
* @constructor JSONEditor
* @param {Element} container Container element
* @param {Object} [options] Object with options. available options:
* {String} mode Editor mode. Available values:
* 'tree' (default), 'view',
* 'form', 'text', and 'code'.
* {function} onChange Callback method, triggered
* on change of contents
* {function} onError Callback method, triggered
* when an error occurs
* {Boolean} search Enable search box.
* True by default
* Only applicable for modes
* 'tree', 'view', and 'form'
* {Boolean} history Enable history (undo/redo).
* True by default
* Only applicable for modes
* 'tree', 'view', and 'form'
* {String} name Field name for the root node.
* Only applicable for modes
* 'tree', 'view', and 'form'
* {Number} indentation Number of indentation
* spaces. 4 by default.
* Only applicable for
* modes 'text' and 'code'
* {boolean} escapeUnicode If true, unicode
* characters are escaped.
* false by default.
* {boolean} sortObjectKeys If true, object keys are
* sorted before display.
* false by default.
* @param {Object | undefined} json JSON object
*/
function JSONEditor (container, options, json) {
if (!(this instanceof JSONEditor)) {
throw new Error('JSONEditor constructor called without "new".');
}
// check for unsupported browser (IE8 and older)
var ieVersion = util.getInternetExplorerVersion();
if (ieVersion != -1 && ieVersion < 9) {
throw new Error('Unsupported browser, IE9 or newer required. ' +
'Please install the newest version of your browser.');
}
if (options) {
// check for deprecated options
if (options.error) {
console.warn('Option "error" has been renamed to "onError"');
options.onError = options.error;
delete options.error;
}
if (options.change) {
console.warn('Option "change" has been renamed to "onChange"');
options.onChange = options.change;
delete options.change;
}
if (options.editable) {
console.warn('Option "editable" has been renamed to "onEditable"');
options.onEditable = options.editable;
delete options.editable;
}
// validate options
if (options) {
var VALID_OPTIONS = [
'ace', 'theme',
'ajv', 'schema',
'onChange', 'onEditable', 'onError', 'onModeChange',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation', 'sortObjectKeys'
];
Object.keys(options).forEach(function (option) {
if (VALID_OPTIONS.indexOf(option) === -1) {
console.warn('Unknown option "' + option + '". This option will be ignored');
}
});
}
}
if (arguments.length) {
this._create(container, options, json);
}
}
/**
* Configuration for all registered modes. Example:
* {
* tree: {
* mixin: TreeEditor,
* data: 'json'
* },
* text: {
* mixin: TextEditor,
* data: 'text'
* }
* }
*
* @type { Object.<String, {mixin: Object, data: String} > }
*/
JSONEditor.modes = {};
// debounce interval for JSON schema vaidation in milliseconds
JSONEditor.prototype.DEBOUNCE_INTERVAL = 150;
/**
* Create the JSONEditor
* @param {Element} container Container element
* @param {Object} [options] See description in constructor
* @param {Object | undefined} json JSON object
* @private
*/
JSONEditor.prototype._create = function (container, options, json) {
this.container = container;
this.options = options || {};
this.json = json || {};
var mode = this.options.mode || 'tree';
this.setMode(mode);
};
/**
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
JSONEditor.prototype.destroy = function () {};
/**
* Set JSON object in editor
* @param {Object | undefined} json JSON data
*/
JSONEditor.prototype.set = function (json) {
this.json = json;
};
/**
* Get JSON from the editor
* @returns {Object} json
*/
JSONEditor.prototype.get = function () {
return this.json;
};
/**
* Set string containing JSON for the editor
* @param {String | undefined} jsonText
*/
JSONEditor.prototype.setText = function (jsonText) {
this.json = util.parse(jsonText);
};
/**
* Get stringified JSON contents from the editor
* @returns {String} jsonText
*/
JSONEditor.prototype.getText = function () {
return JSON.stringify(this.json);
};
/**
* Set a field name for the root node.
* @param {String | undefined} name
*/
JSONEditor.prototype.setName = function (name) {
if (!this.options) {
this.options = {};
}
this.options.name = name;
};
/**
* Get the field name for the root node.
* @return {String | undefined} name
*/
JSONEditor.prototype.getName = function () {
return this.options && this.options.name;
};
/**
* Change the mode of the editor.
* JSONEditor will be extended with all methods needed for the chosen mode.
* @param {String} mode Available modes: 'tree' (default), 'view', 'form',
* 'text', and 'code'.
*/
JSONEditor.prototype.setMode = function (mode) {
var container = this.container;
var options = util.extend({}, this.options);
var oldMode = options.mode;
var data;
var name;
options.mode = mode;
var config = JSONEditor.modes[mode];
if (config) {
try {
var asText = (config.data == 'text');
name = this.getName();
data = this[asText ? 'getText' : 'get'](); // get text or json
this.destroy();
util.clear(this);
util.extend(this, config.mixin);
this.create(container, options);
this.setName(name);
this[asText ? 'setText' : 'set'](data); // set text or json
if (typeof config.load === 'function') {
try {
config.load.call(this);
}
catch (err) {
console.error(err);
}
}
if (typeof options.onModeChange === 'function' && mode !== oldMode) {
try {
options.onModeChange(mode, oldMode);
}
catch (err) {
console.error(err);
}
}
}
catch (err) {
this._onError(err);
}
}
else {
throw new Error('Unknown mode "' + options.mode + '"');
}
};
/**
* Get the current mode
* @return {string}
*/
JSONEditor.prototype.getMode = function () {
return this.options.mode;
};
/**
* Throw an error. If an error callback is configured in options.error, this
* callback will be invoked. Else, a regular error is thrown.
* @param {Error} err
* @private
*/
JSONEditor.prototype._onError = function(err) {
if (this.options && typeof this.options.onError === 'function') {
this.options.onError(err);
}
else {
throw err;
}
};
/**
* Set a JSON schema for validation of the JSON object.
* To remove the schema, call JSONEditor.setSchema(null)
* @param {Object | null} schema
*/
JSONEditor.prototype.setSchema = function (schema) {
// compile a JSON schema validator if a JSON schema is provided
if (schema) {
var ajv;
try {
// grab ajv from options if provided, else create a new instance
ajv = this.options.ajv || Ajv({ allErrors: true, verbose: true });
}
catch (err) {
console.warn('Failed to create an instance of Ajv, JSON Schema validation is not available. Please use a JSONEditor bundle including Ajv, or pass an instance of Ajv as via the configuration option `ajv`.');
}
if (ajv) {
this.validateSchema = ajv.compile(schema);
// add schema to the options, so that when switching to an other mode,
// the set schema is not lost
this.options.schema = schema;
// validate now
this.validate();
}
this.refresh(); // update DOM
}
else {
// remove current schema
this.validateSchema = null;
this.options.schema = null;
this.validate(); // to clear current error messages
this.refresh(); // update DOM
}
};
/**
* Validate current JSON object against the configured JSON schema
* Throws an exception when no JSON schema is configured
*/
JSONEditor.prototype.validate = function () {
// must be implemented by treemode and textmode
};
/**
* Refresh the rendered contents
*/
JSONEditor.prototype.refresh = function () {
// can be implemented by treemode and textmode
};
/**
* Register a plugin with one ore multiple modes for the JSON Editor.
*
* A mode is described as an object with properties:
*
* - `mode: String` The name of the mode.
* - `mixin: Object` An object containing the mixin functions which
* will be added to the JSONEditor. Must contain functions
* create, get, getText, set, and setText. May have
* additional functions.
* When the JSONEditor switches to a mixin, all mixin
* functions are added to the JSONEditor, and then
* the function `create(container, options)` is executed.
* - `data: 'text' | 'json'` The type of data that will be used to load the mixin.
* - `[load: function]` An optional function called after the mixin
* has been loaded.
*
* @param {Object | Array} mode A mode object or an array with multiple mode objects.
*/
JSONEditor.registerMode = function (mode) {
var i, prop;
if (util.isArray(mode)) {
// multiple modes
for (i = 0; i < mode.length; i++) {
JSONEditor.registerMode(mode[i]);
}
}
else {
// validate the new mode
if (!('mode' in mode)) throw new Error('Property "mode" missing');
if (!('mixin' in mode)) throw new Error('Property "mixin" missing');
if (!('data' in mode)) throw new Error('Property "data" missing');
var name = mode.mode;
if (name in JSONEditor.modes) {
throw new Error('Mode "' + name + '" already registered');
}
// validate the mixin
if (typeof mode.mixin.create !== 'function') {
throw new Error('Required function "create" missing on mixin');
}
var reserved = ['setMode', 'registerMode', 'modes'];
for (i = 0; i < reserved.length; i++) {
prop = reserved[i];
if (prop in mode.mixin) {
throw new Error('Reserved property "' + prop + '" not allowed in mixin');
}
}
JSONEditor.modes[name] = mode;
}
};
// register tree and text modes
JSONEditor.registerMode(treemode);
JSONEditor.registerMode(textmode);
module.exports = JSONEditor;

View File

@ -1,114 +0,0 @@
'use strict';
var ContextMenu = require('./ContextMenu');
/**
* Create a select box to be used in the editor menu's, which allows to switch mode
* @param {HTMLElement} container
* @param {String[]} modes Available modes: 'code', 'form', 'text', 'tree', 'view'
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view'
* @param {function(mode: string)} onSwitch Callback invoked on switch
* @constructor
*/
function ModeSwitcher(container, modes, current, onSwitch) {
// available modes
var availableModes = {
code: {
'text': 'Code',
'title': 'Switch to code highlighter',
'click': function () {
onSwitch('code')
}
},
form: {
'text': 'Form',
'title': 'Switch to form editor',
'click': function () {
onSwitch('form');
}
},
text: {
'text': 'Text',
'title': 'Switch to plain text editor',
'click': function () {
onSwitch('text');
}
},
tree: {
'text': 'Tree',
'title': 'Switch to tree editor',
'click': function () {
onSwitch('tree');
}
},
view: {
'text': 'View',
'title': 'Switch to tree view',
'click': function () {
onSwitch('view');
}
}
};
// list the selected modes
var items = [];
for (var i = 0; i < modes.length; i++) {
var mode = modes[i];
var item = availableModes[mode];
if (!item) {
throw new Error('Unknown mode "' + mode + '"');
}
item.className = 'jsoneditor-type-modes' + ((current == mode) ? ' jsoneditor-selected' : '');
items.push(item);
}
// retrieve the title of current mode
var currentMode = availableModes[current];
if (!currentMode) {
throw new Error('Unknown mode "' + current + '"');
}
var currentTitle = currentMode.text;
// create the html element
var box = document.createElement('button');
box.className = 'jsoneditor-modes jsoneditor-separator';
box.innerHTML = currentTitle + ' &#x25BE;';
box.title = 'Switch editor mode';
box.onclick = function () {
var menu = new ContextMenu(items);
menu.show(box);
};
var frame = document.createElement('div');
frame.className = 'jsoneditor-modes';
frame.style.position = 'relative';
frame.appendChild(box);
container.appendChild(frame);
this.dom = {
container: container,
box: box,
frame: frame
};
}
/**
* Set focus to switcher
*/
ModeSwitcher.prototype.focus = function () {
this.dom.box.focus();
};
/**
* Destroy the ModeSwitcher, remove from DOM
*/
ModeSwitcher.prototype.destroy = function () {
if (this.dom && this.dom.frame && this.dom.frame.parentNode) {
this.dom.frame.parentNode.removeChild(this.dom.frame);
}
this.dom = null;
};
module.exports = ModeSwitcher;

File diff suppressed because it is too large Load Diff

View File

@ -1,312 +0,0 @@
'use strict';
/**
* @constructor SearchBox
* Create a search box in given HTML container
* @param {JSONEditor} editor The JSON Editor to attach to
* @param {Element} container HTML container element of where to
* create the search box
*/
function SearchBox (editor, container) {
var searchBox = this;
this.editor = editor;
this.timeout = undefined;
this.delay = 200; // ms
this.lastText = undefined;
this.dom = {};
this.dom.container = container;
var table = document.createElement('table');
this.dom.table = table;
table.className = 'jsoneditor-search';
container.appendChild(table);
var tbody = document.createElement('tbody');
this.dom.tbody = tbody;
table.appendChild(tbody);
var tr = document.createElement('tr');
tbody.appendChild(tr);
var td = document.createElement('td');
tr.appendChild(td);
var results = document.createElement('div');
this.dom.results = results;
results.className = 'jsoneditor-results';
td.appendChild(results);
td = document.createElement('td');
tr.appendChild(td);
var divInput = document.createElement('div');
this.dom.input = divInput;
divInput.className = 'jsoneditor-frame';
divInput.title = 'Search fields and values';
td.appendChild(divInput);
// table to contain the text input and search button
var tableInput = document.createElement('table');
divInput.appendChild(tableInput);
var tbodySearch = document.createElement('tbody');
tableInput.appendChild(tbodySearch);
tr = document.createElement('tr');
tbodySearch.appendChild(tr);
var refreshSearch = document.createElement('button');
refreshSearch.className = 'jsoneditor-refresh';
td = document.createElement('td');
td.appendChild(refreshSearch);
tr.appendChild(td);
var search = document.createElement('input');
this.dom.search = search;
search.oninput = function (event) {
searchBox._onDelayedSearch(event);
};
search.onchange = function (event) { // For IE 9
searchBox._onSearch();
};
search.onkeydown = function (event) {
searchBox._onKeyDown(event);
};
search.onkeyup = function (event) {
searchBox._onKeyUp(event);
};
refreshSearch.onclick = function (event) {
search.select();
};
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
td = document.createElement('td');
td.appendChild(search);
tr.appendChild(td);
var searchNext = document.createElement('button');
searchNext.title = 'Next result (Enter)';
searchNext.className = 'jsoneditor-next';
searchNext.onclick = function () {
searchBox.next();
};
td = document.createElement('td');
td.appendChild(searchNext);
tr.appendChild(td);
var searchPrevious = document.createElement('button');
searchPrevious.title = 'Previous result (Shift+Enter)';
searchPrevious.className = 'jsoneditor-previous';
searchPrevious.onclick = function () {
searchBox.previous();
};
td = document.createElement('td');
td.appendChild(searchPrevious);
tr.appendChild(td);
}
/**
* Go to the next search result
* @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default.
*/
SearchBox.prototype.next = function(focus) {
if (this.results != undefined) {
var index = (this.resultIndex != undefined) ? this.resultIndex + 1 : 0;
if (index > this.results.length - 1) {
index = 0;
}
this._setActiveResult(index, focus);
}
};
/**
* Go to the prevous search result
* @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default.
*/
SearchBox.prototype.previous = function(focus) {
if (this.results != undefined) {
var max = this.results.length - 1;
var index = (this.resultIndex != undefined) ? this.resultIndex - 1 : max;
if (index < 0) {
index = max;
}
this._setActiveResult(index, focus);
}
};
/**
* Set new value for the current active result
* @param {Number} index
* @param {boolean} [focus] If true, focus will be set to the next result.
* focus is false by default.
* @private
*/
SearchBox.prototype._setActiveResult = function(index, focus) {
// de-activate current active result
if (this.activeResult) {
var prevNode = this.activeResult.node;
var prevElem = this.activeResult.elem;
if (prevElem == 'field') {
delete prevNode.searchFieldActive;
}
else {
delete prevNode.searchValueActive;
}
prevNode.updateDom();
}
if (!this.results || !this.results[index]) {
// out of range, set to undefined
this.resultIndex = undefined;
this.activeResult = undefined;
return;
}
this.resultIndex = index;
// set new node active
var node = this.results[this.resultIndex].node;
var elem = this.results[this.resultIndex].elem;
if (elem == 'field') {
node.searchFieldActive = true;
}
else {
node.searchValueActive = true;
}
this.activeResult = this.results[this.resultIndex];
node.updateDom();
// TODO: not so nice that the focus is only set after the animation is finished
node.scrollTo(function () {
if (focus) {
node.focus(elem);
}
});
};
/**
* Cancel any running onDelayedSearch.
* @private
*/
SearchBox.prototype._clearDelay = function() {
if (this.timeout != undefined) {
clearTimeout(this.timeout);
delete this.timeout;
}
};
/**
* Start a timer to execute a search after a short delay.
* Used for reducing the number of searches while typing.
* @param {Event} event
* @private
*/
SearchBox.prototype._onDelayedSearch = function (event) {
// execute the search after a short delay (reduces the number of
// search actions while typing in the search text box)
this._clearDelay();
var searchBox = this;
this.timeout = setTimeout(function (event) {
searchBox._onSearch();
},
this.delay);
};
/**
* Handle onSearch event
* @param {boolean} [forceSearch] If true, search will be executed again even
* when the search text is not changed.
* Default is false.
* @private
*/
SearchBox.prototype._onSearch = function (forceSearch) {
this._clearDelay();
var value = this.dom.search.value;
var text = (value.length > 0) ? value : undefined;
if (text != this.lastText || forceSearch) {
// only search again when changed
this.lastText = text;
this.results = this.editor.search(text);
this._setActiveResult(undefined);
// display search results
if (text != undefined) {
var resultCount = this.results.length;
switch (resultCount) {
case 0: this.dom.results.innerHTML = 'no&nbsp;results'; break;
case 1: this.dom.results.innerHTML = '1&nbsp;result'; break;
default: this.dom.results.innerHTML = resultCount + '&nbsp;results'; break;
}
}
else {
this.dom.results.innerHTML = '';
}
}
};
/**
* Handle onKeyDown event in the input box
* @param {Event} event
* @private
*/
SearchBox.prototype._onKeyDown = function (event) {
var keynum = event.which;
if (keynum == 27) { // ESC
this.dom.search.value = ''; // clear search
this._onSearch();
event.preventDefault();
event.stopPropagation();
}
else if (keynum == 13) { // Enter
if (event.ctrlKey) {
// force to search again
this._onSearch(true);
}
else if (event.shiftKey) {
// move to the previous search result
this.previous();
}
else {
// move to the next search result
this.next();
}
event.preventDefault();
event.stopPropagation();
}
};
/**
* Handle onKeyUp event in the input box
* @param {Event} event
* @private
*/
SearchBox.prototype._onKeyUp = function (event) {
var keynum = event.keyCode;
if (keynum != 27 && keynum != 13) { // !show and !Enter
this._onDelayedSearch(event); // For IE 9
}
};
/**
* Clear the search results
*/
SearchBox.prototype.clear = function () {
this.dom.search.value = '';
this._onSearch();
};
/**
* Destroy the search box
*/
SearchBox.prototype.destroy = function () {
this.editor = null;
this.dom.container.removeChild(this.dom.table);
this.dom = null;
this.results = null;
this.activeResult = null;
this._clearDelay();
};
module.exports = SearchBox;

View File

@ -1,9 +0,0 @@
// load brace
var ace = require('brace');
// load required ace modules
require('brace/mode/json');
require('brace/ext/searchbox');
require('./theme-jsoneditor');
module.exports = ace;

View File

@ -1,144 +0,0 @@
/* ***** BEGIN LICENSE BLOCK *****
* Distributed under the BSD license:
*
* Copyright (c) 2010, Ajax.org B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Ajax.org B.V. nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* ***** END LICENSE BLOCK ***** */
ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function(acequire, exports, module) {
exports.isDark = false;
exports.cssClass = "ace-jsoneditor";
exports.cssText = ".ace-jsoneditor .ace_gutter {\
background: #ebebeb;\
color: #333\
}\
\
.ace-jsoneditor.ace_editor {\
font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;\
line-height: 1.3;\
}\
.ace-jsoneditor .ace_print-margin {\
width: 1px;\
background: #e8e8e8\
}\
.ace-jsoneditor .ace_scroller {\
background-color: #FFFFFF\
}\
.ace-jsoneditor .ace_text-layer {\
color: gray\
}\
.ace-jsoneditor .ace_variable {\
color: #1a1a1a\
}\
.ace-jsoneditor .ace_cursor {\
border-left: 2px solid #000000\
}\
.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {\
border-left: 0px;\
border-bottom: 1px solid #000000\
}\
.ace-jsoneditor .ace_marker-layer .ace_selection {\
background: lightgray\
}\
.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {\
box-shadow: 0 0 3px 0px #FFFFFF;\
border-radius: 2px\
}\
.ace-jsoneditor .ace_marker-layer .ace_step {\
background: rgb(255, 255, 0)\
}\
.ace-jsoneditor .ace_marker-layer .ace_bracket {\
margin: -1px 0 0 -1px;\
border: 1px solid #BFBFBF\
}\
.ace-jsoneditor .ace_marker-layer .ace_active-line {\
background: #FFFBD1\
}\
.ace-jsoneditor .ace_gutter-active-line {\
background-color : #dcdcdc\
}\
.ace-jsoneditor .ace_marker-layer .ace_selected-word {\
border: 1px solid lightgray\
}\
.ace-jsoneditor .ace_invisible {\
color: #BFBFBF\
}\
.ace-jsoneditor .ace_keyword,\
.ace-jsoneditor .ace_meta,\
.ace-jsoneditor .ace_support.ace_constant.ace_property-value {\
color: #AF956F\
}\
.ace-jsoneditor .ace_keyword.ace_operator {\
color: #484848\
}\
.ace-jsoneditor .ace_keyword.ace_other.ace_unit {\
color: #96DC5F\
}\
.ace-jsoneditor .ace_constant.ace_language {\
color: darkorange\
}\
.ace-jsoneditor .ace_constant.ace_numeric {\
color: red\
}\
.ace-jsoneditor .ace_constant.ace_character.ace_entity {\
color: #BF78CC\
}\
.ace-jsoneditor .ace_invalid {\
color: #FFFFFF;\
background-color: #FF002A;\
}\
.ace-jsoneditor .ace_fold {\
background-color: #AF956F;\
border-color: #000000\
}\
.ace-jsoneditor .ace_storage,\
.ace-jsoneditor .ace_support.ace_class,\
.ace-jsoneditor .ace_support.ace_function,\
.ace-jsoneditor .ace_support.ace_other,\
.ace-jsoneditor .ace_support.ace_type {\
color: #C52727\
}\
.ace-jsoneditor .ace_string {\
color: green\
}\
.ace-jsoneditor .ace_comment {\
color: #BCC8BA\
}\
.ace-jsoneditor .ace_entity.ace_name.ace_tag,\
.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {\
color: #606060\
}\
.ace-jsoneditor .ace_markup.ace_underline {\
text-decoration: underline\
}\
.ace-jsoneditor .ace_indent-guide {\
background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y\
}";
var dom = acequire("../lib/dom");
dom.importCssString(exports.cssText, exports.cssClass);
});

View File

@ -1,228 +0,0 @@
'use strict';
var util = require('./util');
var ContextMenu = require('./ContextMenu');
/**
* A factory function to create an AppendNode, which depends on a Node
* @param {Node} Node
*/
function appendNodeFactory(Node) {
/**
* @constructor AppendNode
* @extends Node
* @param {TreeEditor} editor
* Create a new AppendNode. This is a special node which is created at the
* end of the list with childs for an object or array
*/
function AppendNode (editor) {
/** @type {TreeEditor} */
this.editor = editor;
this.dom = {};
}
AppendNode.prototype = new Node();
/**
* Return a table row with an append button.
* @return {Element} dom TR element
*/
AppendNode.prototype.getDom = function () {
// TODO: implement a new solution for the append node
var dom = this.dom;
if (dom.tr) {
return dom.tr;
}
this._updateEditability();
// a row for the append button
var trAppend = document.createElement('tr');
trAppend.node = this;
dom.tr = trAppend;
// TODO: consistent naming
if (this.editor.options.mode === 'tree') {
// a cell for the dragarea column
dom.tdDrag = document.createElement('td');
// create context menu
var tdMenu = document.createElement('td');
dom.tdMenu = tdMenu;
var menu = document.createElement('button');
menu.className = 'jsoneditor-contextmenu';
menu.title = 'Click to open the actions menu (Ctrl+M)';
dom.menu = menu;
tdMenu.appendChild(dom.menu);
}
// a cell for the contents (showing text 'empty')
var tdAppend = document.createElement('td');
var domText = document.createElement('div');
domText.innerHTML = '(empty)';
domText.className = 'jsoneditor-readonly';
tdAppend.appendChild(domText);
dom.td = tdAppend;
dom.text = domText;
this.updateDom();
return trAppend;
};
/**
* Update the HTML dom of the Node
*/
AppendNode.prototype.updateDom = function () {
var dom = this.dom;
var tdAppend = dom.td;
if (tdAppend) {
tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px';
// TODO: not so nice hard coded offset
}
var domText = dom.text;
if (domText) {
domText.innerHTML = '(empty ' + this.parent.type + ')';
}
// attach or detach the contents of the append node:
// hide when the parent has childs, show when the parent has no childs
var trAppend = dom.tr;
if (!this.isVisible()) {
if (dom.tr.firstChild) {
if (dom.tdDrag) {
trAppend.removeChild(dom.tdDrag);
}
if (dom.tdMenu) {
trAppend.removeChild(dom.tdMenu);
}
trAppend.removeChild(tdAppend);
}
}
else {
if (!dom.tr.firstChild) {
if (dom.tdDrag) {
trAppend.appendChild(dom.tdDrag);
}
if (dom.tdMenu) {
trAppend.appendChild(dom.tdMenu);
}
trAppend.appendChild(tdAppend);
}
}
};
/**
* Check whether the AppendNode is currently visible.
* the AppendNode is visible when its parent has no childs (i.e. is empty).
* @return {boolean} isVisible
*/
AppendNode.prototype.isVisible = function () {
return (this.parent.childs.length == 0);
};
/**
* Show a contextmenu for this node
* @param {HTMLElement} anchor The element to attach the menu to.
* @param {function} [onClose] Callback method called when the context menu
* is being closed.
*/
AppendNode.prototype.showContextMenu = function (anchor, onClose) {
var node = this;
var titles = Node.TYPE_TITLES;
var items = [
// create append button
{
'text': 'Append',
'title': 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)',
'submenuTitle': 'Select the type of the field to be appended',
'className': 'jsoneditor-insert',
'click': function () {
node._onAppend('', '', 'auto');
},
'submenu': [
{
'text': 'Auto',
'className': 'jsoneditor-type-auto',
'title': titles.auto,
'click': function () {
node._onAppend('', '', 'auto');
}
},
{
'text': 'Array',
'className': 'jsoneditor-type-array',
'title': titles.array,
'click': function () {
node._onAppend('', []);
}
},
{
'text': 'Object',
'className': 'jsoneditor-type-object',
'title': titles.object,
'click': function () {
node._onAppend('', {});
}
},
{
'text': 'String',
'className': 'jsoneditor-type-string',
'title': titles.string,
'click': function () {
node._onAppend('', '', 'string');
}
}
]
}
];
var menu = new ContextMenu(items, {close: onClose});
menu.show(anchor, this.editor.content);
};
/**
* Handle an event. The event is catched centrally by the editor
* @param {Event} event
*/
AppendNode.prototype.onEvent = function (event) {
var type = event.type;
var target = event.target || event.srcElement;
var dom = this.dom;
// highlight the append nodes parent
var menu = dom.menu;
if (target == menu) {
if (type == 'mouseover') {
this.editor.highlighter.highlight(this.parent);
}
else if (type == 'mouseout') {
this.editor.highlighter.unhighlight();
}
}
// context menu events
if (type == 'click' && target == dom.menu) {
var highlighter = this.editor.highlighter;
highlighter.highlight(this.parent);
highlighter.lock();
util.addClassName(dom.menu, 'jsoneditor-selected');
this.showContextMenu(dom.menu, function () {
util.removeClassName(dom.menu, 'jsoneditor-selected');
highlighter.unlock();
highlighter.unhighlight();
});
}
if (type == 'keydown') {
this.onKeyDown(event);
}
};
return AppendNode;
}
module.exports = appendNodeFactory;

View File

@ -1,15 +0,0 @@
The file jsonlint.js is copied from the following project:
https://github.com/josdejong/jsonlint at 85a19d7
which is a fork of the (currently not maintained) project:
https://github.com/zaach/jsonlint
The forked project contains some fixes to allow the file to be bundled with
browserify. The file is copied in this project to prevent issues with linking
to a github project from package.json, which is for example not supported
by jspm.
As soon as zaach/jsonlint is being maintained again we can push the fix
to the original library and use it as dependency again.

View File

@ -1,418 +0,0 @@
/* Jison generated parser */
var jsonlint = (function(){
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
var $0 = $$.length - 1;
switch (yystate) {
case 1: // replace escaped characters with actual character
this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
.replace(/\\v/g,'\v')
.replace(/\\f/g,'\f')
.replace(/\\b/g,'\b');
break;
case 2:this.$ = Number(yytext);
break;
case 3:this.$ = null;
break;
case 4:this.$ = true;
break;
case 5:this.$ = false;
break;
case 6:return this.$ = $$[$0-1];
break;
case 13:this.$ = {};
break;
case 14:this.$ = $$[$0-1];
break;
case 15:this.$ = [$$[$0-2], $$[$0]];
break;
case 16:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
break;
case 17:this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
break;
case 18:this.$ = [];
break;
case 19:this.$ = $$[$0-1];
break;
case 20:this.$ = [$$[$0]];
break;
case 21:this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
break;
}
},
table: [{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],12:1,13:2,15:7,16:8,17:[1,14],23:[1,15]},{1:[3]},{14:[1,16]},{14:[2,7],18:[2,7],22:[2,7],24:[2,7]},{14:[2,8],18:[2,8],22:[2,8],24:[2,8]},{14:[2,9],18:[2,9],22:[2,9],24:[2,9]},{14:[2,10],18:[2,10],22:[2,10],24:[2,10]},{14:[2,11],18:[2,11],22:[2,11],24:[2,11]},{14:[2,12],18:[2,12],22:[2,12],24:[2,12]},{14:[2,3],18:[2,3],22:[2,3],24:[2,3]},{14:[2,4],18:[2,4],22:[2,4],24:[2,4]},{14:[2,5],18:[2,5],22:[2,5],24:[2,5]},{14:[2,1],18:[2,1],21:[2,1],22:[2,1],24:[2,1]},{14:[2,2],18:[2,2],22:[2,2],24:[2,2]},{3:20,4:[1,12],18:[1,17],19:18,20:19},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:23,15:7,16:8,17:[1,14],23:[1,15],24:[1,21],25:22},{1:[2,6]},{14:[2,13],18:[2,13],22:[2,13],24:[2,13]},{18:[1,24],22:[1,25]},{18:[2,16],22:[2,16]},{21:[1,26]},{14:[2,18],18:[2,18],22:[2,18],24:[2,18]},{22:[1,28],24:[1,27]},{22:[2,20],24:[2,20]},{14:[2,14],18:[2,14],22:[2,14],24:[2,14]},{3:20,4:[1,12],20:29},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:30,15:7,16:8,17:[1,14],23:[1,15]},{14:[2,19],18:[2,19],22:[2,19],24:[2,19]},{3:5,4:[1,12],5:6,6:[1,13],7:3,8:[1,9],9:4,10:[1,10],11:[1,11],13:31,15:7,16:8,17:[1,14],23:[1,15]},{18:[2,17],22:[2,17]},{18:[2,15],22:[2,15]},{22:[2,21],24:[2,21]}],
defaultActions: {16:[2,6]},
parseError: function parseError(str, hash) {
throw new Error(str);
},
parse: function parse(input) {
var self = this,
stack = [0],
vstack = [null], // semantic value stack
lstack = [], // location stack
table = this.table,
yytext = '',
yylineno = 0,
yyleng = 0,
recovering = 0,
TERROR = 2,
EOF = 1;
//this.reductionCount = this.shiftCount = 0;
this.lexer.setInput(input);
this.lexer.yy = this.yy;
this.yy.lexer = this.lexer;
if (typeof this.lexer.yylloc == 'undefined')
this.lexer.yylloc = {};
var yyloc = this.lexer.yylloc;
lstack.push(yyloc);
if (typeof this.yy.parseError === 'function')
this.parseError = this.yy.parseError;
function popStack (n) {
stack.length = stack.length - 2*n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
function lex() {
var token;
token = self.lexer.lex() || 1; // $end = 1
// if token isn't its numeric value, convert
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
while (true) {
// retreive state number from top of stack
state = stack[stack.length-1];
// use default actions if available
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol == null)
symbol = lex();
// read action for current state and first input
action = table[state] && table[state][symbol];
}
// handle parse error
_handle_error:
if (typeof action === 'undefined' || !action.length || !action[0]) {
if (!recovering) {
// Report error
expected = [];
for (p in table[state]) if (this.terminals_[p] && p > 2) {
expected.push("'"+this.terminals_[p]+"'");
}
var errStr = '';
if (this.lexer.showPosition) {
errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+expected.join(', ') + ", got '" + this.terminals_[symbol]+ "'";
} else {
errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
(symbol == 1 /*EOF*/ ? "end of input" :
("'"+(this.terminals_[symbol] || symbol)+"'"));
}
this.parseError(errStr,
{text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
}
// just recovered from another error
if (recovering == 3) {
if (symbol == EOF) {
throw new Error(errStr || 'Parsing halted.');
}
// discard current lookahead and grab another
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
symbol = lex();
}
// try to recover from error
while (1) {
// check for error recovery rule in this state
if ((TERROR.toString()) in table[state]) {
break;
}
if (state == 0) {
throw new Error(errStr || 'Parsing halted.');
}
popStack(1);
state = stack[stack.length-1];
}
preErrorSymbol = symbol; // save the lookahead token
symbol = TERROR; // insert generic error symbol as new lookahead
state = stack[stack.length-1];
action = table[state] && table[state][TERROR];
recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
}
// this shouldn't happen, unless resolve defaults are off
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
}
switch (action[0]) {
case 1: // shift
//this.shiftCount++;
stack.push(symbol);
vstack.push(this.lexer.yytext);
lstack.push(this.lexer.yylloc);
stack.push(action[1]); // push state
symbol = null;
if (!preErrorSymbol) { // normal execution/no error
yyleng = this.lexer.yyleng;
yytext = this.lexer.yytext;
yylineno = this.lexer.yylineno;
yyloc = this.lexer.yylloc;
if (recovering > 0)
recovering--;
} else { // error just occurred, resume old lookahead f/ before error
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2: // reduce
//this.reductionCount++;
len = this.productions_[action[1]][1];
// perform semantic action
yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
// default location, uses first token for firsts, last for lasts
yyval._$ = {
first_line: lstack[lstack.length-(len||1)].first_line,
last_line: lstack[lstack.length-1].last_line,
first_column: lstack[lstack.length-(len||1)].first_column,
last_column: lstack[lstack.length-1].last_column
};
r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
if (typeof r !== 'undefined') {
return r;
}
// pop off stack
if (len) {
stack = stack.slice(0,-1*len*2);
vstack = vstack.slice(0, -1*len);
lstack = lstack.slice(0, -1*len);
}
stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
vstack.push(yyval.$);
lstack.push(yyval._$);
// goto new state = table[STATE][NONTERMINAL]
newState = table[stack[stack.length-2]][stack[stack.length-1]];
stack.push(newState);
break;
case 3: // accept
return true;
}
}
return true;
}};
/* Jison generated lexer */
var lexer = (function(){
var lexer = ({EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parseError) {
this.yy.parseError(str, hash);
} else {
throw new Error(str);
}
},
setInput:function (input) {
this._input = input;
this._more = this._less = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
return this;
},
input:function () {
var ch = this._input[0];
this.yytext+=ch;
this.yyleng++;
this.match+=ch;
this.matched+=ch;
var lines = ch.match(/\n/);
if (lines) this.yylineno++;
this._input = this._input.slice(1);
return ch;
},
unput:function (ch) {
this._input = ch + this._input;
return this;
},
more:function () {
this._more = true;
return this;
},
less:function (n) {
this._input = this.match.slice(n) + this._input;
},
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
},
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c+"^";
},
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) this.done = true;
var token,
match,
tempMatch,
index,
col,
lines;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i=0;i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (!this.options.flex) break;
}
}
if (match) {
lines = match[0].match(/\n.*/g);
if (lines) this.yylineno += lines.length;
this.yylloc = {first_line: this.yylloc.last_line,
last_line: this.yylineno+1,
first_column: this.yylloc.last_column,
last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
this.yytext += match[0];
this.match += match[0];
this.yyleng = this.yytext.length;
this._more = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]);
if (this.done && this._input) this.done = false;
if (token) return token;
else return;
}
if (this._input === "") {
return this.EOF;
} else {
this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
{text: "", token: null, line: this.yylineno});
}
},
lex:function lex() {
var r = this.next();
if (typeof r !== 'undefined') {
return r;
} else {
return this.lex();
}
},
begin:function begin(condition) {
this.conditionStack.push(condition);
},
popState:function popState() {
return this.conditionStack.pop();
},
_currentRules:function _currentRules() {
return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
},
topState:function () {
return this.conditionStack[this.conditionStack.length-2];
},
pushState:function begin(condition) {
this.begin(condition);
}});
lexer.options = {};
lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 1:return 6
break;
case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
break;
case 3:return 17
break;
case 4:return 18
break;
case 5:return 23
break;
case 6:return 24
break;
case 7:return 22
break;
case 8:return 21
break;
case 9:return 10
break;
case 10:return 11
break;
case 11:return 8
break;
case 12:return 14
break;
case 13:return 'INVALID'
break;
}
};
lexer.rules = [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/];
lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}};
;
return lexer;})()
parser.lexer = lexer;
return parser;
})();
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = jsonlint;
exports.parse = jsonlint.parse.bind(jsonlint);
}

View File

@ -1,29 +0,0 @@
/*!
* jsoneditor.js
*
* @brief
* JSONEditor 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.
*
* Supported browsers: Chrome, Firefox, Safari, Opera, Internet Explorer 8+
*
* @license
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* Copyright (c) 2011-2016 Jos de Jong, http://jsoneditoronline.org
*
* @author Jos de Jong, <wjosdejong@gmail.com>
* @version @@version
* @date @@date
*/

Some files were not shown because too many files have changed in this diff Show More