Initial commit with a new Svelte setup

This commit is contained in:
josdejong 2020-04-25 22:32:20 +02:00
parent cfa5575884
commit 1bc28041d3
92 changed files with 1035 additions and 31597 deletions

View File

@ -1,5 +0,0 @@
{
"presets": [
["@babel/preset-env"]
]
}

13
.gitignore vendored
View File

@ -1,10 +1,5 @@
/node_modules/
/public/build/
.DS_Store
.idea .idea
*.iml
.vscode
build
dist
downloads
node_modules
*.zip
npm-debug.log
/.vs

3
.mocharc.json Normal file
View File

@ -0,0 +1,3 @@
{
"test": "./src/**/*.test.js"
}

View File

@ -1,13 +0,0 @@
bower.json
CONTRIBUTING.md
downloads
misc
node_modules
test
tools
.idea
component.json
.npmignore
.gitignore
*.zip
npm-debug.log

View File

@ -1,5 +0,0 @@
language: node_js
node_js:
- "lts/*"
script: npm test && npm run lint

View File

@ -1,11 +0,0 @@
{
"groups": {
"default": {
"packages": [
"examples/react_advanced_demo/package.json",
"examples/react_demo/package.json",
"package.json"
]
}
}
}

View File

@ -1,238 +0,0 @@
const fs = require('fs')
const path = require('path')
const gulp = require('gulp')
const log = require('fancy-log')
const format = require('date-format')
const concatCss = require('gulp-concat-css')
const minifyCSS = require('gulp-clean-css')
const sass = require('gulp-sass')
const mkdirp = require('mkdirp')
const webpack = require('webpack')
const uglify = require('uglify-js')
const btoa = require('btoa')
const NAME = 'jsoneditor'
const NAME_MINIMALIST = 'jsoneditor-minimalist'
const ENTRY = './src/js/JSONEditor.js'
const HEADER = './src/js/header.js'
const IMAGE = './src/scss/img/jsoneditor-icons.svg'
const DOCS = './src/docs/*'
const DIST = path.join(__dirname, 'dist')
// generate banner with today's date and correct version
function createBanner () {
const today = format.asString('yyyy-MM-dd', new Date()) // 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({
banner: createBanner(),
entryOnly: true,
raw: true
})
const webpackConfigModule = {
rules: [
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
}
]
}
// create a single instance of the compiler to allow caching
const compiler = webpack({
entry: ENTRY,
output: {
library: 'JSONEditor',
libraryTarget: 'umd',
path: DIST,
filename: NAME + '.js'
},
plugins: [bannerPlugin],
optimization: {
// We no not want to minimize our code.
minimize: false
},
module: webpackConfigModule,
resolve: {
extensions: ['.js'],
mainFields: ['main'] // pick ES5 version of vanilla-picker
},
cache: true
})
// create a single instance of the compiler to allow caching
const compilerMinimalist = webpack({
entry: ENTRY,
output: {
library: 'JSONEditor',
libraryTarget: 'umd',
path: DIST,
filename: NAME_MINIMALIST + '.js'
},
module: webpackConfigModule,
plugins: [
bannerPlugin,
new webpack.IgnorePlugin(new RegExp('^ace-builds')),
new webpack.IgnorePlugin(new RegExp('worker-json-data-url')),
new webpack.IgnorePlugin(new RegExp('^ajv')),
new webpack.IgnorePlugin(new RegExp('^vanilla-picker'))
],
optimization: {
// We no not want to minimize our code.
minimize: false
},
cache: true
})
function minify (name) {
const code = String(fs.readFileSync(DIST + '/' + name + '.js'))
const result = uglify.minify(code, {
sourceMap: {
url: name + '.map'
},
output: {
comments: /@license/,
max_line_len: 64000 // extra large because we have embedded code for workers
}
})
if (result.error) {
throw result.error
}
const fileMin = DIST + '/' + name + '.min.js'
const fileMap = DIST + '/' + name + '.map'
fs.writeFileSync(fileMin, result.code)
fs.writeFileSync(fileMap, result.map)
log('Minified ' + fileMin)
log('Mapped ' + fileMap)
}
// make dist folder structure
gulp.task('mkdir', function (done) {
mkdirp.sync(DIST)
mkdirp.sync(DIST + '/img')
done()
})
// Create an embedded version of the json worker code: a data url
gulp.task('embed-json-worker', function (done) {
const workerBundleFile = './node_modules/ace-builds/src-noconflict/worker-json.js'
const workerEmbeddedFile = './src/js/generated/worker-json-data-url.js'
const workerScript = String(fs.readFileSync(workerBundleFile))
const workerDataUrl = 'data:application/javascript;base64,' + btoa(workerScript)
fs.writeFileSync(workerEmbeddedFile, 'module.exports = \'' + workerDataUrl + '\'\n')
done()
})
// bundle javascript
gulp.task('bundle', 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) {
if (err) {
log(err)
}
log('bundled ' + NAME + '.js')
done()
})
})
// bundle minimalist version of javascript
gulp.task('bundle-minimalist', function (done) {
// update the banner contents (has a date in it which should stay up to date)
bannerPlugin.banner = createBanner()
compilerMinimalist.run(function (err, stats) {
if (err) {
log(err)
}
log('bundled ' + NAME_MINIMALIST + '.js')
done()
})
})
// bundle css
gulp.task('bundle-css', function (done) {
gulp
.src(['src/scss/jsoneditor.scss'])
.pipe(
sass({
// importer: tildeImporter
})
)
.pipe(concatCss(NAME + '.css'))
.pipe(gulp.dest(DIST))
.pipe(concatCss(NAME + '.min.css'))
.pipe(minifyCSS())
.pipe(gulp.dest(DIST))
done()
})
// create a folder img and copy the icons
gulp.task('copy-img', function (done) {
gulp.src(IMAGE).pipe(gulp.dest(DIST + '/img'))
log('Copied images')
done()
})
// create a folder img and copy the icons
gulp.task('copy-docs', function (done) {
gulp.src(DOCS).pipe(gulp.dest(DIST))
log('Copied doc')
done()
})
gulp.task('minify', function (done) {
minify(NAME)
done()
})
gulp.task('minify-minimalist', function (done) {
minify(NAME_MINIMALIST)
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', gulp.series('bundle', 'bundle-css', 'copy-img', function () {
gulp.watch(['src/**/*'], gulp.series('bundle', 'bundle-css', 'copy-img'))
}))
// The default task (called when you run `gulp`)
gulp.task('default', gulp.series(
'mkdir',
'embed-json-worker',
gulp.parallel(
'copy-img',
'copy-docs',
'bundle-css',
gulp.series('bundle', 'minify'),
gulp.series('bundle-minimalist', 'minify-minimalist')
)
))

View File

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

14
misc/architecture.md Normal file
View File

@ -0,0 +1,14 @@
# JSONEditor Svelte architecture choices
- Immutable -> one-way data binding
- All Actions based on JSONPatch
- It must be possible to persist all state, including expanded/collapsed and
selection.
- State with search results, and, expanded state, and selection is separate
from the JSON document itself, and are JSON objectswith the same structure,
using symbols.
- Must be able to open huge JSON files
- Must work directly on the JSON object itself, not on a wrapped object model
- Display only the first 100 items of an array etc. Or show items in groups
of 100 items.
- Search must not crash on large files. Stop at 999 results or something.

9497
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,75 +1,29 @@
{ {
"name": "jsoneditor", "name": "svelte-app",
"version": "8.6.6", "version": "1.0.0",
"main": "./index",
"description": "A web-based tool to view, edit, format, and validate JSON",
"tags": [
"json",
"editor",
"viewer",
"formatter"
],
"author": "Jos de Jong <wjosdejong@gmail.com>",
"license": "Apache-2.0",
"homepage": "https://github.com/josdejong/jsoneditor",
"repository": {
"type": "git",
"url": "https://github.com/josdejong/jsoneditor.git"
},
"bugs": "https://github.com/josdejong/jsoneditor/issues",
"scripts": { "scripts": {
"build": "gulp", "build": "rollup -c",
"minify": "gulp minify", "dev": "rollup -c -w",
"start": "gulp watch", "start": "sirv public",
"test": "mocha test --require @babel/register", "test": "mocha ./src/**/*.test.js"
"lint": "standard --env=mocha",
"prepublishOnly": "npm test && npm run build"
}, },
"type": "module",
"dependencies": { "dependencies": {
"ace-builds": "^1.4.11", "@fortawesome/free-regular-svg-icons": "5.13.0",
"ajv": "^6.12.2", "@fortawesome/free-solid-svg-icons": "5.13.0",
"javascript-natural-sort": "^0.7.1", "svelte-awesome": "2.3.0"
"jmespath": "^0.15.0",
"json-source-map": "^0.6.1",
"mobius1-selectr": "^2.4.13",
"picomodal": "^3.0.0",
"vanilla-picker": "^2.10.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.9.0", "@rollup/plugin-commonjs": "11.1.0",
"@babel/preset-env": "7.9.5", "@rollup/plugin-node-resolve": "7.1.3",
"@babel/register": "7.9.0",
"babel-loader": "8.1.0",
"btoa": "1.2.1",
"date-format": "3.0.0",
"fancy-log": "1.3.3",
"gulp": "4.0.2",
"gulp-clean-css": "4.3.0",
"gulp-concat-css": "3.1.0",
"gulp-sass": "4.0.2",
"jsdom": "16.2.2",
"json-loader": "0.5.7",
"mkdirp": "1.0.4",
"mocha": "7.1.1", "mocha": "7.1.1",
"standard": "14.3.3", "rollup": "2.7.2",
"uglify-js": "3.9.1", "rollup-plugin-livereload": "1.2.0",
"webpack": "4.43.0" "rollup-plugin-svelte": "5.2.1",
}, "rollup-plugin-terser": "5.3.0",
"files": [ "sass": "1.26.5",
"dist", "sirv-cli": "0.4.5",
"docs", "svelte": "3.21.0",
"examples", "svelte-preprocess": "3.7.4"
"src",
"HISTORY.md",
"index.js",
"LICENSE",
"NOTICE",
"README.md"
],
"standard": {
"ignore": [
"src/js/assets",
"examples/react*"
]
} }
} }

BIN
public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

16
public/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>JSON Editor (Svelte)</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

75
rollup.config.js Normal file
View File

@ -0,0 +1,75 @@
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
import autoPreprocess from 'svelte-preprocess';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// // we'll extract any component CSS out into
// // a separate file - better for performance
// css: css => {
// css.write('public/build/bundle.css');
// },
preprocess: autoPreprocess()
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration -
// consult the documentation for details:
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
function serve() {
let started = false;
return {
writeBundle() {
if (!started) {
started = true;
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
}
}
};
}

196
src/App.svelte Normal file
View File

@ -0,0 +1,196 @@
<script>
import Icon from 'svelte-awesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import Node from './JSONNode.svelte'
import { search } from './utils'
import { beforeUpdate, afterUpdate } from 'svelte'
export let searchText = ''
let json = {
'array': [1, 2, 3, {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
},
}],
'boolean': true,
'color': '#82b92c',
'null': null,
'number': 123,
'object': {'a': 'b', 'c': 'd', nested: {
name: 'Item ' + 2,
id: String(2),
index: 2,
time: new Date().toISOString(),
location: {
latitude: 1.23,
longitude: 23.44,
coordinates: [23.44, 1.23]
},
}},
'string': 'Hello World'
}
let uniDirectionalValue = 'test uni directional flow in Svelte';
console.time('load editor')
beforeUpdate(() => console.time('render app'))
afterUpdate(() => console.timeEnd('render app'))
// console.log('search results "d"', search(null, json, 'd'))
// console.log('search results "e"', search(null, json, 'e'))
// console.log('search results "2"', search(null, json, '2'))
function doSearch (json, searchText) {
console.time('search')
const result = search(null, json, searchText)
console.timeEnd('search')
return result
}
$: searchResult = searchText ? doSearch(json, searchText) : undefined
$: formattedName = `"${name}"`
$: console.log('json.number', json.number)
function loadLargeJson () {
const count = 500
console.log('create large json', { count })
console.time('create large json')
const largeJson = {
}
largeJson.numbers = []
largeJson.array = []
for (let i = 0; i < count; i++) {
const longitude = 4 + i / count;
const latitude = 51 + i / count;
largeJson.numbers.push(i);
largeJson.array.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude,
longitude,
coordinates: [longitude, latitude]
},
random: Math.random()
})
}
console.timeEnd('create large json')
// const stringifiedSize = JSON.stringify(largeJson).length
// console.log(`large json stringified size: ${filesize(stringifiedSize)}`)
return largeJson
}
// json = loadLargeJson()
function handleChange (value, key) {
console.log('App handleChange')
json = value
}
function handleInputTextArea (event) {
console.log('on:input')
try {
json = JSON.parse(event.target.value)
} catch (err) {
console.error(err)
}
}
function handleLoadLargeJson () {
json = loadLargeJson()
}
function handleClearJson () {
json = {}
}
setTimeout(() => {
console.timeEnd('load editor')
console.log('loaded' )
})
function handleInputUniDirectional (event) {
console.log('change', event.target.value)
uniDirectionalValue = event.target.value
}
let inputTestValue = 'input test'
function onChangeInputTest (value) {
inputTestValue = value
}
</script>
<style>
html, body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
h1 {
color: purple;
}
.editor {
width: 500px;
height: 500px;
overflow: auto;
border: 1px solid lightgray;
}
</style>
<p>
<Icon data={faSearch} /> Search: <input bind:value={searchText} />
</p>
<div class="editor">
<Node
value={json}
searchResult={searchResult}
expanded={true}
onChange={handleChange}
/>
</div>
<!-- <textarea
class='code'
value={JSON.stringify(json, null, 2)}
on:input={handleInputTextArea}
/> -->
<p>
<button on:click={handleLoadLargeJson}>load large json</button>
<button on:click={handleClearJson}>clear json</button>
</p>
<!-- <pre>
<code>
{JSON.stringify(json, null, 2)}
</code>
</pre> -->

136
src/JSONNode.svelte Normal file
View File

@ -0,0 +1,136 @@
<script>
import { getType, SEARCH_PROPERTY, SEARCH_VALUE } from './utils'
export let key = 'root'
export let value
export let searchResult
export let onChange
export let expanded = true
export let indentation = 0
const DEFAULT_LIMIT = 10000
let limit = DEFAULT_LIMIT
$: type = getType(value)
$: props = type === 'object'
? Object.keys(value).map(key => {
return { key, value: value[key] }
})
: undefined
$: limited = type === 'array' && value.length > limit
$: items = type === 'array'
? limited ? value.slice(0, limit) : value
: undefined
function toggle () {
expanded = !expanded
}
function handleInput (event) {
const value = event.target.innerText
onChange(value, key)
}
function handleChange (childValue, childKey) {
// FIXME: use an immutability setIn function here
if (type === 'array') {
const updatedValue = value.slice(0) // copy the array
updatedValue[childKey] = childValue
onChange(updatedValue, key)
} else if (type === 'object') {
const updatedValue = {
...value,
[childKey]: childValue
}
onChange(updatedValue, key)
}
}
function handleShowAll () {
limit = Infinity
}
</script>
<style type="text/scss">
$search-background-color: gold;
.key {
color: gray;
}
.key.search {
background-color: $search-background-color;
}
.items,
.props {
padding-left: 24px;
}
.value {
display: inline-block;
padding: 3px;
}
.key.search,
.value.search {
background-color: $search-background-color;
}
</style>
<div class='json-node'>
<span class="key {searchResult && searchResult[SEARCH_PROPERTY] ? 'search' : ''}">
{key} :
</span>
{#if type !== 'value'}
<button on:click={toggle}>
{#if expanded}
collapse
{:else}
expand
{/if}
</button>
{/if}
{#if type === 'array'}
{#if expanded}
<div class="items">
{#each items as item, index (index)}
<svelte:self
key={index}
value={item}
searchResult={searchResult ? searchResult[index] : undefined}
indentation={indentation + 1}
onChange={handleChange}
/>
{/each}
{#if limited}
<div>
(showing {limit} of {value.length} items <button on:click={handleShowAll}>show all</button>)
</div>
{/if}
</div>
{/if}
{:else if type === 'object'}
{#if expanded}
<div class="props">
{#each props as prop (prop.key)}
<svelte:self
key={prop.key}
value={prop.value}
searchResult={searchResult ? searchResult[prop.key] : undefined}
indentation={indentation + 1}
onChange={handleChange}
/>
{/each}
</div>
{/if}
{:else}
<div
class="value {searchResult && searchResult[SEARCH_VALUE] ? 'search' : ''}"
contenteditable="true"
on:input={handleInput}
>
{value}
</div>
{/if}
</div>

View File

@ -1,44 +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.
- `vanilla-picker`, used as color picker.
This reduces the the size of the minified and gzipped JavaScript file
from about 210 kB to about 70 kB (one third).
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.
- You don't need the color picker, or want to provide your own
color picker using `onColorPicker`.
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,429 +0,0 @@
'use strict'
import { createAbsoluteAnchor } from './createAbsoluteAnchor'
import { addClassName, getSelection, removeClassName, setSelection } from './util'
import { translate } from './i18n'
/**
* 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
*/
export class ContextMenu {
constructor (items, options) {
this.dom = {}
const me = this
const 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
const root = document.createElement('div')
root.className = 'jsoneditor-contextmenu-root'
dom.root = root
// create a container element
const menu = document.createElement('div')
menu.className = 'jsoneditor-contextmenu'
dom.menu = menu
root.appendChild(menu)
// create a list to hold the menu items
const 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
const focusButton = document.createElement('button')
focusButton.type = 'button'
dom.focusButton = focusButton
const 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(item => {
if (item.type === 'separator') {
// create a separator
const separator = document.createElement('div')
separator.className = 'jsoneditor-separator'
const li = document.createElement('li')
li.appendChild(separator)
list.appendChild(li)
} else {
const domItem = {}
// create a menu item
const li = document.createElement('li')
list.appendChild(li)
// create a button in the menu item
const button = document.createElement('button')
button.type = 'button'
button.className = item.className
domItem.button = button
if (item.title) {
button.title = item.title
}
if (item.click) {
button.onclick = 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
const divIcon = document.createElement('div')
divIcon.className = 'jsoneditor-icon'
button.appendChild(divIcon)
const divText = document.createElement('div')
divText.className = 'jsoneditor-text' +
(item.click ? '' : ' jsoneditor-right-margin')
divText.appendChild(document.createTextNode(item.text))
button.appendChild(divText)
let buttonSubmenu
if (item.click) {
// submenu and a button with a click handler
button.className += ' jsoneditor-default'
const buttonExpand = document.createElement('button')
buttonExpand.type = '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
const divExpand = document.createElement('div')
divExpand.className = 'jsoneditor-expand'
button.appendChild(divExpand)
buttonSubmenu = button
}
// attach a handler to expand/collapse the submenu
buttonSubmenu.onclick = event => {
event.preventDefault()
me._onExpandItem(domItem)
buttonSubmenu.focus()
}
// create the submenu
const domSubItems = []
domItem.subItems = domSubItems
const 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>' +
'<div class="jsoneditor-text">' + translate(item.text) + '</div>'
}
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(item => {
const 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
*/
_getVisibleButtons () {
const buttons = []
const me = this
this.dom.items.forEach(item => {
buttons.push(item.button)
if (item.buttonExpand) {
buttons.push(item.buttonExpand)
}
if (item.subItems && item === me.expandedItem) {
item.subItems.forEach(subItem => {
buttons.push(subItem.button)
if (subItem.buttonExpand) {
buttons.push(subItem.buttonExpand)
}
// TODO: change to fully recursive method
})
}
})
return buttons
}
/**
* Attach the menu to an anchor
* @param {HTMLElement} anchor Anchor where the menu will be attached as sibling.
* @param {HTMLElement} frame The root of the JSONEditor window
* @param {Boolean=} ignoreParent ignore anchor parent in regard to the calculation of the position, needed when the parent position is absolute
*/
show (anchor, frame, ignoreParent) {
this.hide()
// determine whether to display the menu below or above the anchor
let showBelow = true
const parent = anchor.parentNode
const anchorRect = anchor.getBoundingClientRect()
const parentRect = parent.getBoundingClientRect()
const frameRect = frame.getBoundingClientRect()
const me = this
this.dom.absoluteAnchor = createAbsoluteAnchor(anchor, frame, () => {
me.hide()
})
if (anchorRect.bottom + this.maxHeight < frameRect.bottom) {
// fits below -> show below
} else if (anchorRect.top - this.maxHeight > frameRect.top) {
// fits above -> show above
showBelow = false
} else {
// doesn't fit above nor below -> show below
}
const topGap = ignoreParent ? 0 : (anchorRect.top - parentRect.top)
// position the menu
if (showBelow) {
// display the menu below the anchor
const anchorHeight = anchor.offsetHeight
this.dom.menu.style.left = '0'
this.dom.menu.style.top = topGap + anchorHeight + 'px'
this.dom.menu.style.bottom = ''
} else {
// display the menu above the anchor
this.dom.menu.style.left = '0'
this.dom.menu.style.top = ''
this.dom.menu.style.bottom = '0px'
}
// attach the menu to the temporary, absolute anchor
// parent.insertBefore(this.dom.root, anchor);
this.dom.absoluteAnchor.appendChild(this.dom.root)
// move focus to the first button in the context menu
this.selection = getSelection()
this.anchor = anchor
setTimeout(() => {
me.dom.focusButton.focus()
}, 0)
if (ContextMenu.visibleMenu) {
ContextMenu.visibleMenu.hide()
}
ContextMenu.visibleMenu = this
}
/**
* Hide the context menu if visible
*/
hide () {
// remove temporary absolutely positioned anchor
if (this.dom.absoluteAnchor) {
this.dom.absoluteAnchor.destroy()
delete this.dom.absoluteAnchor
}
// remove the menu from the DOM
if (this.dom.root.parentNode) {
this.dom.root.parentNode.removeChild(this.dom.root)
if (this.onClose) {
this.onClose()
}
}
if (ContextMenu.visibleMenu === this) {
ContextMenu.visibleMenu = undefined
}
}
/**
* Expand a submenu
* Any currently expanded submenu will be hided.
* @param {Object} domItem
* @private
*/
_onExpandItem (domItem) {
const me = this
const alreadyVisible = (domItem === this.expandedItem)
// hide the currently visible submenu
const expandedItem = this.expandedItem
if (expandedItem) {
// var ul = expandedItem.ul;
expandedItem.ul.style.height = '0'
expandedItem.ul.style.padding = ''
setTimeout(() => {
if (me.expandedItem !== expandedItem) {
expandedItem.ul.style.display = ''
removeClassName(expandedItem.ul.parentNode, 'jsoneditor-selected')
}
}, 300) // timeout duration must match the css transition duration
this.expandedItem = undefined
}
if (!alreadyVisible) {
const ul = domItem.ul
ul.style.display = 'block'
// eslint-disable-next-line no-unused-expressions
ul.clientHeight // force a reflow in Firefox
setTimeout(() => {
if (me.expandedItem === domItem) {
let childsHeight = 0
for (let i = 0; i < ul.childNodes.length; i++) {
childsHeight += ul.childNodes[i].clientHeight
}
ul.style.height = childsHeight + 'px'
ul.style.padding = '5px 10px'
}
}, 0)
addClassName(ul.parentNode, 'jsoneditor-selected')
this.expandedItem = domItem
}
}
/**
* Handle onkeydown event
* @param {Event} event
* @private
*/
_onKeyDown (event) {
const target = event.target
const keynum = event.which
let handled = false
let buttons, targetIndex, prevButton, nextButton
if (keynum === 27) { // ESC
// hide the menu on ESC key
// restore previous selection and focus
if (this.selection) {
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()
}
}
}
// currently displayed context menu, a singleton. We may only have one visible context menu
ContextMenu.visibleMenu = undefined
export default ContextMenu

View File

@ -1,169 +0,0 @@
/**
* Show errors and schema warnings in a clickable table view
* @param {Object} config
* @property {boolean} errorTableVisible
* @property {function (boolean) : void} onToggleVisibility
* @property {function (number)} [onFocusLine]
* @property {function (number)} onChangeHeight
* @constructor
*/
export class ErrorTable {
constructor (config) {
this.errorTableVisible = config.errorTableVisible
this.onToggleVisibility = config.onToggleVisibility
this.onFocusLine = config.onFocusLine || (() => {})
this.onChangeHeight = config.onChangeHeight
this.dom = {}
const validationErrorsContainer = document.createElement('div')
validationErrorsContainer.className = 'jsoneditor-validation-errors-container'
this.dom.validationErrorsContainer = validationErrorsContainer
const additionalErrorsIndication = document.createElement('div')
additionalErrorsIndication.style.display = 'none'
additionalErrorsIndication.className = 'jsoneditor-additional-errors fadein'
additionalErrorsIndication.innerHTML = 'Scroll for more &#9663;'
this.dom.additionalErrorsIndication = additionalErrorsIndication
validationErrorsContainer.appendChild(additionalErrorsIndication)
const validationErrorIcon = document.createElement('span')
validationErrorIcon.className = 'jsoneditor-validation-error-icon'
validationErrorIcon.style.display = 'none'
this.dom.validationErrorIcon = validationErrorIcon
const validationErrorCount = document.createElement('span')
validationErrorCount.className = 'jsoneditor-validation-error-count'
validationErrorCount.style.display = 'none'
this.dom.validationErrorCount = validationErrorCount
this.dom.parseErrorIndication = document.createElement('span')
this.dom.parseErrorIndication.className = 'jsoneditor-parse-error-icon'
this.dom.parseErrorIndication.style.display = 'none'
}
getErrorTable () {
return this.dom.validationErrorsContainer
}
getErrorCounter () {
return this.dom.validationErrorCount
}
getWarningIcon () {
return this.dom.validationErrorIcon
}
getErrorIcon () {
return this.dom.parseErrorIndication
}
toggleTableVisibility () {
this.errorTableVisible = !this.errorTableVisible
this.onToggleVisibility(this.errorTableVisible)
}
setErrors (errors, errorLocations) {
// clear any previous errors
if (this.dom.validationErrors) {
this.dom.validationErrors.parentNode.removeChild(this.dom.validationErrors)
this.dom.validationErrors = null
this.dom.additionalErrorsIndication.style.display = 'none'
}
// create the table with errors
// keep default behavior for parse errors
if (this.errorTableVisible && errors.length > 0) {
const validationErrors = document.createElement('div')
validationErrors.className = 'jsoneditor-validation-errors'
validationErrors.innerHTML = '<table class="jsoneditor-text-errors"><tbody></tbody></table>'
const tbody = validationErrors.getElementsByTagName('tbody')[0]
errors.forEach(error => {
let message
if (typeof error === 'string') {
message = '<td colspan="2"><pre>' + error + '</pre></td>'
} else {
message =
'<td>' + (error.dataPath || '') + '</td>' +
'<td><pre>' + error.message + '</pre></td>'
}
let line
if (!isNaN(error.line)) {
line = error.line
} else if (error.dataPath) {
const errLoc = errorLocations.find(loc => loc.path === error.dataPath)
if (errLoc) {
line = errLoc.line + 1
}
}
const trEl = document.createElement('tr')
trEl.className = !isNaN(line) ? 'jump-to-line' : ''
if (error.type === 'error') {
trEl.className += ' parse-error'
} else {
trEl.className += ' validation-error'
}
trEl.innerHTML = ('<td><button class="jsoneditor-schema-error"></button></td><td style="white-space:nowrap;">' + (!isNaN(line) ? ('Ln ' + line) : '') + '</td>' + message)
trEl.onclick = () => {
this.onFocusLine(line)
}
tbody.appendChild(trEl)
})
this.dom.validationErrors = validationErrors
this.dom.validationErrorsContainer.appendChild(validationErrors)
this.dom.additionalErrorsIndication.title = errors.length + ' errors total'
if (this.dom.validationErrorsContainer.clientHeight < this.dom.validationErrorsContainer.scrollHeight) {
this.dom.additionalErrorsIndication.style.display = 'block'
this.dom.validationErrorsContainer.onscroll = () => {
this.dom.additionalErrorsIndication.style.display =
(this.dom.validationErrorsContainer.clientHeight > 0 && this.dom.validationErrorsContainer.scrollTop === 0) ? 'block' : 'none'
}
} else {
this.dom.validationErrorsContainer.onscroll = undefined
}
const height = this.dom.validationErrorsContainer.clientHeight + (this.dom.statusBar ? this.dom.statusBar.clientHeight : 0)
// this.content.style.marginBottom = (-height) + 'px';
// this.content.style.paddingBottom = height + 'px';
this.onChangeHeight(height)
} else {
this.onChangeHeight(0)
}
// update the status bar
const validationErrorsCount = errors.filter(error => error.type !== 'error').length
if (validationErrorsCount > 0) {
this.dom.validationErrorCount.style.display = 'inline'
this.dom.validationErrorCount.innerText = validationErrorsCount
this.dom.validationErrorCount.onclick = this.toggleTableVisibility.bind(this)
this.dom.validationErrorIcon.style.display = 'inline'
this.dom.validationErrorIcon.title = validationErrorsCount + ' schema validation error(s) found'
this.dom.validationErrorIcon.onclick = this.toggleTableVisibility.bind(this)
} else {
this.dom.validationErrorCount.style.display = 'none'
this.dom.validationErrorIcon.style.display = 'none'
}
// update the parse error icon
const hasParseErrors = errors.some(error => error.type === 'error')
if (hasParseErrors) {
const line = errors[0].line
this.dom.parseErrorIndication.style.display = 'block'
this.dom.parseErrorIndication.title = !isNaN(line)
? ('parse error on line ' + line)
: 'parse error - check that the json is valid'
this.dom.parseErrorIndication.onclick = this.toggleTableVisibility.bind(this)
} else {
this.dom.parseErrorIndication.style.display = 'none'
}
}
}

View File

@ -1,100 +0,0 @@
'use strict'
/**
* @constructor FocusTracker
* A custom focus tracker for a DOM element with complex internal DOM structure
* @param {[Object]} config A set of configurations for the FocusTracker
* {DOM Object} target * The DOM object to track (required)
* {Function} onFocus onFocus callback
* {Function} onBlur onBlur callback
*
* @return
*/
export class FocusTracker {
constructor (config) {
this.target = config.target || null
if (!this.target) {
throw new Error('FocusTracker constructor called without a "target" to track.')
}
this.onFocus = (typeof config.onFocus === 'function') ? config.onFocus : null
this.onBlur = (typeof config.onBlur === 'function') ? config.onBlur : null
this._onClick = this._onEvent.bind(this)
this._onKeyUp = function (event) {
if (event.which === 9 || event.keyCode === 9) {
this._onEvent(event)
}
}.bind(this)
this.focusFlag = false
this.firstEventFlag = true
/*
Adds required (click and keyup) event listeners to the 'document' object
to track the focus of the given 'target'
*/
if (this.onFocus || this.onBlur) {
document.addEventListener('click', this._onClick)
document.addEventListener('keyup', this._onKeyUp)
}
}
/**
* Removes the event listeners on the 'document' object
* that were added to track the focus of the given 'target'
*/
destroy () {
document.removeEventListener('click', this._onClick)
document.removeEventListener('keyup', this._onKeyUp)
this._onEvent({ target: document.body }) // calling _onEvent with body element in the hope that the FocusTracker is added to an element inside the body tag
}
/**
* Tracks the focus of the target and calls the onFocus and onBlur
* event callbacks if available.
* @param {Event} [event] The 'click' or 'keyup' event object,
* from the respective events set on
* document object
* @private
*/
_onEvent (event) {
const target = event.target
let focusFlag
if (target === this.target) {
focusFlag = true
} else if (this.target.contains(target) || this.target.contains(document.activeElement)) {
focusFlag = true
} else {
focusFlag = false
}
if (focusFlag) {
if (!this.focusFlag) {
// trigger the onFocus callback
if (this.onFocus) {
this.onFocus({ type: 'focus', target: this.target })
}
this.focusFlag = true
}
} else {
if (this.focusFlag || this.firstEventFlag) {
// trigger the onBlur callback
if (this.onBlur) {
this.onBlur({ type: 'blur', target: this.target })
}
this.focusFlag = false
/*
When switching from one mode to another in the editor, the FocusTracker gets recreated.
At that time, this.focusFlag will be init to 'false' and will fail the above if condition, when blur occurs
this.firstEventFlag is added to overcome that issue
*/
if (this.firstEventFlag) {
this.firstEventFlag = false
}
}
}
}
}

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
*/
export class Highlighter {
constructor () {
this.locked = false
}
/**
* Hightlight given node and its childs
* @param {Node} node
*/
highlight (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
*/
unhighlight () {
if (this.locked) {
return
}
const 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(() => {
me.node.setHighlight(false)
me.node = undefined
me.unhighlightTimer = undefined
}, 0)
}
}
/**
* Cancel an unhighlight action (if before the timeout of the unhighlight action)
* @private
*/
_cancelUnhighlight () {
if (this.unhighlightTimer) {
clearTimeout(this.unhighlightTimer)
this.unhighlightTimer = undefined
}
}
/**
* Lock highlighting or unhighlighting nodes.
* methods highlight and unhighlight do not work while locked.
*/
lock () {
this.locked = true
}
/**
* Unlock highlighting or unhighlighting nodes
*/
unlock () {
this.locked = false
}
}

View File

@ -1,85 +0,0 @@
/**
* Keep track on any history, be able
* @param {function} onChange
* @param {function} calculateItemSize
* @param {number} limit Maximum size of all items in history
* @constructor
*/
export class History {
constructor (onChange, calculateItemSize, limit) {
this.onChange = onChange
this.calculateItemSize = calculateItemSize || (() => 1)
this.limit = limit
this.items = []
this.index = -1
}
add (item) {
// limit number of items in history so that the total size doesn't
// always keep at least one item in memory
while (this._calculateHistorySize() > this.limit && this.items.length > 1) {
this.items.shift()
this.index--
}
// cleanup any redo action that are not valid anymore
this.items = this.items.slice(0, this.index + 1)
this.items.push(item)
this.index++
this.onChange()
}
_calculateHistorySize () {
const calculateItemSize = this.calculateItemSize
let totalSize = 0
this.items.forEach(item => {
totalSize += calculateItemSize(item)
})
return totalSize
}
undo () {
if (!this.canUndo()) {
return
}
this.index--
this.onChange()
return this.items[this.index]
}
redo () {
if (!this.canRedo()) {
return
}
this.index++
this.onChange()
return this.items[this.index]
}
canUndo () {
return this.index > 0
}
canRedo () {
return this.index < this.items.length - 1
}
clear () {
this.items = []
this.index = -1
this.onChange()
}
}

View File

@ -1,491 +0,0 @@
'use strict'
const ace = require('./ace') // may be undefined in case of minimalist bundle
const VanillaPicker = require('./vanilla-picker') // may be undefined in case of minimalist bundle
const { treeModeMixins } = require('./treemode')
const { textModeMixins } = require('./textmode')
const { previewModeMixins } = require('./previewmode')
const { clear, extend, getInternetExplorerVersion, parse } = require('./util')
const { tryRequireAjv } = require('./tryRequireAjv')
const { showTransformModal } = require('./showTransformModal')
const { showSortModal } = require('./showSortModal')
const Ajv = tryRequireAjv()
if (typeof Promise === 'undefined') {
console.error('Promise undefined. Please load a Promise polyfill in the browser in order to use JSONEditor')
}
/**
* @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.
* Does not pass the contents itself.
* See also `onChangeJSON` and
* `onChangeText`.
* {function} onChangeJSON Callback method, triggered
* in modes on change of contents,
* passing the changed contents
* as JSON.
* Only applicable for modes
* 'tree', 'view', and 'form'.
* {function} onChangeText Callback method, triggered
* in modes on change of contents,
* passing the changed contents
* as stringified JSON.
* {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.
* {function} onSelectionChange Callback method,
* triggered on node selection change
* Only applicable for modes
* 'tree', 'view', and 'form'
* {function} onTextSelectionChange Callback method,
* triggered on text selection change
* Only applicable for modes
* {HTMLElement} modalAnchor The anchor element to apply an
* overlay and display the modals in a
* centered location.
* Defaults to document.body
* 'text' and 'code'
* {function} onEvent Callback method, triggered
* when an event occurs in
* a JSON field or value.
* Only applicable for
* modes 'form', 'tree' and
* 'view'
* {function} onFocus Callback method, triggered
* when the editor comes into focus,
* passing an object {type, target},
* Applicable for all modes
* {function} onBlur Callback method, triggered
* when the editor goes out of focus,
* passing an object {type, target},
* Applicable for all modes
* {function} onClassName Callback method, triggered
* when a Node DOM is rendered. Function returns
* a css class name to be set on a node.
* Only applicable for
* modes 'form', 'tree' and
* 'view'
* {Number} maxVisibleChilds Number of children allowed for a node
* in 'tree', 'view', or 'form' mode before
* the "show more/show all" buttons appear.
* 100 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)
const ieVersion = 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
}
// warn if onChangeJSON is used when mode can be `text` or `code`
if (options.onChangeJSON) {
if (options.mode === 'text' || options.mode === 'code' ||
(options.modes && (options.modes.indexOf('text') !== -1 || options.modes.indexOf('code') !== -1))) {
console.warn('Option "onChangeJSON" is not applicable to modes "text" and "code". ' +
'Use "onChangeText" or "onChange" instead.')
}
}
// validate options
if (options) {
Object.keys(options).forEach(option => {
if (JSONEditor.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 validation in milliseconds
JSONEditor.prototype.DEBOUNCE_INTERVAL = 150
JSONEditor.VALID_OPTIONS = [
'ajv', 'schema', 'schemaRefs', 'templates',
'ace', 'theme', 'autocomplete',
'onChange', 'onChangeJSON', 'onChangeText',
'onEditable', 'onError', 'onEvent', 'onModeChange', 'onNodeName', 'onValidate', 'onCreateMenu',
'onSelectionChange', 'onTextSelectionChange', 'onClassName',
'onFocus', 'onBlur',
'colorPicker', 'onColorPicker',
'timestampTag', 'timestampFormat',
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform',
'maxVisibleChilds', 'onValidationError',
'modalAnchor', 'popupAnchor',
'createQuery', 'executeQuery', 'queryDescription'
]
/**
* 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 || {}
const mode = this.options.mode || (this.options.modes && this.options.modes[0]) || 'tree'
this.setMode(mode)
}
/**
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
JSONEditor.prototype.destroy = () => {}
/**
* 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 = 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) {
// if the mode is the same as current mode (and it's not the first time), do nothing.
if (mode === this.options.mode && this.create) {
return
}
const container = this.container
const options = extend({}, this.options)
const oldMode = options.mode
let data
let name
options.mode = mode
const config = JSONEditor.modes[mode]
if (config) {
try {
const asText = (config.data === 'text')
name = this.getName()
data = this[asText ? 'getText' : 'get']() // get text or json
this.destroy()
clear(this)
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
* @param {Object.<string, Object>=} schemaRefs Schemas that are referenced using the `$ref` property from the JSON schema that are set in the `schema` option,
+ the object structure in the form of `{reference_key: schemaObject}`
*/
JSONEditor.prototype.setSchema = function (schema, schemaRefs) {
// compile a JSON schema validator if a JSON schema is provided
if (schema) {
let ajv
try {
// grab ajv from options if provided, else create a new instance
if (this.options.ajv) {
ajv = this.options.ajv
} else {
ajv = Ajv({
allErrors: true,
verbose: true,
schemaId: 'auto',
$data: true
})
// support both draft-04 and draft-06 alongside the latest draft-07
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'))
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'))
}
} 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) {
if (schemaRefs) {
for (const ref in schemaRefs) {
ajv.removeSchema(ref) // When updating a schema - old refs has to be removed first
if (schemaRefs[ref]) {
ajv.addSchema(schemaRefs[ref], ref)
}
}
this.options.schemaRefs = schemaRefs
}
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.options.schemaRefs = 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 = () => {
// must be implemented by treemode and textmode
}
/**
* Refresh the rendered contents
*/
JSONEditor.prototype.refresh = () => {
// 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 = mode => {
let i, prop
if (Array.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')
const 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')
}
const 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, text, and preview modes
JSONEditor.registerMode(treeModeMixins)
JSONEditor.registerMode(textModeMixins)
JSONEditor.registerMode(previewModeMixins)
// expose some of the libraries that can be used customized
JSONEditor.ace = ace
JSONEditor.Ajv = Ajv
JSONEditor.VanillaPicker = VanillaPicker
// expose some utils (this is undocumented, unofficial)
JSONEditor.showTransformModal = showTransformModal
JSONEditor.showSortModal = showSortModal
// default export for TypeScript ES6 projects
JSONEditor.default = JSONEditor
module.exports = JSONEditor

View File

@ -1,123 +0,0 @@
'use strict'
import { ContextMenu } from './ContextMenu'
import { translate } from './i18n'
/**
* 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', 'preview'
* @param {String} current Available modes: 'code', 'form', 'text', 'tree', 'view', 'preview'
* @param {function(mode: string)} onSwitch Callback invoked on switch
* @constructor
*/
export class ModeSwitcher {
constructor (container, modes, current, onSwitch) {
// available modes
const availableModes = {
code: {
text: translate('modeCodeText'),
title: translate('modeCodeTitle'),
click: function () {
onSwitch('code')
}
},
form: {
text: translate('modeFormText'),
title: translate('modeFormTitle'),
click: function () {
onSwitch('form')
}
},
text: {
text: translate('modeTextText'),
title: translate('modeTextTitle'),
click: function () {
onSwitch('text')
}
},
tree: {
text: translate('modeTreeText'),
title: translate('modeTreeTitle'),
click: function () {
onSwitch('tree')
}
},
view: {
text: translate('modeViewText'),
title: translate('modeViewTitle'),
click: function () {
onSwitch('view')
}
},
preview: {
text: translate('modePreviewText'),
title: translate('modePreviewTitle'),
click: function () {
onSwitch('preview')
}
}
}
// list the selected modes
const items = []
for (let i = 0; i < modes.length; i++) {
const mode = modes[i]
const 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
const currentMode = availableModes[current]
if (!currentMode) {
throw new Error('Unknown mode "' + current + '"')
}
const currentTitle = currentMode.text
// create the html element
const box = document.createElement('button')
box.type = 'button'
box.className = 'jsoneditor-modes jsoneditor-separator'
box.innerHTML = currentTitle + ' &#x25BE;'
box.title = translate('modeEditorTitle')
box.onclick = () => {
const menu = new ContextMenu(items)
menu.show(box, container)
}
const 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
*/
focus () {
this.dom.box.focus()
}
/**
* Destroy the ModeSwitcher, remove from DOM
*/
destroy () {
if (this.dom && this.dom.frame && this.dom.frame.parentNode) {
this.dom.frame.parentNode.removeChild(this.dom.frame)
}
this.dom = null
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,333 +0,0 @@
'use strict'
import { findUniqueName } from './util'
/**
* @constructor History
* Store action history, enables undo and redo
* @param {JSONEditor} editor
*/
export class NodeHistory {
constructor (editor) {
this.editor = editor
this.history = []
this.index = -1
this.clear()
// helper function to find a Node from a path
function findNode (path) {
return editor.node.findNodeByInternalPath(path)
}
// map with all supported actions
this.actions = {
editField: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
const node = parentNode.childs[params.index]
node.updateField(params.oldValue)
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
const node = parentNode.childs[params.index]
node.updateField(params.newValue)
}
},
editValue: {
undo: function (params) {
findNode(params.path).updateValue(params.oldValue)
},
redo: function (params) {
findNode(params.path).updateValue(params.newValue)
}
},
changeType: {
undo: function (params) {
findNode(params.path).changeType(params.oldType)
},
redo: function (params) {
findNode(params.path).changeType(params.newType)
}
},
appendNodes: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(node => {
parentNode.removeChild(node)
})
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
params.nodes.forEach(node => {
parentNode.appendChild(node)
})
}
},
insertBeforeNodes: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(node => {
parentNode.removeChild(node)
})
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
const beforeNode = findNode(params.beforePath)
params.nodes.forEach(node => {
parentNode.insertBefore(node, beforeNode)
})
}
},
insertAfterNodes: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(node => {
parentNode.removeChild(node)
})
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
let afterNode = findNode(params.afterPath)
params.nodes.forEach(node => {
parentNode.insertAfter(node, afterNode)
afterNode = node
})
}
},
removeNodes: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
const beforeNode = parentNode.childs[params.index] || parentNode.append
params.nodes.forEach(node => {
parentNode.insertBefore(node, beforeNode)
})
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
params.paths.map(findNode).forEach(node => {
parentNode.removeChild(node)
})
}
},
duplicateNodes: {
undo: function (params) {
const parentNode = findNode(params.parentPath)
params.clonePaths.map(findNode).forEach(node => {
parentNode.removeChild(node)
})
},
redo: function (params) {
const parentNode = findNode(params.parentPath)
let afterNode = findNode(params.afterPath)
const nodes = params.paths.map(findNode)
nodes.forEach(node => {
const clone = node.clone()
if (parentNode.type === 'object') {
const existingFieldNames = parentNode.getFieldNames()
clone.field = findUniqueName(node.field, existingFieldNames)
}
parentNode.insertAfter(clone, afterNode)
afterNode = clone
})
}
},
moveNodes: {
undo: function (params) {
const oldParentNode = findNode(params.oldParentPath)
const newParentNode = findNode(params.newParentPath)
const oldBeforeNode = oldParentNode.childs[params.oldIndex] || oldParentNode.append
// first copy the nodes, then move them
const nodes = newParentNode.childs.slice(params.newIndex, params.newIndex + params.count)
nodes.forEach((node, index) => {
node.field = params.fieldNames[index]
oldParentNode.moveBefore(node, oldBeforeNode)
})
// This is a hack to work around an issue that we don't know tha original
// path of the new parent after dragging, as the node is already moved at that time.
if (params.newParentPathRedo === null) {
params.newParentPathRedo = newParentNode.getInternalPath()
}
},
redo: function (params) {
const oldParentNode = findNode(params.oldParentPathRedo)
const newParentNode = findNode(params.newParentPathRedo)
const newBeforeNode = newParentNode.childs[params.newIndexRedo] || newParentNode.append
// first copy the nodes, then move them
const nodes = oldParentNode.childs.slice(params.oldIndexRedo, params.oldIndexRedo + params.count)
nodes.forEach((node, index) => {
node.field = params.fieldNames[index]
newParentNode.moveBefore(node, newBeforeNode)
})
}
},
sort: {
undo: function (params) {
const node = findNode(params.path)
node.hideChilds()
node.childs = params.oldChilds
node.updateDom({ updateIndexes: true })
node.showChilds()
},
redo: function (params) {
const node = findNode(params.path)
node.hideChilds()
node.childs = params.newChilds
node.updateDom({ updateIndexes: true })
node.showChilds()
}
},
transform: {
undo: function (params) {
findNode(params.path).setInternalValue(params.oldValue)
// TODO: would be nice to restore the state of the node and childs
},
redo: function (params) {
findNode(params.path).setInternalValue(params.newValue)
// TODO: would be nice to restore the state of the node and childs
}
}
// 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.
*/
onChange () {}
/**
* 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.
*/
add (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
*/
clear () {
this.history = []
this.index = -1
// fire onchange event
this.onChange()
}
/**
* Check if there is an action available for undo
* @return {Boolean} canUndo
*/
canUndo () {
return (this.index >= 0)
}
/**
* Check if there is an action available for redo
* @return {Boolean} canRedo
*/
canRedo () {
return (this.index < this.history.length - 1)
}
/**
* Undo the last action
*/
undo () {
if (this.canUndo()) {
const obj = this.history[this.index]
if (obj) {
const action = this.actions[obj.action]
if (action && action.undo) {
action.undo(obj.params)
if (obj.params.oldSelection) {
try {
this.editor.setDomSelection(obj.params.oldSelection)
} catch (err) {
console.error(err)
}
}
} else {
console.error(new Error('unknown action "' + obj.action + '"'))
}
}
this.index--
// fire onchange event
this.onChange()
}
}
/**
* Redo the last action
*/
redo () {
if (this.canRedo()) {
this.index++
const obj = this.history[this.index]
if (obj) {
const action = this.actions[obj.action]
if (action && action.redo) {
action.redo(obj.params)
if (obj.params.newSelection) {
try {
this.editor.setDomSelection(obj.params.newSelection)
} catch (err) {
console.error(err)
}
}
} else {
console.error(new Error('unknown action "' + obj.action + '"'))
}
}
// fire onchange event
this.onChange()
}
}
/**
* Destroy history
*/
destroy () {
this.editor = null
this.history = []
this.index = -1
}
}

View File

@ -1,325 +0,0 @@
'use strict'
import { translate } from './i18n'
/**
* @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
*/
export class SearchBox {
constructor (editor, container) {
const searchBox = this
this.editor = editor
this.timeout = undefined
this.delay = 200 // ms
this.lastText = undefined
this.results = null
this.dom = {}
this.dom.container = container
const wrapper = document.createElement('div')
this.dom.wrapper = wrapper
wrapper.className = 'jsoneditor-search'
container.appendChild(wrapper)
const results = document.createElement('div')
this.dom.results = results
results.className = 'jsoneditor-results'
wrapper.appendChild(results)
const divInput = document.createElement('div')
this.dom.input = divInput
divInput.className = 'jsoneditor-frame'
divInput.title = translate('searchTitle')
wrapper.appendChild(divInput)
const refreshSearch = document.createElement('button')
refreshSearch.type = 'button'
refreshSearch.className = 'jsoneditor-refresh'
divInput.appendChild(refreshSearch)
const search = document.createElement('input')
search.type = 'text'
this.dom.search = search
search.oninput = event => {
searchBox._onDelayedSearch(event)
}
search.onchange = event => {
// For IE 9
searchBox._onSearch()
}
search.onkeydown = event => {
searchBox._onKeyDown(event)
}
search.onkeyup = event => {
searchBox._onKeyUp(event)
}
refreshSearch.onclick = event => {
search.select()
}
// TODO: ESC in FF restores the last input, is a FF bug, https://bugzilla.mozilla.org/show_bug.cgi?id=598819
divInput.appendChild(search)
const searchNext = document.createElement('button')
searchNext.type = 'button'
searchNext.title = translate('searchNextResultTitle')
searchNext.className = 'jsoneditor-next'
searchNext.onclick = () => {
searchBox.next()
}
divInput.appendChild(searchNext)
const searchPrevious = document.createElement('button')
searchPrevious.type = 'button'
searchPrevious.title = translate('searchPreviousResultTitle')
searchPrevious.className = 'jsoneditor-previous'
searchPrevious.onclick = () => {
searchBox.previous()
}
divInput.appendChild(searchPrevious)
}
/**
* Go to the next search result
* @param {boolean} [focus] If true, focus will be set to the next result
* focus is false by default.
*/
next (focus) {
if (this.results) {
let index = this.resultIndex !== null ? 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.
*/
previous (focus) {
if (this.results) {
const max = this.results.length - 1
let index = this.resultIndex !== null ? 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
*/
_setActiveResult (index, focus) {
// de-activate current active result
if (this.activeResult) {
const prevNode = this.activeResult.node
const 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
const node = this.results[this.resultIndex].node
const 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(() => {
if (focus) {
node.focus(elem)
}
})
}
/**
* Cancel any running onDelayedSearch.
* @private
*/
_clearDelay () {
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
*/
_onDelayedSearch (event) {
// execute the search after a short delay (reduces the number of
// search actions while typing in the search text box)
this._clearDelay()
const searchBox = this
this.timeout = setTimeout(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
*/
_onSearch (forceSearch) {
this._clearDelay()
const value = this.dom.search.value
const 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)
const MAX_SEARCH_RESULTS = this.results[0]
? this.results[0].node.MAX_SEARCH_RESULTS
: Infinity
// try to maintain the current active result if this is still part of the new search results
let activeResultIndex = 0
if (this.activeResult) {
for (let i = 0; i < this.results.length; i++) {
if (this.results[i].node === this.activeResult.node) {
activeResultIndex = i
break
}
}
}
this._setActiveResult(activeResultIndex, false)
// display search results
if (text !== undefined) {
const resultCount = this.results.length
if (resultCount === 0) {
this.dom.results.innerHTML = 'no&nbsp;results'
} else if (resultCount === 1) {
this.dom.results.innerHTML = '1&nbsp;result'
} else if (resultCount > MAX_SEARCH_RESULTS) {
this.dom.results.innerHTML = MAX_SEARCH_RESULTS + '+&nbsp;results'
} else {
this.dom.results.innerHTML = resultCount + '&nbsp;results'
}
} else {
this.dom.results.innerHTML = ''
}
}
}
/**
* Handle onKeyDown event in the input box
* @param {Event} event
* @private
*/
_onKeyDown (event) {
const 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
*/
_onKeyUp (event) {
const keynum = event.keyCode
if (keynum !== 27 && keynum !== 13) {
// !show and !Enter
this._onDelayedSearch(event) // For IE 9
}
}
/**
* Clear the search results
*/
clear () {
this.dom.search.value = ''
this._onSearch()
}
/**
* Refresh searchResults if there is a search value
*/
forceSearch () {
this._onSearch(true)
}
/**
* Test whether the search box value is empty
* @returns {boolean} Returns true when empty.
*/
isEmpty () {
return this.dom.search.value === ''
}
/**
* Destroy the search box
*/
destroy () {
this.editor = null
this.dom.container.removeChild(this.dom.wrapper)
this.dom = null
this.results = null
this.activeResult = null
this._clearDelay()
}
}

View File

@ -1,142 +0,0 @@
'use strict'
import { ContextMenu } from './ContextMenu'
import { translate } from './i18n'
import { addClassName, removeClassName } from './util'
/**
* Creates a component that visualize path selection in tree based editors
* @param {HTMLElement} container
* @param {HTMLElement} root
* @constructor
*/
export class TreePath {
constructor (container, root) {
if (container) {
this.root = root
this.path = document.createElement('div')
this.path.className = 'jsoneditor-treepath'
this.path.setAttribute('tabindex', 0)
this.contentMenuClicked = false
container.appendChild(this.path)
this.reset()
}
}
/**
* Reset component to initial status
*/
reset () {
this.path.innerHTML = translate('selectNode')
}
/**
* Renders the component UI according to a given path objects
* @param {Array<{name: String, childs: Array}>} pathObjs a list of path objects
*
*/
setPath (pathObjs) {
const me = this
this.path.innerHTML = ''
if (pathObjs && pathObjs.length) {
pathObjs.forEach((pathObj, idx) => {
const pathEl = document.createElement('span')
let sepEl
pathEl.className = 'jsoneditor-treepath-element'
pathEl.innerText = pathObj.name
pathEl.onclick = _onSegmentClick.bind(me, pathObj)
me.path.appendChild(pathEl)
if (pathObj.children.length) {
sepEl = document.createElement('span')
sepEl.className = 'jsoneditor-treepath-seperator'
sepEl.innerHTML = '&#9658;'
sepEl.onclick = () => {
me.contentMenuClicked = true
const items = []
pathObj.children.forEach(child => {
items.push({
text: child.name,
className: 'jsoneditor-type-modes' + (pathObjs[idx + 1] + 1 && pathObjs[idx + 1].name === child.name ? ' jsoneditor-selected' : ''),
click: _onContextMenuItemClick.bind(me, pathObj, child.name)
})
})
const menu = new ContextMenu(items)
menu.show(sepEl, me.root, true)
}
me.path.appendChild(sepEl)
}
if (idx === pathObjs.length - 1) {
const leftRectPos = (sepEl || pathEl).getBoundingClientRect().right
if (me.path.offsetWidth < leftRectPos) {
me.path.scrollLeft = leftRectPos
}
if (me.path.scrollLeft) {
const showAllBtn = document.createElement('span')
showAllBtn.className = 'jsoneditor-treepath-show-all-btn'
showAllBtn.title = 'show all path'
showAllBtn.innerHTML = '...'
showAllBtn.onclick = _onShowAllClick.bind(me, pathObjs)
me.path.insertBefore(showAllBtn, me.path.firstChild)
}
}
})
}
function _onShowAllClick (pathObjs) {
me.contentMenuClicked = false
addClassName(me.path, 'show-all')
me.path.style.width = me.path.parentNode.getBoundingClientRect().width - 10 + 'px'
me.path.onblur = () => {
if (me.contentMenuClicked) {
me.contentMenuClicked = false
me.path.focus()
return
}
removeClassName(me.path, 'show-all')
me.path.onblur = undefined
me.path.style.width = ''
me.setPath(pathObjs)
}
}
function _onSegmentClick (pathObj) {
if (this.selectionCallback) {
this.selectionCallback(pathObj)
}
}
function _onContextMenuItemClick (pathObj, selection) {
if (this.contextMenuCallback) {
this.contextMenuCallback(pathObj, selection)
}
}
}
/**
* set a callback function for selection of path section
* @param {Function} callback function to invoke when section is selected
*/
onSectionSelected (callback) {
if (typeof callback === 'function') {
this.selectionCallback = callback
}
}
/**
* set a callback function for selection of path section
* @param {Function} callback function to invoke when section is selected
*/
onContextMenuItemSelected (callback) {
if (typeof callback === 'function') {
this.contextMenuCallback = callback
}
}
}

View File

@ -1,24 +0,0 @@
let ace
if (window.ace) {
// use the already loaded instance of Ace
ace = window.ace
} else {
try {
// load Ace editor
ace = require('ace-builds/src-noconflict/ace')
// load required Ace plugins
require('ace-builds/src-noconflict/mode-json')
require('ace-builds/src-noconflict/ext-searchbox')
// embed Ace json worker
// https://github.com/ajaxorg/ace/issues/3913
const jsonWorkerDataUrl = require('../generated/worker-json-data-url')
ace.config.setModuleUrl('ace/mode/json_worker', jsonWorkerDataUrl)
} catch (err) {
// failed to load Ace (can be minimalist bundle).
// No worries, the editor will fall back to plain text if needed.
}
}
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 ***** */
window.ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], (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: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif;
line-height: 1.3;
background-color: #fff;
}
.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
}`
const dom = acequire('../lib/dom')
dom.importCssString(exports.cssText, exports.cssClass)
})

View File

@ -1,251 +0,0 @@
'use strict'
import { ContextMenu } from './ContextMenu'
import { translate } from './i18n'
import { addClassName, removeClassName } from './util'
/**
* A factory function to create an AppendNode, which depends on a Node
* @param {Node} Node
*/
export 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
const dom = this.dom
if (dom.tr) {
return dom.tr
}
this._updateEditability()
// a row for the append button
const trAppend = document.createElement('tr')
trAppend.className = 'jsoneditor-append'
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
const tdMenu = document.createElement('td')
dom.tdMenu = tdMenu
const menu = document.createElement('button')
menu.type = 'button'
menu.className = 'jsoneditor-button jsoneditor-contextmenu-button'
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')
const tdAppend = document.createElement('td')
const domText = document.createElement('div')
domText.innerHTML = '(' + translate('empty') + ')'
domText.className = 'jsoneditor-readonly'
tdAppend.appendChild(domText)
dom.td = tdAppend
dom.text = domText
this.updateDom()
return trAppend
}
/**
* Append node doesn't have a path
* @returns {null}
*/
AppendNode.prototype.getPath = () => null
/**
* Append node doesn't have an index
* @returns {null}
*/
AppendNode.prototype.getIndex = () => null
/**
* Update the HTML dom of the Node
*/
AppendNode.prototype.updateDom = function (options) {
const dom = this.dom
const tdAppend = dom.td
if (tdAppend) {
tdAppend.style.paddingLeft = (this.getLevel() * 24 + 26) + 'px'
// TODO: not so nice hard coded offset
}
const domText = dom.text
if (domText) {
domText.innerHTML = '(' + translate('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
const 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) {
const node = this
const appendSubmenu = [
{
text: translate('auto'),
className: 'jsoneditor-type-auto',
title: translate('autoType'),
click: function () {
node._onAppend('', '', 'auto')
}
},
{
text: translate('array'),
className: 'jsoneditor-type-array',
title: translate('arrayType'),
click: function () {
node._onAppend('', [])
}
},
{
text: translate('object'),
className: 'jsoneditor-type-object',
title: translate('objectType'),
click: function () {
node._onAppend('', {})
}
},
{
text: translate('string'),
className: 'jsoneditor-type-string',
title: translate('stringType'),
click: function () {
node._onAppend('', '', 'string')
}
}
]
node.addTemplates(appendSubmenu, true)
let items = [
// create append button
{
text: translate('appendText'),
title: translate('appendTitleAuto'),
submenuTitle: translate('appendSubmenuTitle'),
className: 'jsoneditor-insert',
click: function () {
node._onAppend('', '', 'auto')
},
submenu: appendSubmenu
}
]
if (this.editor.options.onCreateMenu) {
const path = node.parent.getPath()
items = this.editor.options.onCreateMenu(items, {
type: 'append',
path: path,
paths: [path]
})
}
const menu = new ContextMenu(items, { close: onClose })
menu.show(anchor, this.editor.getPopupAnchor())
}
/**
* Handle an event. The event is caught centrally by the editor
* @param {Event} event
*/
AppendNode.prototype.onEvent = function (event) {
const type = event.type
const target = event.target || event.srcElement
const dom = this.dom
// highlight the append nodes parent
const 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) {
const highlighter = this.editor.highlighter
highlighter.highlight(this.parent)
highlighter.lock()
addClassName(dom.menu, 'jsoneditor-selected')
this.showContextMenu(dom.menu, () => {
removeClassName(dom.menu, 'jsoneditor-selected')
highlighter.unlock()
highlighter.unhighlight()
})
}
if (type === 'keydown') {
this.onKeyDown(event)
}
}
return AppendNode
}

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,6 +0,0 @@
This is a copy of the Selectr project
https://github.com/Mobius1/Selectr
Reason is that the project is not maintained and has some issues
loading it via `require` in a webpack project.

File diff suppressed because it is too large Load Diff

View File

@ -1,472 +0,0 @@
/*!
* Selectr 2.4.0
* https://github.com/Mobius1/Selectr
*
* Released under the MIT license
*/
.selectr-container {
position: relative;
}
.selectr-container li {
list-style: none;
}
.selectr-hidden {
position: absolute;
overflow: hidden;
clip: rect(0px, 0px, 0px, 0px);
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0 none;
}
.selectr-visible {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
opacity: 0;
z-index: 11;
}
.selectr-desktop.multiple .selectr-visible {
display: none;
}
.selectr-desktop.multiple.native-open .selectr-visible {
top: 100%;
min-height: 200px !important;
height: auto;
opacity: 1;
display: block;
}
.selectr-container.multiple.selectr-mobile .selectr-selected {
z-index: 0;
}
.selectr-selected {
position: relative;
z-index: 1;
box-sizing: border-box;
width: 100%;
padding: 7px 28px 7px 14px;
cursor: pointer;
border: 1px solid $jse-grey;
border-radius: 3px;
background-color: $jse-white;
}
.selectr-selected::before {
position: absolute;
top: 50%;
right: 10px;
width: 0;
height: 0;
content: '';
-o-transform: rotate(0deg) translate3d(0px, -50%, 0px);
-ms-transform: rotate(0deg) translate3d(0px, -50%, 0px);
-moz-transform: rotate(0deg) translate3d(0px, -50%, 0px);
-webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);
transform: rotate(0deg) translate3d(0px, -50%, 0px);
border-width: 4px 4px 0 4px;
border-style: solid;
border-color: #6c7a86 transparent transparent;
}
.selectr-container.open .selectr-selected::before,
.selectr-container.native-open .selectr-selected::before {
border-width: 0 4px 4px 4px;
border-style: solid;
border-color: transparent transparent #6c7a86;
}
.selectr-label {
display: none;
overflow: hidden;
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
}
.selectr-placeholder {
color: #6c7a86;
}
.selectr-tags {
margin: 0;
padding: 0;
white-space: normal;
}
.has-selected .selectr-tags {
margin: 0 0 -2px;
}
.selectr-tag {
list-style: none;
position: relative;
float: left;
padding: 2px 25px 2px 8px;
margin: 0 2px 2px 0;
cursor: default;
color: $jse-white;
border: medium none;
border-radius: 10px;
background: #acb7bf none repeat scroll 0 0;
}
.selectr-container.multiple.has-selected .selectr-selected {
padding: 5px 28px 5px 5px;
}
.selectr-options-container {
position: absolute;
z-index: 10000;
top: calc(100% - 1px);
left: 0;
display: none;
box-sizing: border-box;
width: 100%;
border-width: 0 1px 1px;
border-style: solid;
border-color: transparent $jse-grey $jse-grey;
border-radius: 0 0 3px 3px;
background-color: $jse-white;
}
.selectr-container.open .selectr-options-container {
display: block;
}
.selectr-input-container {
position: relative;
display: none;
}
.selectr-clear,
.selectr-input-clear,
.selectr-tag-remove {
position: absolute;
top: 50%;
right: 22px;
width: 20px;
height: 20px;
padding: 0;
cursor: pointer;
-o-transform: translate3d(0px, -50%, 0px);
-ms-transform: translate3d(0px, -50%, 0px);
-moz-transform: translate3d(0px, -50%, 0px);
-webkit-transform: translate3d(0px, -50%, 0px);
transform: translate3d(0px, -50%, 0px);
border: medium none;
background-color: transparent;
z-index: 11;
}
.selectr-clear,
.selectr-input-clear {
display: none;
}
.selectr-container.has-selected .selectr-clear,
.selectr-input-container.active .selectr-input-clear {
display: block;
}
.selectr-selected .selectr-tag-remove {
right: 2px;
}
.selectr-clear::before,
.selectr-clear::after,
.selectr-input-clear::before,
.selectr-input-clear::after,
.selectr-tag-remove::before,
.selectr-tag-remove::after {
position: absolute;
top: 5px;
left: 9px;
width: 2px;
height: 10px;
content: ' ';
background-color: #6c7a86;
}
.selectr-tag-remove::before,
.selectr-tag-remove::after {
top: 4px;
width: 3px;
height: 12px;
background-color: $jse-white;
}
.selectr-clear:before,
.selectr-input-clear::before,
.selectr-tag-remove::before {
-o-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.selectr-clear:after,
.selectr-input-clear::after,
.selectr-tag-remove::after {
-o-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-webkit-transform: rotate(-45deg);
transform: rotate(-45deg);
}
.selectr-input-container.active,
.selectr-input-container.active .selectr-clear {
display: block;
}
.selectr-input {
top: 5px;
left: 5px;
box-sizing: border-box;
width: calc(100% - 30px);
margin: 10px 15px;
padding: 7px 30px 7px 9px;
border: 1px solid $jse-grey;
border-radius: 3px;
}
.selectr-notice {
display: none;
box-sizing: border-box;
width: 100%;
padding: 8px 16px;
border-top: 1px solid $jse-grey;
border-radius: 0 0 3px 3px;
background-color: $jse-white;
}
.selectr-container.notice .selectr-notice {
display: block;
}
.selectr-container.notice .selectr-selected {
border-radius: 3px 3px 0 0;
}
.selectr-options {
position: relative;
top: calc(100% + 2px);
display: none;
overflow-x: auto;
overflow-y: scroll;
max-height: 200px;
margin: 0;
padding: 0;
}
.selectr-container.open .selectr-options,
.selectr-container.open .selectr-input-container,
.selectr-container.notice .selectr-options-container {
display: block;
}
.selectr-option {
position: relative;
display: block;
padding: 5px 20px;
list-style: outside none none;
cursor: pointer;
font-weight: normal;
}
.selectr-options.optgroups > .selectr-option {
padding-left: 25px;
}
.selectr-optgroup {
font-weight: bold;
padding: 0;
}
.selectr-optgroup--label {
font-weight: bold;
margin-top: 10px;
padding: 5px 15px;
}
.selectr-match {
text-decoration: underline;
}
.selectr-option.selected {
background-color: #ddd;
}
.selectr-option.active {
color: $jse-white;
background-color: #5897fb;
}
.selectr-option.disabled {
opacity: 0.4;
}
.selectr-option.excluded {
display: none;
}
.selectr-container.open .selectr-selected {
border-color: $jse-grey $jse-grey transparent $jse-grey;
border-radius: 3px 3px 0 0;
}
.selectr-container.open .selectr-selected::after {
-o-transform: rotate(180deg) translate3d(0px, 50%, 0px);
-ms-transform: rotate(180deg) translate3d(0px, 50%, 0px);
-moz-transform: rotate(180deg) translate3d(0px, 50%, 0px);
-webkit-transform: rotate(180deg) translate3d(0px, 50%, 0px);
transform: rotate(180deg) translate3d(0px, 50%, 0px);
}
.selectr-disabled {
opacity: .6;
}
.selectr-empty,
.has-selected .selectr-placeholder {
display: none;
}
.has-selected .selectr-label {
display: block;
}
/* TAGGABLE */
.taggable .selectr-selected {
padding: 4px 28px 4px 4px;
}
.taggable .selectr-selected::after {
display: table;
content: " ";
clear: both;
}
.taggable .selectr-label {
width: auto;
}
.taggable .selectr-tags {
float: left;
display: block;
}
.taggable .selectr-placeholder {
display: none;
}
.input-tag {
float: left;
min-width: 90px;
width: auto;
}
.selectr-tag-input {
border: medium none;
padding: 3px 10px;
width: 100%;
font-family: inherit;
font-weight: inherit;
font-size: inherit;
}
.selectr-input-container.loading::after {
position: absolute;
top: 50%;
right: 20px;
width: 20px;
height: 20px;
content: '';
-o-transform: translate3d(0px, -50%, 0px);
-ms-transform: translate3d(0px, -50%, 0px);
-moz-transform: translate3d(0px, -50%, 0px);
-webkit-transform: translate3d(0px, -50%, 0px);
transform: translate3d(0px, -50%, 0px);
-o-transform-origin: 50% 0 0;
-ms-transform-origin: 50% 0 0;
-moz-transform-origin: 50% 0 0;
-webkit-transform-origin: 50% 0 0;
transform-origin: 50% 0 0;
-moz-animation: 500ms linear 0s normal forwards infinite running spin;
-webkit-animation: 500ms linear 0s normal forwards infinite running spin;
animation: 500ms linear 0s normal forwards infinite running spin;
border-width: 3px;
border-style: solid;
border-color: #aaa #ddd #ddd;
border-radius: 50%;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);
transform: rotate(0deg) translate3d(0px, -50%, 0px);
}
100% {
-webkit-transform: rotate(360deg) translate3d(0px, -50%, 0px);
transform: rotate(360deg) translate3d(0px, -50%, 0px);
}
}
@keyframes spin {
0% {
-webkit-transform: rotate(0deg) translate3d(0px, -50%, 0px);
transform: rotate(0deg) translate3d(0px, -50%, 0px);
}
100% {
-webkit-transform: rotate(360deg) translate3d(0px, -50%, 0px);
transform: rotate(360deg) translate3d(0px, -50%, 0px);
}
}
.selectr-container.open.inverted .selectr-selected {
border-color: transparent $jse-grey $jse-grey;
border-radius: 0 0 3px 3px;
}
.selectr-container.inverted .selectr-options-container {
border-width: 1px 1px 0;
border-color: $jse-grey $jse-grey transparent;
border-radius: 3px 3px 0 0;
background-color: $jse-white;
}
.selectr-container.inverted .selectr-options-container {
top: auto;
bottom: calc(100% - 1px);
}
.selectr-container ::-webkit-input-placeholder {
color: #6c7a86;
opacity: 1;
}
.selectr-container ::-moz-placeholder {
color: #6c7a86;
opacity: 1;
}
.selectr-container :-ms-input-placeholder {
color: #6c7a86;
opacity: 1;
}
.selectr-container ::placeholder {
color: #6c7a86;
opacity: 1;
}

View File

@ -1,381 +0,0 @@
'use strict'
const defaultFilterFunction = {
start: function (token, match, config) {
return match.indexOf(token) === 0
},
contain: function (token, match, config) {
return match.indexOf(token) > -1
}
}
export function autocomplete (config) {
config = config || {}
config.filter = config.filter || 'start'
config.trigger = config.trigger || 'keydown'
config.confirmKeys = config.confirmKeys || [39, 35, 9] // right, end, tab
config.caseSensitive = config.caseSensitive || false // autocomplete case sensitive
let fontSize = ''
let fontFamily = ''
const wrapper = document.createElement('div')
wrapper.style.position = 'relative'
wrapper.style.outline = '0'
wrapper.style.border = '0'
wrapper.style.margin = '0'
wrapper.style.padding = '0'
const dropDown = document.createElement('div')
dropDown.className = 'autocomplete dropdown'
dropDown.style.position = 'absolute'
dropDown.style.visibility = 'hidden'
let spacer
let leftSide // <-- it will contain the leftSide part of the textfield (the bit that was already autocompleted)
const createDropDownController = (elem, rs) => {
let rows = []
let ix = 0
let oldIndex = -1
// TODO: move this styling in JS to SCSS
const onMouseOver = function () { this.style.backgroundColor = '#ddd' }
const onMouseOut = function () { this.style.backgroundColor = '' }
const onMouseDown = function () { p.hide(); p.onmouseselection(this.__hint, p.rs) }
var p = {
rs: rs,
hide: function () {
elem.style.visibility = 'hidden'
// rs.hideDropDown();
},
refresh: function (token, array) {
elem.style.visibility = 'hidden'
ix = 0
elem.innerHTML = ''
const vph = (window.innerHeight || document.documentElement.clientHeight)
const rect = elem.parentNode.getBoundingClientRect()
const distanceToTop = rect.top - 6 // heuristic give 6px
const distanceToBottom = vph - rect.bottom - 6 // distance from the browser border.
rows = []
const filterFn = typeof config.filter === 'function' ? config.filter : defaultFilterFunction[config.filter]
const filtered = !filterFn ? [] : array.filter(match => filterFn(config.caseSensitive ? token : token.toLowerCase(), config.caseSensitive ? match : match.toLowerCase(), config))
rows = filtered.map(row => {
const divRow = document.createElement('div')
divRow.className = 'item'
// divRow.style.color = config.color;
divRow.onmouseover = onMouseOver
divRow.onmouseout = onMouseOut
divRow.onmousedown = onMouseDown
divRow.__hint = row
divRow.innerHTML = row.substring(0, token.length) + '<b>' + row.substring(token.length) + '</b>'
elem.appendChild(divRow)
return divRow
})
if (rows.length === 0) {
return // nothing to show.
}
if (rows.length === 1 && ((token.toLowerCase() === rows[0].__hint.toLowerCase() && !config.caseSensitive) ||
(token === rows[0].__hint && config.caseSensitive))) {
return // do not show the dropDown if it has only one element which matches what we have just displayed.
}
if (rows.length < 2) return
p.highlight(0)
if (distanceToTop > distanceToBottom * 3) { // Heuristic (only when the distance to the to top is 4 times more than distance to the bottom
elem.style.maxHeight = distanceToTop + 'px' // we display the dropDown on the top of the input text
elem.style.top = ''
elem.style.bottom = '100%'
} else {
elem.style.top = '100%'
elem.style.bottom = ''
elem.style.maxHeight = distanceToBottom + 'px'
}
elem.style.visibility = 'visible'
},
highlight: function (index) {
if (oldIndex !== -1 && rows[oldIndex]) {
rows[oldIndex].className = 'item'
}
rows[index].className = 'item hover'
oldIndex = index
},
move: function (step) { // moves the selection either up or down (unless it's not possible) step is either +1 or -1.
if (elem.style.visibility === 'hidden') return '' // nothing to move if there is no dropDown. (this happens if the user hits escape and then down or up)
if (ix + step === -1 || ix + step === rows.length) return rows[ix].__hint // NO CIRCULAR SCROLLING.
ix += step
p.highlight(ix)
return rows[ix].__hint// txtShadow.value = uRows[uIndex].__hint ;
},
onmouseselection: function () { } // it will be overwritten.
}
return p
}
function setEndOfContenteditable (contentEditableElement) {
let range, selection
if (document.createRange) {
// Firefox, Chrome, Opera, Safari, IE 9+
range = document.createRange()// Create a range (a range is a like the selection but invisible)
range.selectNodeContents(contentEditableElement)// Select the entire contents of the element with the range
range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start
selection = window.getSelection()// get the selection object (allows you to change selection)
selection.removeAllRanges()// remove any selections already made
selection.addRange(range)// make the range you have just created the visible selection
} else if (document.selection) {
// IE 8 and lower
range = document.body.createTextRange()// Create a range (a range is a like the selection but invisible)
range.moveToElementText(contentEditableElement)// Select the entire contents of the element with the range
range.collapse(false)// collapse the range to the end point. false means collapse to end rather than the start
range.select()// Select the range (make it the visible selection
}
}
function calculateWidthForText (text) {
if (spacer === undefined) { // on first call only.
spacer = document.createElement('span')
spacer.style.visibility = 'hidden'
spacer.style.position = 'fixed'
spacer.style.outline = '0'
spacer.style.margin = '0'
spacer.style.padding = '0'
spacer.style.border = '0'
spacer.style.left = '0'
spacer.style.whiteSpace = 'pre'
spacer.style.fontSize = fontSize
spacer.style.fontFamily = fontFamily
spacer.style.fontWeight = 'normal'
document.body.appendChild(spacer)
}
// Used to encode an HTML string into a plain text.
// taken from http://stackoverflow.com/questions/1219860/javascript-jquery-html-encoding
spacer.innerHTML = String(text).replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
return spacer.getBoundingClientRect().right
}
const rs = {
onArrowDown: function () { }, // defaults to no action.
onArrowUp: function () { }, // defaults to no action.
onEnter: function () { }, // defaults to no action.
onTab: function () { }, // defaults to no action.
startFrom: 0,
options: [],
element: null,
elementHint: null,
elementStyle: null,
wrapper: wrapper, // Only to allow easy access to the HTML elements to the final user (possibly for minor customizations)
show: function (element, startPos, options) {
this.startFrom = startPos
this.wrapper.remove()
if (this.elementHint) {
this.elementHint.remove()
this.elementHint = null
}
if (fontSize === '') {
fontSize = window.getComputedStyle(element).getPropertyValue('font-size')
}
if (fontFamily === '') {
fontFamily = window.getComputedStyle(element).getPropertyValue('font-family')
}
dropDown.style.marginLeft = '0'
dropDown.style.marginTop = element.getBoundingClientRect().height + 'px'
this.options = options.map(String)
if (this.element !== element) {
this.element = element
this.elementStyle = {
zIndex: this.element.style.zIndex,
position: this.element.style.position,
backgroundColor: this.element.style.backgroundColor,
borderColor: this.element.style.borderColor
}
}
this.element.style.zIndex = 3
this.element.style.position = 'relative'
this.element.style.backgroundColor = 'transparent'
this.element.style.borderColor = 'transparent'
this.elementHint = element.cloneNode()
this.elementHint.className = 'autocomplete hint'
this.elementHint.style.zIndex = 2
this.elementHint.style.position = 'absolute'
this.elementHint.onfocus = () => { this.element.focus() }
if (this.element.addEventListener) {
this.element.removeEventListener('keydown', keyDownHandler)
this.element.addEventListener('keydown', keyDownHandler, false)
this.element.removeEventListener('blur', onBlurHandler)
this.element.addEventListener('blur', onBlurHandler, false)
}
wrapper.appendChild(this.elementHint)
wrapper.appendChild(dropDown)
element.parentElement.appendChild(wrapper)
this.repaint(element)
},
setText: function (text) {
this.element.innerText = text
},
getText: function () {
return this.element.innerText
},
hideDropDown: function () {
this.wrapper.remove()
if (this.elementHint) {
this.elementHint.remove()
this.elementHint = null
dropDownController.hide()
this.element.style.zIndex = this.elementStyle.zIndex
this.element.style.position = this.elementStyle.position
this.element.style.backgroundColor = this.elementStyle.backgroundColor
this.element.style.borderColor = this.elementStyle.borderColor
}
},
repaint: function (element) {
let text = element.innerText
text = text.replace('\n', '')
const optionsLength = this.options.length
// breaking text in leftSide and token.
const token = text.substring(this.startFrom)
leftSide = text.substring(0, this.startFrom)
for (let i = 0; i < optionsLength; i++) {
const opt = this.options[i]
if ((!config.caseSensitive && opt.toLowerCase().indexOf(token.toLowerCase()) === 0) ||
(config.caseSensitive && opt.indexOf(token) === 0)) { // <-- how about upperCase vs. lowercase
this.elementHint.innerText = leftSide + token + opt.substring(token.length)
this.elementHint.realInnerText = leftSide + opt
break
}
}
// moving the dropDown and refreshing it.
dropDown.style.left = calculateWidthForText(leftSide) + 'px'
dropDownController.refresh(token, this.options)
this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + 10 + 'px'
const wasDropDownHidden = (dropDown.style.visibility === 'hidden')
if (!wasDropDownHidden) { this.elementHint.style.width = calculateWidthForText(this.elementHint.innerText) + dropDown.clientWidth + 'px' }
}
}
var dropDownController = createDropDownController(dropDown, rs)
var keyDownHandler = function (e) {
// console.log("Keydown:" + e.keyCode);
e = e || window.event
const keyCode = e.keyCode
if (this.elementHint == null) return
if (keyCode === 33) { return } // page up (do nothing)
if (keyCode === 34) { return } // page down (do nothing);
if (keyCode === 27) { // escape
rs.hideDropDown()
rs.element.focus()
e.preventDefault()
e.stopPropagation()
return
}
let text = this.element.innerText
text = text.replace('\n', '')
if (config.confirmKeys.indexOf(keyCode) >= 0) { // (autocomplete triggered)
if (keyCode === 9) {
if (this.elementHint.innerText.length === 0) {
rs.onTab()
}
}
if (this.elementHint.innerText.length > 0) { // if there is a hint
if (this.element.innerText !== this.elementHint.realInnerText) {
this.element.innerText = this.elementHint.realInnerText
rs.hideDropDown()
setEndOfContenteditable(this.element)
if (keyCode === 9) {
rs.element.focus()
e.preventDefault()
e.stopPropagation()
}
}
}
return
}
if (keyCode === 13) { // enter (autocomplete triggered)
if (this.elementHint.innerText.length === 0) { // if there is a hint
rs.onEnter()
} else {
const wasDropDownHidden = (dropDown.style.visibility === 'hidden')
dropDownController.hide()
if (wasDropDownHidden) {
rs.hideDropDown()
rs.element.focus()
rs.onEnter()
return
}
this.element.innerText = this.elementHint.realInnerText
rs.hideDropDown()
setEndOfContenteditable(this.element)
e.preventDefault()
e.stopPropagation()
}
return
}
if (keyCode === 40) { // down
const token = text.substring(this.startFrom)
const m = dropDownController.move(+1)
if (m === '') { rs.onArrowDown() }
this.elementHint.innerText = leftSide + token + m.substring(token.length)
this.elementHint.realInnerText = leftSide + m
e.preventDefault()
e.stopPropagation()
return
}
if (keyCode === 38) { // up
const token = text.substring(this.startFrom)
const m = dropDownController.move(-1)
if (m === '') { rs.onArrowUp() }
this.elementHint.innerText = leftSide + token + m.substring(token.length)
this.elementHint.realInnerText = leftSide + m
e.preventDefault()
e.stopPropagation()
}
}.bind(rs)
var onBlurHandler = e => {
rs.hideDropDown()
// console.log("Lost focus.");
}
dropDownController.onmouseselection = (text, rs) => {
rs.element.innerText = rs.elementHint.innerText = leftSide + text
rs.hideDropDown()
window.setTimeout(() => {
rs.element.focus()
setEndOfContenteditable(rs.element)
}, 1)
}
return rs
}

View File

@ -1,5 +0,0 @@
export const DEFAULT_MODAL_ANCHOR = document.body
export const SIZE_LARGE = 10 * 1024 * 1024 // 10 MB
export const MAX_PREVIEW_CHARACTERS = 20000
export const PREVIEW_HISTORY_LIMIT = 2 * 1024 * 1024 * 1024 // 2 GB

View File

@ -1,99 +0,0 @@
import { isChildOf, removeEventListener, addEventListener } from './util'
/**
* Create an anchor element absolutely positioned in the `parent`
* element.
* @param {HTMLElement} anchor
* @param {HTMLElement} parent
* @param {function(HTMLElement)} [onDestroy] Callback when the anchor is destroyed
* @param {boolean} [destroyOnMouseOut=false] If true, anchor will be removed on mouse out
* @returns {HTMLElement}
*/
export function createAbsoluteAnchor (anchor, parent, onDestroy, destroyOnMouseOut = false) {
const root = getRootNode(anchor)
const eventListeners = {}
const anchorRect = anchor.getBoundingClientRect()
const parentRect = parent.getBoundingClientRect()
const absoluteAnchor = document.createElement('div')
absoluteAnchor.className = 'jsoneditor-anchor'
absoluteAnchor.style.position = 'absolute'
absoluteAnchor.style.left = (anchorRect.left - parentRect.left) + 'px'
absoluteAnchor.style.top = (anchorRect.top - parentRect.top) + 'px'
absoluteAnchor.style.width = (anchorRect.width - 2) + 'px'
absoluteAnchor.style.height = (anchorRect.height - 2) + 'px'
absoluteAnchor.style.boxSizing = 'border-box'
parent.appendChild(absoluteAnchor)
function destroy () {
// remove temporary absolutely positioned anchor
if (absoluteAnchor && absoluteAnchor.parentNode) {
absoluteAnchor.parentNode.removeChild(absoluteAnchor)
// remove all event listeners
// all event listeners are supposed to be attached to document.
for (const name in eventListeners) {
if (hasOwnProperty(eventListeners, name)) {
const fn = eventListeners[name]
if (fn) {
removeEventListener(root, name, fn)
}
delete eventListeners[name]
}
}
if (typeof onDestroy === 'function') {
onDestroy(anchor)
}
}
}
function isOutside (target) {
return (target !== absoluteAnchor) && !isChildOf(target, absoluteAnchor)
}
// create and attach event listeners
function destroyIfOutside (event) {
if (isOutside(event.target)) {
destroy()
}
}
eventListeners.mousedown = addEventListener(root, 'mousedown', destroyIfOutside)
eventListeners.mousewheel = addEventListener(root, 'mousewheel', destroyIfOutside)
if (destroyOnMouseOut) {
let destroyTimer = null
absoluteAnchor.onmouseover = () => {
clearTimeout(destroyTimer)
destroyTimer = null
}
absoluteAnchor.onmouseout = () => {
if (!destroyTimer) {
destroyTimer = setTimeout(destroy, 200)
}
}
}
absoluteAnchor.destroy = destroy
return absoluteAnchor
}
/**
* Node.getRootNode shim
* @param {HTMLElement} node node to check
* @return {HTMLElement} node's rootNode or `window` if there is ShadowDOM is not supported.
*/
function getRootNode (node) {
return (typeof node.getRootNode === 'function')
? node.getRootNode()
: window
}
function hasOwnProperty (object, key) {
return Object.prototype.hasOwnProperty.call(object, key)
}

View File

@ -1 +0,0 @@
*

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-2020 Jos de Jong, http://jsoneditoronline.org
*
* @author Jos de Jong, <wjosdejong@gmail.com>
* @version @@version
* @date @@date
*/

View File

@ -1,598 +0,0 @@
'use strict'
/* eslint-disable no-template-curly-in-string */
import './polyfills'
const _locales = ['en', 'pt-BR', 'zh-CN', 'tr', 'ja', 'fr-FR']
const _defs = {
en: {
array: 'Array',
auto: 'Auto',
appendText: 'Append',
appendTitle: 'Append a new field with type \'auto\' after this field (Ctrl+Shift+Ins)',
appendSubmenuTitle: 'Select the type of the field to be appended',
appendTitleAuto: 'Append a new field with type \'auto\' (Ctrl+Shift+Ins)',
ascending: 'Ascending',
ascendingTitle: 'Sort the childs of this ${type} in ascending order',
actionsMenu: 'Click to open the actions menu (Ctrl+M)',
cannotParseFieldError: 'Cannot parse field into JSON',
cannotParseValueError: 'Cannot parse value into JSON',
collapseAll: 'Collapse all fields',
compactTitle: 'Compact JSON data, remove all whitespaces (Ctrl+Shift+\\)',
descending: 'Descending',
descendingTitle: 'Sort the childs of this ${type} in descending order',
drag: 'Drag to move this field (Alt+Shift+Arrows)',
duplicateKey: 'duplicate key',
duplicateText: 'Duplicate',
duplicateTitle: 'Duplicate selected fields (Ctrl+D)',
duplicateField: 'Duplicate this field (Ctrl+D)',
duplicateFieldError: 'Duplicate field name',
empty: 'empty',
expandAll: 'Expand all fields',
expandTitle: 'Click to expand/collapse this field (Ctrl+E). \n' +
'Ctrl+Click to expand/collapse including all childs.',
formatTitle: 'Format JSON data, with proper indentation and line feeds (Ctrl+\\)',
insert: 'Insert',
insertTitle: 'Insert a new field with type \'auto\' before this field (Ctrl+Ins)',
insertSub: 'Select the type of the field to be inserted',
object: 'Object',
ok: 'Ok',
redo: 'Redo (Ctrl+Shift+Z)',
removeText: 'Remove',
removeTitle: 'Remove selected fields (Ctrl+Del)',
removeField: 'Remove this field (Ctrl+Del)',
repairTitle: 'Repair JSON: fix quotes and escape characters, remove comments and JSONP notation, turn JavaScript objects into JSON.',
searchTitle: 'Search fields and values',
searchNextResultTitle: 'Next result (Enter)',
searchPreviousResultTitle: 'Previous result (Shift + Enter)',
selectNode: 'Select a node...',
showAll: 'show all',
showMore: 'show more',
showMoreStatus: 'displaying ${visibleChilds} of ${totalChilds} items.',
sort: 'Sort',
sortTitle: 'Sort the childs of this ${type}',
sortTitleShort: 'Sort contents',
sortFieldLabel: 'Field:',
sortDirectionLabel: 'Direction:',
sortFieldTitle: 'Select the nested field by which to sort the array or object',
sortAscending: 'Ascending',
sortAscendingTitle: 'Sort the selected field in ascending order',
sortDescending: 'Descending',
sortDescendingTitle: 'Sort the selected field in descending order',
string: 'String',
transform: 'Transform',
transformTitle: 'Filter, sort, or transform the childs of this ${type}',
transformTitleShort: 'Filter, sort, or transform contents',
extract: 'Extract',
extractTitle: 'Extract this ${type}',
transformQueryTitle: 'Enter a JMESPath query',
transformWizardLabel: 'Wizard',
transformWizardFilter: 'Filter',
transformWizardSortBy: 'Sort by',
transformWizardSelectFields: 'Select fields',
transformQueryLabel: 'Query',
transformPreviewLabel: 'Preview',
type: 'Type',
typeTitle: 'Change the type of this field',
openUrl: 'Ctrl+Click or Ctrl+Enter to open url in new window',
undo: 'Undo last action (Ctrl+Z)',
validationCannotMove: 'Cannot move a field into a child of itself',
autoType: 'Field type "auto". ' +
'The field type is automatically determined from the value ' +
'and can be a string, number, boolean, or null.',
objectType: 'Field type "object". ' +
'An object contains an unordered set of key/value pairs.',
arrayType: 'Field type "array". ' +
'An array contains an ordered collection of values.',
stringType: 'Field type "string". ' +
'Field type is not determined from the value, ' +
'but always returned as string.',
modeEditorTitle: 'Switch Editor Mode',
modeCodeText: 'Code',
modeCodeTitle: 'Switch to code highlighter',
modeFormText: 'Form',
modeFormTitle: 'Switch to form editor',
modeTextText: 'Text',
modeTextTitle: 'Switch to plain text editor',
modeTreeText: 'Tree',
modeTreeTitle: 'Switch to tree editor',
modeViewText: 'View',
modeViewTitle: 'Switch to tree view',
modePreviewText: 'Preview',
modePreviewTitle: 'Switch to preview mode',
examples: 'Examples',
default: 'Default'
},
'zh-CN': {
array: '数组',
auto: '自动',
appendText: '追加',
appendTitle: '在此字段后追加一个类型为“auto”的新字段 (Ctrl+Shift+Ins)',
appendSubmenuTitle: '选择要追加的字段类型',
appendTitleAuto: '追加类型为“auto”的新字段 (Ctrl+Shift+Ins)',
ascending: '升序',
ascendingTitle: '升序排列${type}的子节点',
actionsMenu: '点击打开动作菜单(Ctrl+M)',
cannotParseFieldError: '无法将字段解析为JSON',
cannotParseValueError: '无法将值解析为JSON',
collapseAll: '缩进所有字段',
compactTitle: '压缩JSON数据删除所有空格 (Ctrl+Shift+\\)',
descending: '降序',
descendingTitle: '降序排列${type}的子节点',
drag: '拖拽移动该节点(Alt+Shift+Arrows)',
duplicateKey: '重复键',
duplicateText: '复制',
duplicateTitle: '复制选中字段(Ctrl+D)',
duplicateField: '复制该字段(Ctrl+D)',
duplicateFieldError: '重复的字段名称',
empty: '清空',
expandAll: '展开所有字段',
expandTitle: '点击 展开/收缩 该字段(Ctrl+E). \n' +
'Ctrl+Click 展开/收缩 包含所有子节点.',
formatTitle: '使用适当的缩进和换行符格式化JSON数据 (Ctrl+\\)',
insert: '插入',
insertTitle: '在此字段前插入类型为“auto”的新字段 (Ctrl+Ins)',
insertSub: '选择要插入的字段类型',
object: '对象',
ok: 'Ok',
redo: '重做 (Ctrl+Shift+Z)',
removeText: '移除',
removeTitle: '移除选中字段 (Ctrl+Del)',
removeField: '移除该字段 (Ctrl+Del)',
repairTitle: '修复JSON修复引号和转义符删除注释和JSONP表示法将JavaScript对象转换为JSON。',
selectNode: '选择一个节点...',
showAll: '展示全部',
showMore: '展示更多',
showMoreStatus: '显示${totalChilds}的${visibleChilds}项目.',
sort: '排序',
sortTitle: '排序${type}的子节点',
sortTitleShort: '内容排序',
sortFieldLabel: '字段:',
sortDirectionLabel: '方向:',
sortFieldTitle: '选择用于对数组或对象排序的嵌套字段',
sortAscending: '升序排序',
sortAscendingTitle: '按照该字段升序排序',
sortDescending: '降序排序',
sortDescendingTitle: '按照该字段降序排序',
string: '字符串',
transform: '变换',
transformTitle: '筛选,排序,或者转换${type}的子节点',
transformTitleShort: '筛选,排序,或者转换内容',
extract: '提取',
extractTitle: '提取这个 ${type}',
transformQueryTitle: '输入JMESPath查询',
transformWizardLabel: '向导',
transformWizardFilter: '筛选',
transformWizardSortBy: '排序',
transformWizardSelectFields: '选择字段',
transformQueryLabel: '查询',
transformPreviewLabel: '预览',
type: '类型',
typeTitle: '更改字段类型',
openUrl: 'Ctrl+Click 或者 Ctrl+Enter 在新窗口打开链接',
undo: '撤销上次动作 (Ctrl+Z)',
validationCannotMove: '无法将字段移入其子节点',
autoType: '字段类型 "auto". ' +
'字段类型由值自动确定 ' +
'可以为 stringnumberboolean或者 null.',
objectType: '字段类型 "object". ' +
'对象包含一组无序的键/值对.',
arrayType: '字段类型 "array". ' +
'数组包含值的有序集合.',
stringType: '字段类型 "string". ' +
'字段类型由值自动确定,' +
'但始终作为字符串返回.',
modeCodeText: '代码',
modeCodeTitle: '切换至代码高亮',
modeFormText: '表单',
modeFormTitle: '切换至表单编辑',
modeTextText: '文本',
modeTextTitle: '切换至文本编辑',
modeTreeText: '树',
modeTreeTitle: '切换至树编辑',
modeViewText: '视图',
modeViewTitle: '切换至树视图',
modePreviewText: '预览',
modePreviewTitle: '切换至预览模式',
examples: '例子',
default: '缺省'
},
'pt-BR': {
array: 'Lista',
auto: 'Automatico',
appendText: 'Adicionar',
appendTitle: 'Adicionar novo campo com tipo \'auto\' depois deste campo (Ctrl+Shift+Ins)',
appendSubmenuTitle: 'Selecione o tipo do campo a ser adicionado',
appendTitleAuto: 'Adicionar novo campo com tipo \'auto\' (Ctrl+Shift+Ins)',
ascending: 'Ascendente',
ascendingTitle: 'Organizar filhor do tipo ${type} em crescente',
actionsMenu: 'Clique para abrir o menu de ações (Ctrl+M)',
cannotParseFieldError: 'Não é possível analisar o campo no JSON',
cannotParseValueError: 'Não é possível analisar o valor em JSON',
collapseAll: 'Fechar todos campos',
compactTitle: 'Dados JSON compactos, remova todos os espaços em branco (Ctrl+Shift+\\)',
descending: 'Descendente',
descendingTitle: 'Organizar o filhos do tipo ${type} em decrescente',
duplicateKey: 'chave duplicada',
drag: 'Arraste para mover este campo (Alt+Shift+Arrows)',
duplicateText: 'Duplicar',
duplicateTitle: 'Duplicar campos selecionados (Ctrl+D)',
duplicateField: 'Duplicar este campo (Ctrl+D)',
duplicateFieldError: 'Nome do campo duplicado',
empty: 'vazio',
expandAll: 'Expandir todos campos',
expandTitle: 'Clique para expandir/encolher este campo (Ctrl+E). \n' +
'Ctrl+Click para expandir/encolher incluindo todos os filhos.',
formatTitle: 'Formate dados JSON, com recuo e feeds de linha adequados (Ctrl+\\)',
insert: 'Inserir',
insertTitle: 'Inserir um novo campo do tipo \'auto\' antes deste campo (Ctrl+Ins)',
insertSub: 'Selecionar o tipo de campo a ser inserido',
object: 'Objeto',
ok: 'Ok',
redo: 'Refazer (Ctrl+Shift+Z)',
removeText: 'Remover',
removeTitle: 'Remover campos selecionados (Ctrl+Del)',
removeField: 'Remover este campo (Ctrl+Del)',
repairTitle: 'Repare JSON: corrija aspas e caracteres de escape, remova comentários e notação JSONP, transforme objetos JavaScript em JSON.',
selectNode: 'Selecione um nódulo...',
showAll: 'mostrar todos',
showMore: 'mostrar mais',
showMoreStatus: 'exibindo ${visibleChilds} de ${totalChilds} itens.',
sort: 'Organizar',
sortTitle: 'Organizar os filhos deste ${type}',
sortTitleShort: 'Organizar os filhos',
sortFieldLabel: 'Campo:',
sortDirectionLabel: 'Direção:',
sortFieldTitle: 'Selecione um campo filho pelo qual ordenar o array ou objeto',
sortAscending: 'Ascendente',
sortAscendingTitle: 'Ordenar o campo selecionado por ordem ascendente',
sortDescending: 'Descendente',
sortDescendingTitle: 'Ordenar o campo selecionado por ordem descendente',
string: 'Texto',
transform: 'Transformar',
transformTitle: 'Filtrar, ordenar ou transformar os filhos deste ${type}',
transformTitleShort: 'Filtrar, ordenar ou transformar conteúdos',
transformQueryTitle: 'Insira uma expressão JMESPath',
transformWizardLabel: 'Assistente',
transformWizardFilter: 'Filtro',
transformWizardSortBy: 'Ordenar por',
transformWizardSelectFields: 'Selecionar campos',
transformQueryLabel: 'Expressão',
transformPreviewLabel: 'Visualizar',
type: 'Tipo',
typeTitle: 'Mudar o tipo deste campo',
openUrl: 'Ctrl+Click ou Ctrl+Enter para abrir link em nova janela',
undo: 'Desfazer último ação (Ctrl+Z)',
validationCannotMove: 'Não pode mover um campo como filho dele mesmo',
autoType: 'Campo do tipo "auto". ' +
'O tipo do campo é determinao automaticamente a partir do seu valor ' +
'e pode ser texto, número, verdade/falso ou nulo.',
objectType: 'Campo do tipo "objeto". ' +
'Um objeto contém uma lista de pares com chave e valor.',
arrayType: 'Campo do tipo "lista". ' +
'Uma lista contem uma coleção de valores ordenados.',
stringType: 'Campo do tipo "string". ' +
'Campo do tipo nao é determinado através do seu valor, ' +
'mas sempre retornara um texto.',
examples: 'Exemplos',
default: 'Revelia'
},
tr: {
array: 'Dizin',
auto: 'Otomatik',
appendText: 'Ekle',
appendTitle: 'Bu alanın altına \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)',
appendSubmenuTitle: 'Eklenecek alanın tipini seç',
appendTitleAuto: '\'Otomatik\' tipinde yeni bir alan ekle (Ctrl+Shift+Ins)',
ascending: 'Artan',
ascendingTitle: '${type}\'ın alt tiplerini artan düzende sırala',
actionsMenu: 'Aksiyon menüsünü açmak için tıklayın (Ctrl+M)',
collapseAll: 'Tüm alanları kapat',
descending: 'Azalan',
descendingTitle: '${type}\'ın alt tiplerini azalan düzende sırala',
drag: 'Bu alanı taşımak için sürükleyin (Alt+Shift+Arrows)',
duplicateKey: 'Var olan anahtar',
duplicateText: 'Aşağıya kopyala',
duplicateTitle: 'Seçili alanlardan bir daha oluştur (Ctrl+D)',
duplicateField: 'Bu alandan bir daha oluştur (Ctrl+D)',
duplicateFieldError: 'Duplicate field name',
cannotParseFieldError: 'Alan JSON\'a ayrıştırılamıyor',
cannotParseValueError: 'JSON\'a değer ayrıştırılamıyor',
empty: 'boş',
expandAll: 'Tüm alanları aç',
expandTitle: 'Bu alanı açmak/kapatmak için tıkla (Ctrl+E). \n' +
'Alt alanlarda dahil tüm alanları açmak için Ctrl+Click ',
insert: 'Ekle',
insertTitle: 'Bu alanın üstüne \'otomatik\' tipinde yeni bir alan ekle (Ctrl+Ins)',
insertSub: 'Araya eklenecek alanın tipini seç',
object: 'Nesne',
ok: 'Tamam',
redo: 'Yeniden yap (Ctrl+Shift+Z)',
removeText: 'Kaldır',
removeTitle: 'Seçilen alanları kaldır (Ctrl+Del)',
removeField: 'Bu alanı kaldır (Ctrl+Del)',
selectNode: 'Bir nesne seç...',
showAll: 'tümünü göster',
showMore: 'daha fazla göster',
showMoreStatus: '${totalChilds} alanın ${visibleChilds} alt alanları gösteriliyor',
sort: 'Sırala',
sortTitle: '${type}\'ın alt alanlarını sırala',
sortTitleShort: 'İçerikleri sırala',
sortFieldLabel: 'Alan:',
sortDirectionLabel: 'Yön:',
sortFieldTitle: 'Diziyi veya nesneyi sıralamak için iç içe geçmiş alanı seçin',
sortAscending: 'Artan',
sortAscendingTitle: 'Seçili alanı artan düzende sırala',
sortDescending: 'Azalan',
sortDescendingTitle: 'Seçili alanı azalan düzende sırala',
string: 'Karakter Dizisi',
transform: 'Dönüştür',
transformTitle: '${type}\'ın alt alanlarını filtrele, sırala veya dönüştür',
transformTitleShort: 'İçerikleri filterele, sırala veya dönüştür',
transformQueryTitle: 'JMESPath sorgusu gir',
transformWizardLabel: 'Sihirbaz',
transformWizardFilter: 'Filtre',
transformWizardSortBy: 'Sırala',
transformWizardSelectFields: 'Alanları seç',
transformQueryLabel: 'Sorgu',
transformPreviewLabel: 'Önizleme',
type: 'Tip',
typeTitle: 'Bu alanın tipini değiştir',
openUrl: 'URL\'i yeni bir pencerede açmak için Ctrl+Click veya Ctrl+Enter',
undo: 'Son değişikliği geri al (Ctrl+Z)',
validationCannotMove: 'Alt alan olarak taşınamıyor',
autoType: 'Alan tipi "otomatik". ' +
'Alan türü otomatik olarak değerden belirlenir' +
've bir dize, sayı, boolean veya null olabilir.',
objectType: 'Alan tipi "nesne". ' +
'Bir nesne, sıralanmamış bir anahtar / değer çifti kümesi içerir.',
arrayType: 'Alan tipi "dizi". ' +
'Bir dizi, düzenli değerler koleksiyonu içerir.',
stringType: 'Alan tipi "karakter dizisi". ' +
'Alan türü değerden belirlenmez,' +
'ancak her zaman karakter dizisi olarak döndürülür.',
modeCodeText: 'Kod',
modeCodeTitle: 'Kod vurgulayıcıya geç',
modeFormText: 'Form',
modeFormTitle: 'Form düzenleyiciye geç',
modeTextText: 'Metin',
modeTextTitle: 'Düz metin düzenleyiciye geç',
modeTreeText: 'Ağaç',
modeTreeTitle: 'Ağaç düzenleyiciye geç',
modeViewText: 'Görünüm',
modeViewTitle: 'Ağaç görünümüne geç',
examples: 'Örnekler',
default: 'Varsayılan'
},
ja: {
array: '配列',
auto: 'オート',
appendText: '追加',
appendTitle: '次のフィールドに"オート"のフィールドを追加 (Ctrl+Shift+Ins)',
appendSubmenuTitle: '追加するフィールドの型を選択してください',
appendTitleAuto: '"オート"のフィールドを追加 (Ctrl+Shift+Ins)',
ascending: '昇順',
ascendingTitle: '${type}の子要素を昇順に並べ替え',
actionsMenu: 'クリックしてアクションメニューを開く (Ctrl+M)',
collapseAll: 'すべてを折りたたむ',
descending: '降順',
descendingTitle: '${type}の子要素を降順に並べ替え',
drag: 'ドラッグして選択中のフィールドを移動 (Alt+Shift+Arrows)',
duplicateKey: '複製キー',
duplicateText: '複製',
duplicateTitle: '選択中のフィールドを複製 (Ctrl+D)',
duplicateField: '選択中のフィールドを複製 (Ctrl+D)',
duplicateFieldError: 'フィールド名が重複しています',
cannotParseFieldError: 'JSONのフィールドを解析できません',
cannotParseValueError: 'JSONの値を解析できません',
empty: '空',
expandAll: 'すべてを展開',
expandTitle: 'クリックしてフィールドを展開/折りたたむ (Ctrl+E). \n' +
'Ctrl+Click ですべての子要素を展開/折りたたむ',
insert: '挿入',
insertTitle: '選択中のフィールドの前に新しいフィールドを挿入 (Ctrl+Ins)',
insertSub: '挿入するフィールドの型を選択',
object: 'オブジェクト',
ok: '実行',
redo: 'やり直す (Ctrl+Shift+Z)',
removeText: '削除',
removeTitle: '選択中のフィールドを削除 (Ctrl+Del)',
removeField: '選択中のフィールドを削除 (Ctrl+Del)',
selectNode: 'ノードを選択...',
showAll: 'すべてを表示',
showMore: 'もっと見る',
showMoreStatus: '${totalChilds}個のアイテムのうち ${visibleChilds}個を表示しています。',
sort: '並べ替え',
sortTitle: '${type}の子要素を並べ替え',
sortTitleShort: '並べ替え',
sortFieldLabel: 'フィールド:',
sortDirectionLabel: '順序:',
sortFieldTitle: '配列またはオブジェクトを並び替えるためのフィールドを選択',
sortAscending: '昇順',
sortAscendingTitle: '選択中のフィールドを昇順に並び替え',
sortDescending: '降順',
sortDescendingTitle: '選択中のフィールドを降順に並び替え',
string: '文字列',
transform: '変換',
transformTitle: '${type}の子要素をフィルター・並び替え・変換する',
transformTitleShort: '内容をフィルター・並び替え・変換する',
extract: '抽出',
extractTitle: '${type}を抽出',
transformQueryTitle: 'JMESPathクエリを入力',
transformWizardLabel: 'ウィザード',
transformWizardFilter: 'フィルター',
transformWizardSortBy: '並び替え',
transformWizardSelectFields: 'フィールドを選択',
transformQueryLabel: 'クエリ',
transformPreviewLabel: 'プレビュー',
type: '型',
typeTitle: '選択中のフィールドの型を変更',
openUrl: 'Ctrl+Click または Ctrl+Enter で 新規ウィンドウでURLを開く',
undo: '元に戻す (Ctrl+Z)',
validationCannotMove: '子要素に移動できません ',
autoType: 'オート: ' +
'フィールドの型は値から自動的に決定されます。 ' +
'(文字列・数値・ブール・null)',
objectType: 'オブジェクト: ' +
'オブジェクトは順序が決まっていないキーと値のペア組み合わせです。',
arrayType: '配列: ' +
'配列は順序が決まっている値の集合体です。',
stringType: '文字列: ' +
'フィールド型は値から決定されませんが、' +
'常に文字列として返されます。',
modeCodeText: 'コードモード',
modeCodeTitle: 'ハイライトモードに切り替え',
modeFormText: 'フォームモード',
modeFormTitle: 'フォームモードに切り替え',
modeTextText: 'テキストモード',
modeTextTitle: 'テキストモードに切り替え',
modeTreeText: 'ツリーモード',
modeTreeTitle: 'ツリーモードに切り替え',
modeViewText: 'ビューモード',
modeViewTitle: 'ビューモードに切り替え',
modePreviewText: 'プレビュー',
modePreviewTitle: 'プレビューに切り替え',
examples: '例',
default: 'デフォルト'
},
'fr-FR': {
array: 'Liste',
auto: 'Auto',
appendText: 'Ajouter',
appendTitle: 'Ajouter un champ de type \'auto\' après ce champ (Ctrl+Shift+Ins)',
appendSubmenuTitle: 'Sélectionner le type du champ à ajouter',
appendTitleAuto: 'Ajouter un champ de type \'auto\' (Ctrl+Shift+Ins)',
ascending: 'Ascendant',
ascendingTitle: 'Trier les enfants de ce ${type} par ordre ascendant',
actionsMenu: 'Ouvrir le menu des actions (Ctrl+M)',
collapseAll: 'Regrouper',
descending: 'Descendant',
descendingTitle: 'Trier les enfants de ce ${type} par ordre descendant',
drag: 'Déplacer (Alt+Shift+Arrows)',
duplicateKey: 'Dupliquer la clé',
duplicateText: 'Dupliquer',
duplicateTitle: 'Dupliquer les champs sélectionnés (Ctrl+D)',
duplicateField: 'Dupliquer ce champ (Ctrl+D)',
duplicateFieldError: 'Dupliquer le nom de champ',
cannotParseFieldError: 'Champ impossible à parser en JSON',
cannotParseValueError: 'Valeur impossible à parser en JSON',
empty: 'vide',
expandAll: 'Étendre',
expandTitle: 'Étendre/regrouper ce champ (Ctrl+E). \n' +
'Ctrl+Click pour étendre/regrouper avec tous les champs.',
insert: 'Insérer',
insertTitle: 'Insérer un champ de type \'auto\' avant ce champ (Ctrl+Ins)',
insertSub: 'Sélectionner le type de champ à insérer',
object: 'Objet',
ok: 'Ok',
redo: 'Rejouer (Ctrl+Shift+Z)',
removeText: 'Supprimer',
removeTitle: 'Supprimer les champs sélectionnés (Ctrl+Del)',
removeField: 'Supprimer ce champ (Ctrl+Del)',
searchTitle: 'Rechercher champs et valeurs',
searchNextResultTitle: 'Résultat suivant (Enter)',
searchPreviousResultTitle: 'Résultat précédent (Shift + Enter)',
selectNode: 'Sélectionner un nœud...',
showAll: 'voir tout',
showMore: 'voir plus',
showMoreStatus: '${visibleChilds} éléments affichés de ${totalChilds}.',
sort: 'Trier',
sortTitle: 'Trier les champs de ce ${type}',
sortTitleShort: 'Trier',
sortFieldLabel: 'Champ:',
sortDirectionLabel: 'Direction:',
sortFieldTitle: 'Sélectionner les champs permettant de trier les listes et objet',
sortAscending: 'Ascendant',
sortAscendingTitle: 'Trier les champs sélectionnés par ordre ascendant',
sortDescending: 'Descendant',
sortDescendingTitle: 'Trier les champs sélectionnés par ordre descendant',
string: 'Chaîne',
transform: 'Transformer',
transformTitle: 'Filtrer, trier, or transformer les enfants de ce ${type}',
transformTitleShort: 'Filtrer, trier ou transformer le contenu',
extract: 'Extraire',
extractTitle: 'Extraire ce ${type}',
transformQueryTitle: 'Saisir une requête JMESPath',
transformWizardLabel: 'Assistant',
transformWizardFilter: 'Filtrer',
transformWizardSortBy: 'Trier par',
transformWizardSelectFields: 'Sélectionner les champs',
transformQueryLabel: 'Requête',
transformPreviewLabel: 'Prévisualisation',
type: 'Type',
typeTitle: 'Changer le type de ce champ',
openUrl: 'Ctrl+Click ou Ctrl+Enter pour ouvrir l\'url dans une autre fenêtre',
undo: 'Annuler la dernière action (Ctrl+Z)',
validationCannotMove: 'Cannot move a field into a child of itself',
autoType: 'Champe de type "auto". ' +
'Ce type de champ est automatiquement déterminé en fonction de la valeur ' +
'et peut être de type "chaîne", "nombre", "booléen" ou null.',
objectType: 'Champ de type "objet". ' +
'Un objet contient un ensemble non ordonné de paires clé/valeur.',
arrayType: 'Champ de type "liste". ' +
'Une liste contient une collection ordonnée de valeurs.',
stringType: 'Champ de type "chaîne". ' +
'Ce type de champ n\'est pas déterminé en fonction de la valeur, ' +
'mais retourne systématiquement une chaîne de caractères.',
modeEditorTitle: 'Changer mode d\'édition',
modeCodeText: 'Code',
modeCodeTitle: 'Activer surlignage code',
modeFormText: 'Formulaire',
modeFormTitle: 'Activer formulaire',
modeTextText: 'Texte',
modeTextTitle: 'Activer éditeur texte',
modeTreeText: 'Arbre',
modeTreeTitle: 'Activer éditeur arbre',
modeViewText: 'Lecture seule',
modeViewTitle: 'Activer vue arbre',
modePreviewText: 'Prévisualisation',
modePreviewTitle: 'Activer mode prévisualiser',
examples: 'Exemples',
default: 'Défaut'
}
}
const _defaultLang = 'en'
const userLang = typeof navigator !== 'undefined'
? navigator.language || navigator.userLanguage
: undefined
let _lang = _locales.find(l => l === userLang) || _defaultLang
export function setLanguage (lang) {
if (!lang) {
return
}
const langFound = _locales.find(l => l === lang)
if (langFound) {
_lang = langFound
} else {
console.error('Language not found')
}
}
export function setLanguages (languages) {
if (!languages) {
return
}
for (const language in languages) {
const langFound = _locales.find(l => l === language)
if (!langFound) {
_locales.push(language)
}
_defs[language] = Object.assign({}, _defs[_defaultLang], _defs[language], languages[language])
}
}
export function translate (key, data, lang) {
if (!lang) {
lang = _lang
}
let text = _defs[lang][key] || _defs[_defaultLang][key] || key
if (data) {
for (const dataKey in data) {
text = text.replace('${' + dataKey + '}', data[dataKey])
}
}
return text
}

View File

@ -1,75 +0,0 @@
import jmespath from 'jmespath'
import { get, parsePath, parseString } from './util'
/**
* Build a JMESPath query based on query options coming from the wizard
* @param {JSON} json The JSON document for which to build the query.
* Used for context information like determining
* the type of values (string or number)
* @param {QueryOptions} queryOptions
* @return {string} Returns a query (as string)
*/
export function createQuery (json, queryOptions) {
const { sort, filter, projection } = queryOptions
let query = ''
if (filter) {
const examplePath = filter.field !== '@'
? ['0'].concat(parsePath('.' + filter.field))
: ['0']
const exampleValue = get(json, examplePath)
const value1 = typeof exampleValue === 'string'
? filter.value
: parseString(filter.value)
query += '[? ' +
filter.field + ' ' +
filter.relation + ' ' +
'`' + JSON.stringify(value1) + '`' +
']'
} else {
query += Array.isArray(json)
? '[*]'
: '@'
}
if (sort) {
if (sort.direction === 'desc') {
query += ' | reverse(sort_by(@, &' + sort.field + '))'
} else {
query += ' | sort_by(@, &' + sort.field + ')'
}
}
if (projection) {
if (query[query.length - 1] !== ']') {
query += ' | [*]'
}
if (projection.fields.length === 1) {
query += '.' + projection.fields[0]
} else if (projection.fields.length > 1) {
query += '.{' +
projection.fields.map(value => {
const parts = value.split('.')
const last = parts[parts.length - 1]
return last + ': ' + value
}).join(', ') +
'}'
} else { // values.length === 0
// ignore
}
}
return query
}
/**
* Execute a JMESPath query
* @param {JSON} json
* @param {string} query
* @return {JSON} Returns the transformed JSON
*/
export function executeQuery (json, query) {
return jmespath.search(json, query)
}

View File

@ -1,197 +0,0 @@
'use strict'
/**
* Convert part of a JSON object to a JSON string.
* Use case is to stringify a small part of a large JSON object so you can see
* a preview.
*
* @param {*} value
* The value to convert to a JSON string.
*
* @param {number | string | null} [space]
* A String or Number object that's used to insert white space into the output
* JSON string for readability purposes. If this is a Number, it indicates the
* number of space characters to use as white space; this number is capped at 10
* if it's larger than that. Values less than 1 indicate that no space should be
* used. If this is a String, the string (or the first 10 characters of the string,
* if it's longer than that) is used as white space. If this parameter is not
* provided (or is null), no white space is used.
*
* @param {number} [limit] Maximum size of the string output.
*
* @returns {string | undefined} Returns the string representation of the JSON object.
*/
export function stringifyPartial (value, space, limit) {
let _space // undefined by default
if (typeof space === 'number') {
if (space > 10) {
_space = repeat(' ', 10)
} else if (space >= 1) {
_space = repeat(' ', space)
}
// else ignore
} else if (typeof space === 'string' && space !== '') {
_space = space
}
const output = stringifyValue(value, _space, '', limit)
return output.length > limit
? (slice(output, limit) + '...')
: output
}
/**
* Stringify a value
* @param {*} value
* @param {string} space
* @param {string} indent
* @param {number} limit
* @return {string | undefined}
*/
function stringifyValue (value, space, indent, limit) {
// boolean, null, number, string, or date
if (typeof value === 'boolean' || value instanceof Boolean ||
value === null ||
typeof value === 'number' || value instanceof Number ||
typeof value === 'string' || value instanceof String ||
value instanceof Date) {
return JSON.stringify(value)
}
// array
if (Array.isArray(value)) {
return stringifyArray(value, space, indent, limit)
}
// object (test lastly!)
if (value && typeof value === 'object') {
return stringifyObject(value, space, indent, limit)
}
return undefined
}
/**
* Stringify an array
* @param {Array} array
* @param {string} space
* @param {string} indent
* @param {number} limit
* @return {string}
*/
function stringifyArray (array, space, indent, limit) {
const childIndent = space ? (indent + space) : undefined
let str = space ? '[\n' : '['
for (let i = 0; i < array.length; i++) {
const item = array[i]
if (space) {
str += childIndent
}
if (typeof item !== 'undefined' && typeof item !== 'function') {
str += stringifyValue(item, space, childIndent, limit)
} else {
str += 'null'
}
if (i < array.length - 1) {
str += space ? ',\n' : ','
}
// stop as soon as we're exceeding the limit
if (str.length > limit) {
return str + '...'
}
}
str += space ? ('\n' + indent + ']') : ']'
return str
}
/**
* Stringify an object
* @param {Object} object
* @param {string} space
* @param {string} indent
* @param {number} limit
* @return {string}
*/
function stringifyObject (object, space, indent, limit) {
const childIndent = space ? (indent + space) : undefined
let first = true
let str = space ? '{\n' : '{'
if (typeof object.toJSON === 'function') {
return stringifyValue(object.toJSON(), space, indent, limit)
}
for (const key in object) {
if (hasOwnProperty(object, key)) {
const value = object[key]
if (first) {
first = false
} else {
str += space ? ',\n' : ','
}
str += space
? (childIndent + '"' + key + '": ')
: ('"' + key + '":')
str += stringifyValue(value, space, childIndent, limit)
// stop as soon as we're exceeding the limit
if (str.length > limit) {
return str + '...'
}
}
}
str += space ? ('\n' + indent + '}') : '}'
return str
}
/**
* Repeat a string a number of times.
* Simple linear solution, we only need up to 10 iterations in practice
* @param {string} text
* @param {number} times
* @return {string}
*/
function repeat (text, times) {
let res = ''
while (times-- > 0) {
res += text
}
return res
}
/**
* Limit the length of text
* @param {string} text
* @param {number} [limit]
* @return {string}
*/
function slice (text, limit) {
return typeof limit === 'number'
? text.slice(0, limit)
: text
}
/**
* Test whether some text contains a JSON array, i.e. the first
* non-white space character is a [
* @param {string} jsonText
* @return {boolean}
*/
export function containsArray (jsonText) {
return /^\s*\[/.test(jsonText)
}
function hasOwnProperty (object, key) {
return Object.prototype.hasOwnProperty.call(object, key)
}

View File

@ -1,54 +0,0 @@
if (typeof Element !== 'undefined') {
// Polyfill for array remove
(() => {
function polyfill (item) {
if ('remove' in item) {
return
}
Object.defineProperty(item, 'remove', {
configurable: true,
enumerable: true,
writable: true,
value: function remove () {
if (this.parentNode !== undefined) { this.parentNode.removeChild(this) }
}
})
}
if (typeof window.Element !== 'undefined') { polyfill(window.Element.prototype) }
if (typeof window.CharacterData !== 'undefined') { polyfill(window.CharacterData.prototype) }
if (typeof window.DocumentType !== 'undefined') { polyfill(window.DocumentType.prototype) }
})()
}
// simple polyfill for Array.findIndex
if (!Array.prototype.findIndex) {
// eslint-disable-next-line no-extend-native
Array.prototype.findIndex = function (predicate) {
for (let i = 0; i < this.length; i++) {
const element = this[i]
if (predicate.call(this, element, i, this)) {
return i
}
}
return -1
}
}
// Polyfill for Array.find
if (!Array.prototype.find) {
// eslint-disable-next-line no-extend-native
Array.prototype.find = function (predicate) {
const i = this.findIndex(predicate)
return this[i]
}
}
// Polyfill for String.trim
if (!String.prototype.trim) {
// eslint-disable-next-line no-extend-native
String.prototype.trim = function () {
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '')
}
}

View File

@ -1,689 +0,0 @@
'use strict'
import { setLanguage, setLanguages, translate } from './i18n'
import { ModeSwitcher } from './ModeSwitcher'
import { ErrorTable } from './ErrorTable'
import { showSortModal } from './showSortModal'
import { showTransformModal } from './showTransformModal'
import { textModeMixins } from './textmode'
import { DEFAULT_MODAL_ANCHOR, MAX_PREVIEW_CHARACTERS, PREVIEW_HISTORY_LIMIT, SIZE_LARGE } from './constants'
import { FocusTracker } from './FocusTracker'
import {
addClassName,
debounce,
escapeUnicodeChars,
formatSize,
isObject,
limitCharacters,
parse,
removeClassName,
repair,
sort,
sortObjectKeys
} from './util'
import { History } from './History'
import { createQuery, executeQuery } from './jmespathQuery'
const textmode = textModeMixins[0].mixin
// create a mixin with the functions for text mode
const previewmode = {}
/**
* Create a JSON document preview, suitable for processing of large documents
* @param {Element} container
* @param {Object} [options] Object with options. See docs for details.
* @private
*/
previewmode.create = function (container, options = {}) {
if (typeof options.statusBar === 'undefined') {
options.statusBar = true
}
// setting default for previewmode
options.mainMenuBar = options.mainMenuBar !== false
options.enableSort = options.enableSort !== false
options.enableTransform = options.enableTransform !== false
options.createQuery = options.createQuery || createQuery
options.executeQuery = options.executeQuery || executeQuery
this.options = options
// indentation
if (typeof options.indentation === 'number') {
this.indentation = Number(options.indentation)
} else {
this.indentation = 2 // number of spaces
}
// language
setLanguages(this.options.languages)
setLanguage(this.options.language)
// determine mode
this.mode = 'preview'
const me = this
this.container = container
this.dom = {}
this.json = undefined
this.text = ''
// TODO: JSON Schema support
// create a debounced validate function
this._debouncedValidate = debounce(this.validate.bind(this), this.DEBOUNCE_INTERVAL)
this.width = container.clientWidth
this.height = container.clientHeight
this.frame = document.createElement('div')
this.frame.className = 'jsoneditor jsoneditor-mode-preview'
this.frame.onclick = event => {
// prevent default submit action when the editor is located inside a form
event.preventDefault()
}
// setting the FocusTracker on 'this.frame' to track the editor's focus event
const focusTrackerConfig = {
target: this.frame,
onFocus: this.options.onFocus || null,
onBlur: this.options.onBlur || null
}
this.frameFocusTracker = new FocusTracker(focusTrackerConfig)
this.content = document.createElement('div')
this.content.className = 'jsoneditor-outer'
this.dom.busy = document.createElement('div')
this.dom.busy.className = 'jsoneditor-busy'
this.dom.busyContent = document.createElement('span')
this.dom.busyContent.innerHTML = 'busy...'
this.dom.busy.appendChild(this.dom.busyContent)
this.content.appendChild(this.dom.busy)
this.dom.previewContent = document.createElement('pre')
this.dom.previewContent.className = 'jsoneditor-preview'
this.dom.previewText = document.createTextNode('')
this.dom.previewContent.appendChild(this.dom.previewText)
this.content.appendChild(this.dom.previewContent)
if (this.options.mainMenuBar) {
addClassName(this.content, 'has-main-menu-bar')
// create menu
this.menu = document.createElement('div')
this.menu.className = 'jsoneditor-menu'
this.frame.appendChild(this.menu)
// create format button
const buttonFormat = document.createElement('button')
buttonFormat.type = 'button'
buttonFormat.className = 'jsoneditor-format'
buttonFormat.title = translate('formatTitle')
this.menu.appendChild(buttonFormat)
buttonFormat.onclick = function handleFormat () {
me.executeWithBusyMessage(() => {
try {
me.format()
} catch (err) {
me._onError(err)
}
}, 'formatting...')
}
// create compact button
const buttonCompact = document.createElement('button')
buttonCompact.type = 'button'
buttonCompact.className = 'jsoneditor-compact'
buttonCompact.title = translate('compactTitle')
this.menu.appendChild(buttonCompact)
buttonCompact.onclick = function handleCompact () {
me.executeWithBusyMessage(() => {
try {
me.compact()
} catch (err) {
me._onError(err)
}
}, 'compacting...')
}
// create sort button
if (this.options.enableSort) {
const sort = document.createElement('button')
sort.type = 'button'
sort.className = 'jsoneditor-sort'
sort.title = translate('sortTitleShort')
sort.onclick = () => {
me._showSortModal()
}
this.menu.appendChild(sort)
}
// create transform button
if (this.options.enableTransform) {
const transform = document.createElement('button')
transform.type = 'button'
transform.title = translate('transformTitleShort')
transform.className = 'jsoneditor-transform'
transform.onclick = () => {
me._showTransformModal()
}
this.dom.transform = transform
this.menu.appendChild(transform)
}
// create repair button
const buttonRepair = document.createElement('button')
buttonRepair.type = 'button'
buttonRepair.className = 'jsoneditor-repair'
buttonRepair.title = translate('repairTitle')
this.menu.appendChild(buttonRepair)
buttonRepair.onclick = () => {
if (me.json === undefined) { // only repair if we don't have valid JSON
me.executeWithBusyMessage(() => {
try {
me.repair()
} catch (err) {
me._onError(err)
}
}, 'repairing...')
}
}
// create history and undo/redo buttons
if (this.options.history !== false) { // default option value is true
const onHistoryChange = () => {
me.dom.undo.disabled = !me.history.canUndo()
me.dom.redo.disabled = !me.history.canRedo()
}
const calculateItemSize = item => // times two to account for the json object
item.text.length * 2
this.history = new History(onHistoryChange, calculateItemSize, PREVIEW_HISTORY_LIMIT)
// create undo button
const undo = document.createElement('button')
undo.type = 'button'
undo.className = 'jsoneditor-undo jsoneditor-separator'
undo.title = translate('undo')
undo.onclick = () => {
const action = me.history.undo()
if (action) {
me._applyHistory(action)
}
}
this.menu.appendChild(undo)
this.dom.undo = undo
// create redo button
const redo = document.createElement('button')
redo.type = 'button'
redo.className = 'jsoneditor-redo'
redo.title = translate('redo')
redo.onclick = () => {
const action = me.history.redo()
if (action) {
me._applyHistory(action)
}
}
this.menu.appendChild(redo)
this.dom.redo = redo
// force enabling/disabling the undo/redo button
this.history.onChange()
}
// create mode box
if (this.options && this.options.modes && this.options.modes.length) {
this.modeSwitcher = new ModeSwitcher(this.menu, this.options.modes, this.options.mode, function onSwitch (mode) {
// switch mode and restore focus
me.setMode(mode)
me.modeSwitcher.focus()
})
}
}
this.errorTable = new ErrorTable({
errorTableVisible: true,
onToggleVisibility: function () {
me.validate()
},
onFocusLine: null,
onChangeHeight: function (height) {
// TODO: change CSS to using flex box, remove setting height using JavaScript
const statusBarHeight = me.dom.statusBar ? me.dom.statusBar.clientHeight : 0
const totalHeight = height + statusBarHeight + 1
me.content.style.marginBottom = (-totalHeight) + 'px'
me.content.style.paddingBottom = totalHeight + 'px'
}
})
this.frame.appendChild(this.content)
this.frame.appendChild(this.errorTable.getErrorTable())
this.container.appendChild(this.frame)
if (options.statusBar) {
addClassName(this.content, 'has-status-bar')
const statusBar = document.createElement('div')
this.dom.statusBar = statusBar
statusBar.className = 'jsoneditor-statusbar'
this.frame.appendChild(statusBar)
this.dom.fileSizeInfo = document.createElement('span')
this.dom.fileSizeInfo.className = 'jsoneditor-size-info'
this.dom.fileSizeInfo.innerText = ''
statusBar.appendChild(this.dom.fileSizeInfo)
this.dom.arrayInfo = document.createElement('span')
this.dom.arrayInfo.className = 'jsoneditor-size-info'
this.dom.arrayInfo.innerText = ''
statusBar.appendChild(this.dom.arrayInfo)
statusBar.appendChild(this.errorTable.getErrorCounter())
statusBar.appendChild(this.errorTable.getWarningIcon())
statusBar.appendChild(this.errorTable.getErrorIcon())
}
this._renderPreview()
this.setSchema(this.options.schema, this.options.schemaRefs)
}
previewmode._renderPreview = function () {
const text = this.getText()
this.dom.previewText.nodeValue = limitCharacters(text, MAX_PREVIEW_CHARACTERS)
if (this.dom.fileSizeInfo) {
this.dom.fileSizeInfo.innerText = 'Size: ' + formatSize(text.length)
}
if (this.dom.arrayInfo) {
if (Array.isArray(this.json)) {
this.dom.arrayInfo.innerText = ('Array: ' + this.json.length + ' items')
} else {
this.dom.arrayInfo.innerText = ''
}
}
}
/**
* Handle a change:
* - Validate JSON schema
* - Send a callback to the onChange listener if provided
* @private
*/
previewmode._onChange = function () {
// validate JSON schema (if configured)
this._debouncedValidate()
// trigger the onChange callback
if (this.options.onChange) {
try {
this.options.onChange()
} catch (err) {
console.error('Error in onChange callback: ', err)
}
}
// trigger the onChangeJSON callback
if (this.options.onChangeJSON) {
try {
this.options.onChangeJSON(this.get())
} catch (err) {
console.error('Error in onChangeJSON callback: ', err)
}
}
// trigger the onChangeText callback
if (this.options.onChangeText) {
try {
this.options.onChangeText(this.getText())
} catch (err) {
console.error('Error in onChangeText callback: ', err)
}
}
}
/**
* Open a sort modal
* @private
*/
previewmode._showSortModal = function () {
const me = this
function onSort (json, sortedBy) {
if (Array.isArray(json)) {
const sortedArray = sort(json, sortedBy.path, sortedBy.direction)
me.sortedBy = sortedBy
me._setAndFireOnChange(sortedArray)
}
if (isObject(json)) {
const sortedObject = sortObjectKeys(json, sortedBy.direction)
me.sortedBy = sortedBy
me._setAndFireOnChange(sortedObject)
}
}
this.executeWithBusyMessage(() => {
const container = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR
const json = me.get()
me._renderPreview() // update array count
showSortModal(container, json, sortedBy => {
me.executeWithBusyMessage(() => {
onSort(json, sortedBy)
}, 'sorting...')
}, me.sortedBy)
}, 'parsing...')
}
/**
* Open a transform modal
* @private
*/
previewmode._showTransformModal = function () {
this.executeWithBusyMessage(() => {
const { createQuery, executeQuery, modalAnchor, queryDescription } = this.options
const json = this.get()
this._renderPreview() // update array count
showTransformModal({
anchor: modalAnchor || DEFAULT_MODAL_ANCHOR,
json,
queryDescription, // can be undefined
createQuery,
executeQuery,
onTransform: query => {
this.executeWithBusyMessage(() => {
const updatedJson = executeQuery(json, query)
this._setAndFireOnChange(updatedJson)
}, 'transforming...')
}
})
}, 'parsing...')
}
/**
* Destroy the editor. Clean up DOM, event listeners, and web workers.
*/
previewmode.destroy = function () {
if (this.frame && this.container && this.frame.parentNode === this.container) {
this.container.removeChild(this.frame)
}
if (this.modeSwitcher) {
this.modeSwitcher.destroy()
this.modeSwitcher = null
}
this._debouncedValidate = null
if (this.history) {
this.history.clear()
this.history = null
}
// Removing the FocusTracker set to track the editor's focus event
this.frameFocusTracker.destroy()
}
/**
* Compact the code in the text editor
*/
previewmode.compact = function () {
const json = this.get()
const text = JSON.stringify(json)
// we know that in this case the json is still the same, so we pass json too
this._setTextAndFireOnChange(text, json)
}
/**
* Format the code in the text editor
*/
previewmode.format = function () {
const json = this.get()
const text = JSON.stringify(json, null, this.indentation)
// we know that in this case the json is still the same, so we pass json too
this._setTextAndFireOnChange(text, json)
}
/**
* Repair the code in the text editor
*/
previewmode.repair = function () {
const text = this.getText()
const repairedText = repair(text)
this._setTextAndFireOnChange(repairedText)
}
/**
* Set focus to the editor
*/
previewmode.focus = function () {
// we don't really have a place to focus,
// let's focus on the transform button
this.dom.transform.focus()
}
/**
* Set json data in the editor
* @param {*} json
*/
previewmode.set = function (json) {
if (this.history) {
this.history.clear()
}
this._set(json)
}
/**
* Update data. Same as calling `set` in text/code mode.
* @param {*} json
*/
previewmode.update = function (json) {
this._set(json)
}
/**
* Set json data
* @param {*} json
*/
previewmode._set = function (json) {
this.text = undefined
this.json = json
this._renderPreview()
this._pushHistory()
// validate JSON schema
this._debouncedValidate()
}
previewmode._setAndFireOnChange = function (json) {
this._set(json)
this._onChange()
}
/**
* Get json data
* @return {*} json
*/
previewmode.get = function () {
if (this.json === undefined) {
const text = this.getText()
this.json = parse(text) // this can throw an error
}
return this.json
}
/**
* Get the text contents of the editor
* @return {String} jsonText
*/
previewmode.getText = function () {
if (this.text === undefined) {
this.text = JSON.stringify(this.json, null, this.indentation)
if (this.options.escapeUnicode === true) {
this.text = escapeUnicodeChars(this.text)
}
}
return this.text
}
/**
* Set the text contents of the editor
* @param {String} jsonText
*/
previewmode.setText = function (jsonText) {
if (this.history) {
this.history.clear()
}
this._setText(jsonText)
}
/**
* Update the text contents
* @param {string} jsonText
*/
previewmode.updateText = function (jsonText) {
// don't update if there are no changes
if (this.getText() === jsonText) {
return
}
this._setText(jsonText)
}
/**
* Set the text contents of the editor
* @param {string} jsonText
* @param {*} [json] Optional JSON instance of the text
* @private
*/
previewmode._setText = function (jsonText, json) {
if (this.options.escapeUnicode === true) {
this.text = escapeUnicodeChars(jsonText)
} else {
this.text = jsonText
}
this.json = json
this._renderPreview()
if (this.json === undefined) {
const me = this
this.executeWithBusyMessage(() => {
try {
// force parsing the json now, else it will be done in validate without feedback
me.json = me.get()
me._renderPreview()
me._pushHistory()
} catch (err) {
// no need to throw an error, validation will show an error
}
}, 'parsing...')
} else {
this._pushHistory()
}
this._debouncedValidate()
}
/**
* Set text and fire onChange callback
* @param {string} jsonText
* @param {*} [json] Optional JSON instance of the text
* @private
*/
previewmode._setTextAndFireOnChange = function (jsonText, json) {
this._setText(jsonText, json)
this._onChange()
}
/**
* Apply history to the current state
* @param {{json?: JSON, text?: string}} action
* @private
*/
previewmode._applyHistory = function (action) {
this.json = action.json
this.text = action.text
this._renderPreview()
this._debouncedValidate()
}
/**
* Push the current state to history
* @private
*/
previewmode._pushHistory = function () {
if (!this.history) {
return
}
const action = {
text: this.text,
json: this.json
}
this.history.add(action)
}
/**
* Execute a heavy, blocking action.
* Before starting the action, show a message on screen like "parsing..."
* @param {function} fn
* @param {string} message
*/
previewmode.executeWithBusyMessage = function (fn, message) {
const size = this.getText().length
if (size > SIZE_LARGE) {
const me = this
addClassName(me.frame, 'busy')
me.dom.busyContent.innerText = message
setTimeout(() => {
fn()
removeClassName(me.frame, 'busy')
me.dom.busyContent.innerText = ''
}, 100)
} else {
fn()
}
}
// TODO: refactor into composable functions instead of this shaky mixin-like structure
previewmode.validate = textmode.validate
previewmode._renderErrors = textmode._renderErrors
// define modes
export const previewModeMixins = [
{
mode: 'preview',
mixin: previewmode,
data: 'json'
}
]

View File

@ -1,154 +0,0 @@
'use strict'
import { translate } from './i18n'
/**
* A factory function to create an ShowMoreNode, which depends on a Node
* @param {function} Node
*/
export function showMoreNodeFactory (Node) {
/**
* @constructor ShowMoreNode
* @extends Node
* @param {TreeEditor} editor
* @param {Node} parent
* Create a new ShowMoreNode. This is a special node which is created
* for arrays or objects having more than 100 items
*/
function ShowMoreNode (editor, parent) {
/** @type {TreeEditor} */
this.editor = editor
this.parent = parent
this.dom = {}
}
ShowMoreNode.prototype = new Node()
/**
* Return a table row with an append button.
* @return {Element} dom TR element
*/
ShowMoreNode.prototype.getDom = function () {
if (this.dom.tr) {
return this.dom.tr
}
this._updateEditability()
// display "show more"
if (!this.dom.tr) {
const me = this
const parent = this.parent
const showMoreButton = document.createElement('a')
showMoreButton.appendChild(document.createTextNode(translate('showMore')))
showMoreButton.href = '#'
showMoreButton.onclick = event => {
// TODO: use callback instead of accessing a method of the parent
parent.visibleChilds = Math.floor(parent.visibleChilds / parent.getMaxVisibleChilds() + 1) *
parent.getMaxVisibleChilds()
me.updateDom()
parent.showChilds()
event.preventDefault()
return false
}
const showAllButton = document.createElement('a')
showAllButton.appendChild(document.createTextNode(translate('showAll')))
showAllButton.href = '#'
showAllButton.onclick = event => {
// TODO: use callback instead of accessing a method of the parent
parent.visibleChilds = Infinity
me.updateDom()
parent.showChilds()
event.preventDefault()
return false
}
const moreContents = document.createElement('div')
const moreText = document.createTextNode(this._getShowMoreText())
moreContents.className = 'jsoneditor-show-more'
moreContents.appendChild(moreText)
moreContents.appendChild(showMoreButton)
moreContents.appendChild(document.createTextNode('. '))
moreContents.appendChild(showAllButton)
moreContents.appendChild(document.createTextNode('. '))
const tdContents = document.createElement('td')
tdContents.appendChild(moreContents)
const moreTr = document.createElement('tr')
if (this.editor.options.mode === 'tree') {
moreTr.appendChild(document.createElement('td'))
moreTr.appendChild(document.createElement('td'))
}
moreTr.appendChild(tdContents)
moreTr.className = 'jsoneditor-show-more'
this.dom.tr = moreTr
this.dom.moreContents = moreContents
this.dom.moreText = moreText
}
this.updateDom()
return this.dom.tr
}
/**
* Update the HTML dom of the Node
*/
ShowMoreNode.prototype.updateDom = function (options) {
if (this.isVisible()) {
// attach to the right child node (the first non-visible child)
this.dom.tr.node = this.parent.childs[this.parent.visibleChilds]
if (!this.dom.tr.parentNode) {
const nextTr = this.parent._getNextTr()
if (nextTr) {
nextTr.parentNode.insertBefore(this.dom.tr, nextTr)
}
}
// update the counts in the text
this.dom.moreText.nodeValue = this._getShowMoreText()
// update left margin
this.dom.moreContents.style.marginLeft = (this.getLevel() + 1) * 24 + 'px'
} else {
if (this.dom.tr && this.dom.tr.parentNode) {
this.dom.tr.parentNode.removeChild(this.dom.tr)
}
}
}
ShowMoreNode.prototype._getShowMoreText = function () {
return translate('showMoreStatus', {
visibleChilds: this.parent.visibleChilds,
totalChilds: this.parent.childs.length
}) + ' '
}
/**
* Check whether the ShowMoreNode is currently visible.
* the ShowMoreNode is visible when it's parent has more childs than
* the current visibleChilds
* @return {boolean} isVisible
*/
ShowMoreNode.prototype.isVisible = function () {
return this.parent.expanded && this.parent.childs.length > this.parent.visibleChilds
}
/**
* Handle an event. The event is caught centrally by the editor
* @param {Event} event
*/
ShowMoreNode.prototype.onEvent = function (event) {
const type = event.type
if (type === 'keydown') {
this.onKeyDown(event)
}
}
return ShowMoreNode
}

View File

@ -1,131 +0,0 @@
import picoModal from 'picomodal'
import { translate } from './i18n'
import { contains, getChildPaths } from './util'
/**
* Show advanced sorting modal
* @param {HTMLElement} container The container where to center
* the modal and create an overlay
* @param {JSON} json The JSON data to be sorted.
* @param {function} onSort Callback function, invoked with
* an object containing the selected
* path and direction
* @param {Object} options
* Available options:
* - {string} path The selected path
* - {'asc' | 'desc'} direction The selected direction
*/
export function showSortModal (container, json, onSort, options) {
const paths = Array.isArray(json)
? getChildPaths(json)
: ['']
const selectedPath = options && options.path && contains(paths, options.path)
? options.path
: paths[0]
const selectedDirection = (options && options.direction) || 'asc'
const content = '<div class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('sort') + '</div>' +
'<form>' +
'<table>' +
'<tbody>' +
'<tr>' +
' <td>' + translate('sortFieldLabel') + ' </td>' +
' <td class="jsoneditor-modal-input">' +
' <div class="jsoneditor-select-wrapper">' +
' <select id="field" title="' + translate('sortFieldTitle') + '">' +
' </select>' +
' </div>' +
' </td>' +
'</tr>' +
'<tr>' +
' <td>' + translate('sortDirectionLabel') + ' </td>' +
' <td class="jsoneditor-modal-input">' +
' <div id="direction" class="jsoneditor-button-group">' +
'<input type="button" ' +
'value="' + translate('sortAscending') + '" ' +
'title="' + translate('sortAscendingTitle') + '" ' +
'data-value="asc" ' +
'class="jsoneditor-button-first jsoneditor-button-asc"/>' +
'<input type="button" ' +
'value="' + translate('sortDescending') + '" ' +
'title="' + translate('sortDescendingTitle') + '" ' +
'data-value="desc" ' +
'class="jsoneditor-button-last jsoneditor-button-desc"/>' +
' </div>' +
' </td>' +
'</tr>' +
'<tr>' +
'<td colspan="2" class="jsoneditor-modal-input jsoneditor-modal-actions">' +
' <input type="submit" id="ok" value="' + translate('ok') + '" />' +
'</td>' +
'</tr>' +
'</tbody>' +
'</table>' +
'</form>' +
'</div>'
picoModal({
parent: container,
content: content,
overlayClass: 'jsoneditor-modal-overlay',
overlayStyles: {
backgroundColor: 'rgb(1,1,1)',
opacity: 0.3
},
modalClass: 'jsoneditor-modal jsoneditor-modal-sort'
})
.afterCreate(modal => {
const form = modal.modalElem().querySelector('form')
const ok = modal.modalElem().querySelector('#ok')
const field = modal.modalElem().querySelector('#field')
const direction = modal.modalElem().querySelector('#direction')
function preprocessPath (path) {
return (path === '')
? '@'
: (path[0] === '.')
? path.slice(1)
: path
}
paths.forEach(path => {
const option = document.createElement('option')
option.text = preprocessPath(path)
option.value = path
field.appendChild(option)
})
function setDirection (value) {
direction.value = value
direction.className = 'jsoneditor-button-group jsoneditor-button-group-value-' + direction.value
}
field.value = selectedPath || paths[0]
setDirection(selectedDirection || 'asc')
direction.onclick = event => {
setDirection(event.target.getAttribute('data-value'))
}
ok.onclick = event => {
event.preventDefault()
event.stopPropagation()
modal.close()
onSort({
path: field.value,
direction: direction.value
})
}
if (form) { // form is not available when JSONEditor is created inside a form
form.onsubmit = ok.onclick
}
})
.afterClose(modal => {
modal.destroy()
})
.show()
}

View File

@ -1,296 +0,0 @@
import picoModal from 'picomodal'
import Selectr from './assets/selectr/selectr'
import { translate } from './i18n'
import { stringifyPartial } from './jsonUtils'
import { getChildPaths, debounce } from './util'
import { MAX_PREVIEW_CHARACTERS } from './constants'
const DEFAULT_DESCRIPTION =
'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.'
/**
* Show advanced filter and transform modal using JMESPath
* @param {Object} params
* @property {HTMLElement} container The container where to center
* the modal and create an overlay
* @property {JSON} json The json data to be transformed
* @property {string} [queryDescription] Optional custom description explaining
* the transform functionality
* @property {function} createQuery Function called to create a query
* from the wizard form
* @property {function} executeQuery Execute a query for the preview pane
* @property {function} onTransform Callback invoked with the created
* query as callback
*/
export function showTransformModal (
{
container,
json,
queryDescription = DEFAULT_DESCRIPTION,
createQuery,
executeQuery,
onTransform
}
) {
const value = json
const content = '<label class="pico-modal-contents">' +
'<div class="pico-modal-header">' + translate('transform') + '</div>' +
'<p>' + queryDescription + '</p>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
'<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
' <table class="jsoneditor-jmespath-wizard-table">' +
' <tbody>' +
' <tr>' +
' <th>' + translate('transformWizardFilter') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-field" >' +
' <select id="filterField">' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-relation" >' +
' <select id="filterRelation">' +
' <option value="==">==</option>' +
' <option value="!=">!=</option>' +
' <option value="<">&lt;</option>' +
' <option value="<=">&lt;=</option>' +
' <option value=">">&gt;</option>' +
' <option value=">=">&gt;=</option>' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-filter-value" >' +
' <input type="text" class="value" placeholder="value..." id="filterValue" />' +
' </div>' +
' </td>' +
' </tr>' +
' <tr>' +
' <th>' + translate('transformWizardSortBy') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <div class="jsoneditor-inline jsoneditor-jmespath-sort-field">' +
' <select id="sortField">' +
' </select>' +
' </div>' +
' <div class="jsoneditor-inline jsoneditor-jmespath-sort-order" >' +
' <select id="sortOrder">' +
' <option value="asc">Ascending</option>' +
' <option value="desc">Descending</option>' +
' </select>' +
' </div>' +
' </td>' +
' </tr>' +
' <tr id="selectFieldsPart">' +
' <th>' + translate('transformWizardSelectFields') + '</th>' +
' <td class="jsoneditor-jmespath-filter">' +
' <select class="jsoneditor-jmespath-select-fields" id="selectFields" multiple></select>' +
' </td>' +
' </tr>' +
' </tbody>' +
' </table>' +
'</div>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformQueryLabel') + ' </div>' +
'<div class="jsoneditor-jmespath-block">' +
' <textarea id="query" ' +
' rows="4" ' +
' autocomplete="off" ' +
' autocorrect="off" ' +
' autocapitalize="off" ' +
' spellcheck="false"' +
' title="' + translate('transformQueryTitle') + '">[*]</textarea>' +
'</div>' +
'<div class="jsoneditor-jmespath-label">' + translate('transformPreviewLabel') + ' </div>' +
'<div class="jsoneditor-jmespath-block">' +
' <textarea id="preview" ' +
' class="jsoneditor-transform-preview"' +
' readonly> </textarea>' +
'</div>' +
'<div class="jsoneditor-jmespath-block jsoneditor-modal-actions">' +
' <input type="submit" id="ok" value="' + translate('ok') + '" autofocus />' +
'</div>' +
'</div>'
picoModal({
parent: container,
content: content,
overlayClass: 'jsoneditor-modal-overlay',
overlayStyles: {
backgroundColor: 'rgb(1,1,1)',
opacity: 0.3
},
modalClass: 'jsoneditor-modal jsoneditor-modal-transform',
focus: false
})
.afterCreate(modal => {
const elem = modal.modalElem()
const wizard = elem.querySelector('#wizard')
const ok = elem.querySelector('#ok')
const filterField = elem.querySelector('#filterField')
const filterRelation = elem.querySelector('#filterRelation')
const filterValue = elem.querySelector('#filterValue')
const sortField = elem.querySelector('#sortField')
const sortOrder = elem.querySelector('#sortOrder')
const selectFields = elem.querySelector('#selectFields')
const query = elem.querySelector('#query')
const preview = elem.querySelector('#preview')
if (!Array.isArray(value)) {
wizard.style.fontStyle = 'italic'
wizard.innerHTML = '(wizard not available for objects, only for arrays)'
}
const sortablePaths = getChildPaths(json)
sortablePaths.forEach(path => {
const formattedPath = preprocessPath(path)
const filterOption = document.createElement('option')
filterOption.text = formattedPath
filterOption.value = formattedPath
filterField.appendChild(filterOption)
const sortOption = document.createElement('option')
sortOption.text = formattedPath
sortOption.value = formattedPath
sortField.appendChild(sortOption)
})
const selectablePaths = getChildPaths(json, true).filter(path => path !== '')
if (selectablePaths.length > 0) {
selectablePaths.forEach(path => {
const formattedPath = preprocessPath(path)
const option = document.createElement('option')
option.text = formattedPath
option.value = formattedPath
selectFields.appendChild(option)
})
} else {
const selectFieldsPart = elem.querySelector('#selectFieldsPart')
if (selectFieldsPart) {
selectFieldsPart.style.display = 'none'
}
}
const selectrFilterField = new Selectr(filterField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
const selectrFilterRelation = new Selectr(filterRelation, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'compare...' })
const selectrSortField = new Selectr(sortField, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'field...' })
const selectrSortOrder = new Selectr(sortOrder, { defaultSelected: false, clearable: true, allowDeselect: true, placeholder: 'order...' })
const selectrSelectFields = new Selectr(selectFields, { multiple: true, clearable: true, defaultSelected: false, placeholder: 'select fields...' })
selectrFilterField.on('selectr.change', generateQueryFromWizard)
selectrFilterRelation.on('selectr.change', generateQueryFromWizard)
filterValue.oninput = generateQueryFromWizard
selectrSortField.on('selectr.change', generateQueryFromWizard)
selectrSortOrder.on('selectr.change', generateQueryFromWizard)
selectrSelectFields.on('selectr.change', generateQueryFromWizard)
elem.querySelector('.pico-modal-contents').onclick = event => {
// prevent the first clear button (in any select box) from getting
// focus when clicking anywhere in the modal. Only allow clicking links.
if (event.target.nodeName !== 'A') {
event.preventDefault()
}
}
function preprocessPath (path) {
return (path === '')
? '@'
: (path[0] === '.')
? path.slice(1)
: path
}
function updatePreview () {
try {
const transformed = executeQuery(value, query.value)
preview.className = 'jsoneditor-transform-preview'
preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)
ok.disabled = false
} catch (err) {
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
preview.value = err.toString()
ok.disabled = true
}
}
const debouncedUpdatePreview = debounce(updatePreview, 300)
function tryCreateQuery (json, queryOptions) {
try {
query.value = createQuery(json, queryOptions)
ok.disabled = false
debouncedUpdatePreview()
} catch (err) {
const message = 'Error: an error happened when executing "createQuery": ' + (err.message || err.toString())
query.value = ''
ok.disabled = true
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
preview.value = message
}
}
function generateQueryFromWizard () {
const queryOptions = {}
if (filterField.value && filterRelation.value && filterValue.value) {
queryOptions.filter = {
field: filterField.value,
relation: filterRelation.value,
value: filterValue.value
}
}
if (sortField.value && sortOrder.value) {
queryOptions.sort = {
field: sortField.value,
direction: sortOrder.value
}
}
if (selectFields.value) {
const fields = []
for (let i = 0; i < selectFields.options.length; i++) {
if (selectFields.options[i].selected) {
const selectedField = selectFields.options[i].value
fields.push(selectedField)
}
}
queryOptions.projection = {
fields
}
}
tryCreateQuery(json, queryOptions)
}
query.oninput = debouncedUpdatePreview
ok.onclick = event => {
event.preventDefault()
event.stopPropagation()
modal.close()
onTransform(query.value)
}
// initialize with empty query
tryCreateQuery(json, {})
setTimeout(() => {
query.select()
query.focus()
query.selectionStart = 3
query.selectionEnd = 3
})
})
.afterClose(modal => {
modal.destroy()
})
.show()
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
exports.tryRequireAjv = function () {
try {
return require('ajv')
} catch (err) {
// no problem... when we need Ajv we will throw a neat exception
}
}

View File

@ -1,7 +0,0 @@
exports.tryRequireThemeJsonEditor = function () {
try {
require('./ace/theme-jsoneditor')
} catch (err) {
console.error(err)
}
}

View File

@ -1,25 +0,0 @@
/**
* @typedef {object} QueryOptions
* @property {FilterOptions} [filter]
* @property {SortOptions} [sort]
* @property {ProjectionOptions} [projection]
*/
/**
* @typedef {object} FilterOptions
* @property {string} field
* @property {string} relation Can be '==', '<', etc
* @property {string} value
*/
/**
* @typedef {object} SortOptions
* @property {string} field
* @property {string} direction Can be 'asc' or 'desc'
*/
/**
* @typedef {object} ProjectionOptions
* @property {string[]} fields
*/

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
import { isPromise, isValidValidationError, stringifyPath } from './util'
/**
* Execute custom validation if configured.
*
* Returns a promise resolving with the custom errors (or an empty array).
*/
export function validateCustom (json, onValidate) {
if (!onValidate) {
return Promise.resolve([])
}
try {
const customValidateResults = onValidate(json)
const resultPromise = isPromise(customValidateResults)
? customValidateResults
: Promise.resolve(customValidateResults)
return resultPromise.then(customValidationPathErrors => {
if (Array.isArray(customValidationPathErrors)) {
return customValidationPathErrors
.filter(error => {
const valid = isValidValidationError(error)
if (!valid) {
console.warn('Ignoring a custom validation error with invalid structure. ' +
'Expected structure: {path: [...], message: "..."}. ' +
'Actual error:', error)
}
return valid
})
.map(error => // change data structure into the structure matching the JSON schema errors
({
dataPath: stringifyPath(error.path),
message: error.message,
type: 'customValidation'
}))
} else {
return []
}
})
} catch (err) {
return Promise.reject(err)
}
}

View File

@ -1,15 +0,0 @@
let VanillaPicker
if (window.Picker) {
// use the already loaded instance of VanillaPicker
VanillaPicker = window.Picker
} else {
try {
// load color picker
VanillaPicker = require('vanilla-picker')
} catch (err) {
// probably running the minimalist bundle
}
}
module.exports = VanillaPicker

9
src/main.js Normal file
View File

@ -0,0 +1,9 @@
import App from './App.svelte';
const app = new App({
target: document.body,
props: {
}
});
export default app;

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,749 +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="240"
height="144"
id="svg4136"
version="1.1"
inkscape:version="0.91 r13725"
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="#ff63ff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1026"
id="namedview4144"
showgrid="true"
inkscape:zoom="4"
inkscape:cx="13.229181"
inkscape:cy="119.82429"
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/ -->
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1"
height="16"
width="16"
y="4"
x="4" />
<rect
id="svg_1-7"
height="16"
width="16"
y="3.999995"
x="28.000006"
style="fill:#ec3f29;fill-opacity:0.94117647;stroke:none;stroke-width:0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
x="52.000004"
y="3.999995"
width="16"
height="16"
id="rect4165" />
<rect
id="rect4175"
height="16"
width="16"
y="3.9999852"
x="172.00002"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
id="rect4175-3"
height="16"
width="16"
y="3.999995"
x="196"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<g
id="g4299"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<g
id="g4299-3"
transform="matrix(0.70710678,-0.70710678,0.70710678,0.70710678,19.029435,12.000001)"
style="stroke:none">
<rect
x="7.0000048"
y="10.999998"
width="9.9999924"
height="1.9999986"
id="svg_1-1-0"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
<rect
x="11.000005"
y="7.0000114"
width="1.9999955"
height="9.9999838"
id="svg_1-1-1-9"
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0" />
</g>
<rect
id="svg_1-7-5"
height="6.9999905"
width="6.9999909"
y="7.0000048"
x="55.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#4c4c4c;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="58"
y="10.00001"
width="6.9999909"
height="6.9999905"
id="rect4354" />
<rect
id="svg_1-7-5-7"
height="6.9999905"
width="6.9999909"
y="10.000005"
x="58.000004"
style="fill:#ffffff;fill-opacity:1;stroke:#3c80df;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<g
id="g4378">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="10.999999"
width="7.9999909"
height="1.9999965"
id="svg_1-7-5-3" />
<rect
id="rect4374"
height="1.9999946"
width="11.999995"
y="7.0000005"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
<rect
id="rect4376"
height="1.9999995"
width="3.9999928"
y="14.999996"
x="198"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0" />
</g>
<g
transform="matrix(1,0,0,-1,-23.999995,23.999995)"
id="g4383">
<rect
id="rect4385"
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="rect4387" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0"
x="198"
y="14.999996"
width="3.9999928"
height="1.9999995"
id="rect4389" />
</g>
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-4"
width="16"
height="16"
x="76"
y="3.9999199" />
<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.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"
id="path4351"
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.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"
id="path4351-9"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-25"
width="16"
height="16"
x="100"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
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"
id="path2987"
inkscape:connector-curvature="0" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
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"
id="path2987-1"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-73"
width="16"
height="16"
x="124"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
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"
id="path3780"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
d="m 129.72704,13.478838 4.60852,0.01 -2.30426,-5.5497996 z"
id="path3782"
inkscape:connector-curvature="0" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none"
id="rect3754-35"
width="16"
height="16"
x="148"
y="3.9999199" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 156.47655,5.8917384 0,2.1797 0.46093,2.3983996 1.82813,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 152.51561,5.8906384 0,2.1797 0.46094,2.3983996 1.82812,0 0.39844,-2.3983996 0,-2.1797 z"
id="path5008-2-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.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-0"
height="1.9999945"
width="15.99999"
y="105"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-7"
height="1.9999945"
width="9"
y="109"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1"
height="1.9999945"
width="12"
y="125"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4"
height="1.9999945"
width="10"
y="137"
x="76.000008"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4"
height="1.9999945"
width="10"
y="129"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<rect
id="rect4761-1-1-4-4-3"
height="1.9999945"
width="9"
y="133"
x="82"
style="fill:#ffffff;fill-opacity:0.8;stroke:none;stroke-width:0" />
<path
inkscape:connector-curvature="0"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:0.8;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.66157866;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
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.96599996;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.96599996;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.96599996;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.96599996;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.96599996;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.96599996;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" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:#ffffff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:0.8"
d="M 99.994369,113.0221 102,114.98353 l 7,-6.9558 3,0.97227 2,-1 1,-2 0,-3 -3,3 -3,-3 3,-3 -3,0 -2,1 -1,2 0.99437,3.0221 z"
id="path4268"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccc" />
<rect
id="rect4175-3-5"
height="16"
width="16"
y="4"
x="220"
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0" />
<path
style="fill:#ffffff;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 234,6 0,2 -5,5 0,5 -2,0 0,-5 -5,-5 0,-2"
id="path3546"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<g
transform="matrix(1.3333328,0,0,-1.5999992,-139.9999,127.19999)"
id="g4383-6">
<rect
id="rect4385-2"
height="1.2499905"
width="5.9999924"
y="12.625005"
x="198.00002"
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="15.125007"
width="7.4999928"
height="1.2499949"
id="rect4387-9" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="7.6250024"
width="2.9999909"
height="1.2499905"
id="rect4389-1-0" />
<rect
style="fill:#ffffff;fill-opacity:0.8;stroke:#000000;stroke-width:0"
x="198.00002"
y="10.125004"
width="4.4999919"
height="1.2499905"
id="rect4389-1-9" />
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 207.00001,16.375004 0,-5.625005 -2.25,0 3,-3.1250014 3,3.1250014 -2.25,0 0,5.625005 -1.5,0"
id="path4402"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
</g>
<path
style="fill:#ffffff;fill-opacity:0.8;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 164,100 0,3 -6,6 0,7 -4,0 0,-7 -6,-6 0,-3"
id="path3546-2-2"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:#4c4c4c;fill-opacity:1;stroke:none;stroke-width:0"
id="svg_1-3"
height="16"
width="16"
y="28"
x="4" />
<path
sodipodi:nodetypes="ccccccccc"
inkscape:connector-curvature="0"
id="path4402-5-7"
d="m 15,41 0,-7 -4,0 0,3 -5,-4 5,-4 0,3 6,0 0,9"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.68465352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,11 +0,0 @@
@import "jsoneditor/reset";
@import "jsoneditor/variables";
@import "jsoneditor/autocomplete";
@import "jsoneditor/contextmenu";
@import "jsoneditor/editor";
@import "jsoneditor/menu";
@import "jsoneditor/navigationbar";
@import "jsoneditor/searchbox";
@import "jsoneditor/statusbar";
@import "jsoneditor/treepath";
@import "./../js/assets/selectr/selectr";

View File

@ -1,30 +0,0 @@
.jsoneditor {
.autocomplete {
&.dropdown {
position: absolute;
background: $jse-white;
box-shadow: $jse-box-shadow;
border: 1px solid $jse-bar-border;
overflow-x: hidden;
overflow-y: auto;
cursor: default;
margin: 0;
padding: 5px;
text-align: left;
outline: 0;
font-family: $jse-font-mono;
font-size: $jse-font-size;
.item {
color: #333;
&.hover {
background-color: #ddd;
}
}
}
&.hint {
color: #aaa;
top: 4px;
left: 4px;
}
}
}

View File

@ -1,443 +0,0 @@
.jsoneditor-contextmenu-root {
position: relative;
width: 0;
height: 0;
}
.jsoneditor-contextmenu {
position: absolute;
box-sizing: content-box;
z-index: 1;
.jsoneditor-menu {
position: relative;
left: 0;
top: 0;
width: 128px;
height: auto;
background: $jse-white;
border: 1px solid $jse-bar-border;
box-shadow: $jse-box-shadow;
list-style: none;
margin: 0;
padding: 0;
button {
position: relative;
padding: 0 4px 0 0;
margin: 0;
width: 128px;
height: auto;
border: none;
cursor: pointer;
color: $jse-contextmenu-color;
background: transparent;
font-size: $jse-font-size;
font-family: $jse-font;
box-sizing: border-box;
text-align: left;
&::-moz-focus-inner {
padding: 0;
border: 0;
}
&.jsoneditor-default {
width: 96px;
}
&.jsoneditor-expand {
float: right;
width: 32px;
height: 24px;
border-left: 1px solid $jse-separator;
}
}
li {
overflow: hidden;
ul {
display: none;
position: relative;
left: -10px;
top: 0;
border: none;
box-shadow: $jse-box-shadow-inner;
padding: 0 10px;
-webkit-transition: all 0.3s ease-out;
-moz-transition: all 0.3s ease-out;
-o-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
.jsoneditor-icon {
margin-left: 24px;
}
li {
button {
padding-left: 24px;
animation: all ease-in-out 1s;
}
}
}
button {
.jsoneditor-expand {
position: absolute;
top: 0;
right: 0;
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url($jse-icons) 0 -72px;
}
}
}
}
.jsoneditor-icon {
position: absolute;
top: 0;
left: 0;
width: 24px;
height: 24px;
border: none;
padding: 0;
margin: 0;
background-image: url($jse-icons);
}
.jsoneditor-text {
padding: 4px 0 4px 24px;
word-wrap: break-word;
&.jsoneditor-right-margin {
padding-right: 24px;
}
}
.jsoneditor-separator {
height: 0;
border-top: 1px solid $jse-separator;
padding-top: 5px;
margin-top: 5px;
}
button {
&.jsoneditor-remove {
.jsoneditor-icon {
background-position: -24px 0;
}
}
&.jsoneditor-append {
.jsoneditor-icon {
background-position: 0 0;
}
}
&.jsoneditor-insert {
.jsoneditor-icon {
background-position: 0 0;
}
}
&.jsoneditor-duplicate {
.jsoneditor-icon {
background-position: -48px 0;
}
}
&.jsoneditor-sort-asc {
.jsoneditor-icon {
background-position: -168px 0;
}
}
&.jsoneditor-sort-desc {
.jsoneditor-icon {
background-position: -192px 0;
}
}
&.jsoneditor-transform {
.jsoneditor-icon {
background-position: -216px 0;
}
}
&.jsoneditor-extract {
.jsoneditor-icon {
background-position: 0 -24px;
}
}
&.jsoneditor-type-string {
.jsoneditor-icon {
background-position: -144px 0;
}
}
&.jsoneditor-type-auto {
.jsoneditor-icon {
background-position: -120px 0;
}
}
&.jsoneditor-type-object {
.jsoneditor-icon {
background-position: -72px 0;
}
}
&.jsoneditor-type-array {
.jsoneditor-icon {
background-position: -96px 0;
}
}
&.jsoneditor-type-modes {
.jsoneditor-icon {
background-image: none;
width: 6px;
}
}
}
}
.jsoneditor-contextmenu ul,
.jsoneditor-contextmenu li {
box-sizing: content-box;
position: relative;
}
.jsoneditor-contextmenu .jsoneditor-menu button:hover,
.jsoneditor-contextmenu .jsoneditor-menu button:focus {
color: $jse-content-color;
background-color: $jse-preview;
outline: none;
}
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected,
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:hover,
.jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected:focus {
color: $jse-white;
background-color: $jse-number;
}
.jsoneditor-contextmenu .jsoneditor-menu li ul li button:hover,
.jsoneditor-contextmenu .jsoneditor-menu li ul li button:focus {
background-color: $jse-preview;
}
.jsoneditor-modal {
max-width: 95%;
border-radius: 2px !important;
padding: 45px 15px 15px 15px !important;
box-shadow: $jse-box-shadow;
color: $jse-contextmenu-color;
line-height: 1.3em;
&.jsoneditor-modal-transform {
width: 600px !important;
}
.pico-modal-header {
position: absolute;
box-sizing: border-box;
top: 0;
left: 0;
width: 100%;
padding: 0 10px;
height: 30px;
line-height: 30px;
font-family: $jse-font;
font-size: 11pt;
background: $jse-blue;
color: $jse-white;
}
table {
width: 100%;
td {
padding: 3px 0;
&.jsoneditor-modal-input {
text-align: right;
padding-right: 0;
white-space: nowrap;
}
&.jsoneditor-modal-actions {
padding-top: 15px;
}
}
th {
vertical-align: middle;
}
}
p {
&:first-child {
margin-top: 0;
}
}
a {
color: $jse-blue;
}
.jsoneditor-jmespath-block {
margin-bottom: 10px;
}
.pico-close {
background: none !important;
font-size: 24px !important;
top: 7px !important;
right: 7px !important;
color: $jse-white;
}
input {
padding: 4px;
}
input[type="text"] {
cursor: inherit;
}
input[disabled] {
background: $jse-empty;
color: $jse-readonly;
}
.jsoneditor-select-wrapper {
position: relative;
display: inline-block;
&:after {
content: "";
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 6px solid #666;
position: absolute;
right: 8px;
top: 14px;
pointer-events: none;
}
}
select {
padding: 3px 24px 3px 10px;
min-width: 180px;
max-width: 350px;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
text-indent: 0;
text-overflow: "";
font-size: $jse-font-size;
line-height: 1.5em;
&::-ms-expand {
display: none;
}
}
.jsoneditor-button-group {
input {
padding: 4px 10px;
margin: 0;
border-radius: 0;
border-left-style: none;
&.jsoneditor-button-first {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-left-style: solid;
}
&.jsoneditor-button-last {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
}
}
.jsoneditor-transform-preview {
background: $jse-preview;
height: 200px;
&.jsoneditor-error {
color: $jse-number;
}
}
.jsoneditor-jmespath-wizard {
line-height: 1.2em;
width: 100%;
padding: 0;
border-radius: 3px;
}
.jsoneditor-jmespath-label {
font-weight: bold;
color: dodgerblue;
margin-top: 20px;
margin-bottom: 5px;
}
.jsoneditor-jmespath-wizard-table {
width: 100%;
border-collapse: collapse;
}
.jsoneditor-jmespath-wizard-label {
font-style: italic;
margin: 4px 0 2px 0;
}
.jsoneditor-inline {
position: relative;
display: inline-block;
width: 100%;
padding-top: 2px;
padding-bottom: 2px;
&:not(:last-child) {
padding-right: 2px;
}
}
.jsoneditor-jmespath-filter {
display: flex;
flex-wrap: wrap;
}
.jsoneditor-jmespath-filter-field {
width: 180px;
}
.jsoneditor-jmespath-filter-relation {
width: 100px;
}
.jsoneditor-jmespath-filter-value {
min-width: 180px;
flex: 1;
}
.jsoneditor-jmespath-sort-field {
width: 170px;
}
.jsoneditor-jmespath-sort-order {
width: 150px;
}
.jsoneditor-jmespath-select-fields {
width: 100%;
}
.selectr-selected {
border-color: $jse-bar-border;
padding: 4px 28px 4px 8px;
.selectr-tag {
background-color: $jse-blue;
border-radius: 5px;
}
}
}
.jsoneditor-modal table th,
.jsoneditor-modal table td {
text-align: left;
vertical-align: middle;
font-weight: normal;
color: $jse-contextmenu-color;
border-spacing: 0;
border-collapse: collapse;
}
.jsoneditor-modal select,
.jsoneditor-modal textarea,
.jsoneditor-modal input,
.jsoneditor-modal input[type="text"],
.jsoneditor-modal input[type="text"]:focus,
.jsoneditor-modal #query {
background: #ffffff;
border: 1px solid $jse-bar-border;
color: $jse-contextmenu-color;
border-radius: 3px;
padding: 4px;
}
.jsoneditor-modal,
.jsoneditor-modal table td,
.jsoneditor-modal table th,
.jsoneditor-modal select,
.jsoneditor-modal option,
.jsoneditor-modal textarea,
.jsoneditor-modal input,
.jsoneditor-modal input[type="text"],
.jsoneditor-modal #query {
font-size: 10.5pt;
font-family: $jse-font;
}
.jsoneditor-modal #query,
.jsoneditor-modal .jsoneditor-transform-preview {
font-family: $jse-font-mono;
font-size: $jse-font-size;
width: 100%;
box-sizing: border-box;
}
.jsoneditor-modal input[type="button"],
.jsoneditor-modal input[type="submit"] {
background: $jse-preview;
padding: 4px 20px;
}
.jsoneditor-modal select,
.jsoneditor-modal input {
cursor: pointer;
}
.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-asc input.jsoneditor-button-asc,
.jsoneditor-modal .jsoneditor-button-group.jsoneditor-button-group-value-desc input.jsoneditor-button-desc {
background: $jse-blue;
border-color: $jse-blue;
color: $jse-white;
}

View File

@ -1,583 +0,0 @@
.jsoneditor {
color: $jse-content-color;
border: thin solid $jse-blue;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 100%;
height: 100%;
position: relative;
padding: 0;
line-height: 100%;
}
div.jsoneditor-field,
div.jsoneditor-value,
div.jsoneditor-readonly,
div.jsoneditor-default {
border: 1px solid transparent;
min-height: 16px;
min-width: 32px;
padding: 2px;
margin: 1px;
word-wrap: break-word;
float: left;
}
div.jsoneditor-field p,
div.jsoneditor-value p {
margin: 0;
}
div {
&.jsoneditor-value {
word-break: break-word;
&.jsoneditor-empty {
&::after {
content: "value";
}
}
&.jsoneditor-string {
color: $jse-string;
}
&.jsoneditor-number {
color: $jse-number;
}
&.jsoneditor-boolean {
color: $jse-boolean;
}
&.jsoneditor-null {
color: $jse-null;
}
&.jsoneditor-invalid {
color: $jse-invalid;
}
}
&.jsoneditor-readonly {
min-width: 16px;
color: $jse-readonly;
}
&.jsoneditor-empty {
border-color: $jse-bar-border;
border-style: dashed;
border-radius: 2px;
}
&.jsoneditor-field {
&.jsoneditor-empty {
&::after {
content: "field";
}
}
}
&.jsoneditor {
td {
vertical-align: top;
&.jsoneditor-separator {
padding: 3px 0;
vertical-align: top;
color: $jse-readonly;
}
&.jsoneditor-tree {
vertical-align: top;
}
}
&.busy {
pre {
&.jsoneditor-preview {
background: $jse-preview;
color: $jse-readonly;
}
}
div {
&.jsoneditor-busy {
display: inherit;
}
}
}
code {
&.jsoneditor-preview {
background: none;
}
}
&.jsoneditor-mode-preview {
pre {
&.jsoneditor-preview {
width: 100%;
height: 100%;
box-sizing: border-box;
overflow: auto;
padding: 2px;
margin: 0;
white-space: pre-wrap;
word-break: break-all;
}
}
}
}
&.jsoneditor-default {
color: $jse-readonly;
padding-left: 10px;
}
&.jsoneditor-tree {
width: 100%;
height: 100%;
position: relative;
overflow: auto;
button {
&.jsoneditor-button {
width: 24px;
height: 24px;
padding: 0;
margin: 0;
border: none;
cursor: pointer;
background: transparent url($jse-icons);
&:focus {
background-color: $jse-preview;
outline: #e5e5e5 solid 1px;
}
}
&.jsoneditor-collapsed {
background-position: 0 -48px;
}
&.jsoneditor-expanded {
background-position: 0 -72px;
}
&.jsoneditor-contextmenu-button {
background-position: -48px -72px;
}
&.jsoneditor-invisible {
visibility: hidden;
background: none;
}
&.jsoneditor-dragarea {
background: url($jse-icons) -72px -72px;
cursor: move;
}
}
*:focus {
outline: none;
}
div {
&.jsoneditor-show-more {
display: inline-block;
padding: 3px 4px;
margin: 2px 0;
background-color: $jse-separator;
border-radius: 3px;
color: $jse-readonly;
font-family: $jse-font;
font-size: $jse-font-size;
a {
display: inline-block;
color: $jse-readonly;
}
}
&.jsoneditor-color {
display: inline-block;
width: 12px;
height: 12px;
margin: 4px;
border: 1px solid $jse-readonly;
cursor: pointer;
}
&.jsoneditor-date {
background: $jse-date;
color: $jse-white;
font-family: $jse-font;
border-radius: 3px;
display: inline-block;
padding: 3px;
margin: 0 3px;
}
}
table {
&.jsoneditor-tree {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
}
}
.jsoneditor-button {
&.jsoneditor-schema-error {
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
background: url($jse-icons) -168px -48px;
}
}
}
&.jsoneditor-outer {
position: static;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
&.has-nav-bar {
margin-top: -26px;
padding-top: 26px;
&.has-main-menu-bar {
margin-top: -61px;
padding-top: 61px;
}
}
&.has-status-bar {
margin-bottom: -26px;
padding-bottom: 26px;
}
&.has-main-menu-bar {
margin-top: -35px;
padding-top: 35px;
}
}
&.jsoneditor-busy {
position: absolute;
top: 15%;
left: 0;
box-sizing: border-box;
width: 100%;
text-align: center;
display: none;
span {
background-color: $jse-busy;
border: 1px solid $jse-busy-border-color;
border-radius: 3px;
padding: 5px 15px;
box-shadow: $jse-box-shadow-sm;
}
}
}
div.jsoneditor-field.jsoneditor-empty::after,
div.jsoneditor-value.jsoneditor-empty::after {
pointer-events: none;
color: $jse-empty;
font-size: 8pt;
}
div.jsoneditor-value.jsoneditor-url,
a.jsoneditor-value.jsoneditor-url {
color: $jse-string;
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: $jse-number;
}
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: $jse-busy;
border: 1px solid $jse-busy-border-color;
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: $jse-highlight-bg;
border: 1px solid $jse-highlight-border-color;
border-radius: 2px;
}
div.jsoneditor-value.jsoneditor-object,
div.jsoneditor-value.jsoneditor-array {
min-width: 16px;
}
div.jsoneditor-tree button.jsoneditor-contextmenu-button:hover,
div.jsoneditor-tree button.jsoneditor-contextmenu-button:focus,
div.jsoneditor-tree button.jsoneditor-contextmenu-button.jsoneditor-selected,
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button {
background-position: -48px -48px;
}
div.jsoneditor-tree div.jsoneditor-show-more a:hover,
div.jsoneditor-tree div.jsoneditor-show-more a:focus {
color: $jse-number;
}
textarea.jsoneditor-text,
.ace-jsoneditor {
min-height: 150px;
* {
font-family: $jse-font-mono;
}
}
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: $jse-white;
resize: none;
}
}
tr.jsoneditor-highlight,
tr.jsoneditor-selected {
background-color: $jse-empty;
}
tr.jsoneditor-selected button.jsoneditor-dragarea,
tr.jsoneditor-selected button.jsoneditor-contextmenu-button {
visibility: hidden;
}
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-dragarea,
tr.jsoneditor-selected.jsoneditor-first button.jsoneditor-contextmenu-button {
visibility: visible;
}
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-field,
div.jsoneditor-value,
div.jsoneditor td,
div.jsoneditor th,
div.jsoneditor textarea,
pre.jsoneditor-preview,
.jsoneditor-schema-error,
.jsoneditor-popover{
font-family: $jse-font-mono;
font-size: $jse-font-size;
color: $jse-content-color;
}
.jsoneditor-schema-error {
cursor: default;
display: inline-block;
height: 24px;
line-height: 24px;
position: relative;
text-align: center;
width: 24px;
}
.jsoneditor-popover {
background-color: $jse-popover-bg;
border-radius: 3px;
box-shadow: $jse-box-shadow-sm;
color: $jse-white;
padding: 7px 10px;
position: absolute;
cursor: auto;
width: 200px;
&.jsoneditor-above {
bottom: 32px;
left: -98px;
&:before {
border-top: 7px solid $jse-popover-bg;
bottom: -7px;
}
}
&.jsoneditor-below {
top: 32px;
left: -98px;
&:before {
border-bottom: 7px solid $jse-popover-bg;
top: -7px;
}
}
&.jsoneditor-left {
top: -7px;
right: 32px;
&:before {
border-left: 7px solid $jse-popover-bg;
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-right {
top: -7px;
left: 32px;
&:before {
border-right: 7px solid $jse-popover-bg;
border-top: 7px solid transparent;
border-bottom: 7px solid transparent;
content: "";
top: 19px;
left: -14px;
margin-left: inherit;
margin-top: -7px;
position: absolute;
}
}
&:before {
border-right: 7px solid transparent;
border-left: 7px solid transparent;
content: "";
display: block;
left: 50%;
margin-left: -7px;
position: absolute;
}
}
.jsoneditor-text-errors {
tr {
&.jump-to-line {
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
}
.jsoneditor-schema-error:hover .jsoneditor-popover,
.jsoneditor-schema-error:focus .jsoneditor-popover {
display: block;
animation: fade-in 0.3s linear 1, move-up 0.3s linear 1;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
.jsoneditor {
.jsoneditor-validation-errors-container {
max-height: 130px;
overflow-y: auto;
}
.jsoneditor-validation-errors {
width: 100%;
overflow: hidden;
}
.jsoneditor-additional-errors {
position: absolute;
margin: auto;
bottom: 31px;
left: calc(50% - 92px);
color: $jse-readonly;
background-color: $jse-light-bg;
padding: 7px 15px;
border-radius: 8px;
&.visible {
visibility: visible;
opacity: 1;
transition: opacity 2s linear;
}
&.hidden {
visibility: hidden;
opacity: 0;
transition: visibility 0s 2s, opacity 2s linear;
}
}
.jsoneditor-text-errors {
width: 100%;
border-collapse: collapse;
border-top: 1px solid $jse-highlight-border-color;
td {
padding: 3px 6px;
vertical-align: middle;
pre {
margin: 0;
white-space: normal;
}
}
tr {
background-color: $jse-busy;
&.parse-error {
background-color: $jse-error;
}
}
}
}
.jsoneditor-text-errors {
.jsoneditor-schema-error {
border: none;
width: 24px;
height: 24px;
padding: 0;
margin: 0 4px 0 0;
cursor: pointer;
}
tr {
.jsoneditor-schema-error {
background: url($jse-icons) -168px -48px;
}
&.parse-error {
.jsoneditor-schema-error {
background: url($jse-icons) -25px 0px;
}
}
}
}
.jsoneditor-anchor {
cursor: pointer;
.picker_wrapper {
&.popup {
&.popup_bottom {
top: 28px;
left: -10px;
}
}
}
}
.fadein {
-webkit-animation: fadein 0.3s;
animation: fadein 0.3s;
-moz-animation: fadein 0.3s;
-o-animation: fadein 0.3s;
}
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
// override some styles which where cleared in reset.scss
.jsoneditor-modal {
input[type="search"].selectr-input {
border: 1px solid #d3d3d3;
width: calc(100% - 4px);
margin: 2px;
padding: 4px;
box-sizing: border-box;
}
button.selectr-input-clear {
right: 8px;
}
}

View File

@ -1,126 +0,0 @@
.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: $jse-white;
background-color: $jse-blue;
border-bottom: 1px solid $jse-blue;
}
.jsoneditor-menu > button,
.jsoneditor-menu > .jsoneditor-modes > button {
width: 26px;
height: 26px;
margin: 2px;
padding: 0;
border-radius: 2px;
border: 1px solid transparent;
background: transparent url($jse-icons);
color: $jse-white;
opacity: 0.8;
font-family: $jse-font;
font-size: $jse-font-size;
float: left;
}
.jsoneditor-menu > button:hover,
.jsoneditor-menu > .jsoneditor-modes > button:hover {
background-color: rgba(255,255,255,0.2);
border: 1px solid rgba(255,255,255,0.4);
}
.jsoneditor-menu > button:focus,
.jsoneditor-menu > button:active,
.jsoneditor-menu > .jsoneditor-modes > button:focus,
.jsoneditor-menu > .jsoneditor-modes > button:active {
background-color: rgba(255,255,255,0.3);
}
.jsoneditor-menu > button:disabled,
.jsoneditor-menu > .jsoneditor-modes > button:disabled {
opacity: 0.5;
background-color: transparent;
border: none;
}
.jsoneditor-menu > button.jsoneditor-collapse-all {
background-position: 0 -96px;
}
.jsoneditor-menu > button.jsoneditor-expand-all {
background-position: 0 -120px;
}
.jsoneditor-menu > button.jsoneditor-sort {
background-position: -120px -96px;
}
.jsoneditor-menu > button.jsoneditor-transform {
background-position: -144px -96px;
}
.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-sort,
.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-sort,
.jsoneditor.jsoneditor-mode-view > .jsoneditor-menu > button.jsoneditor-transform,
.jsoneditor.jsoneditor-mode-form > .jsoneditor-menu > button.jsoneditor-transform {
display: none;
}
.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-menu > button.jsoneditor-repair {
background-position: -96px -96px;
}
.jsoneditor-menu > .jsoneditor-modes {
display: inline-block;
float: left;
}
.jsoneditor-menu > .jsoneditor-modes > button {
background-image: none;
width: auto;
padding-left: 6px;
padding-right: 6px;
}
.jsoneditor-menu > button.jsoneditor-separator,
.jsoneditor-menu > .jsoneditor-modes > button.jsoneditor-separator {
margin-left: 10px;
}
.jsoneditor-menu a {
font-family: $jse-font;
font-size: $jse-font-size;
color: $jse-white;
opacity: 0.8;
vertical-align: middle;
}
.jsoneditor-menu a:hover {
opacity: 1;
}
.jsoneditor-menu a.jsoneditor-poweredBy {
font-size: 8pt;
position: absolute;
right: 0;
top: 0;
padding: 10px;
}

View File

@ -1,17 +0,0 @@
.jsoneditor-navigation-bar {
width: 100%;
height: 26px;
line-height: 26px;
padding: 0;
margin: 0;
border-bottom: 1px solid $jse-bar-border;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: $jse-readonly;
background-color: $jse-light-bg;
overflow: hidden;
font-family: $jse-font;
font-size: $jse-font-size;
}

View File

@ -1,51 +0,0 @@
.jsoneditor,
.jsoneditor-modal {
input,
input:not([type]),
input[type="text"],
input[type="search"], {
height: auto;
border: inherit;
box-shadow: none;
font-size: inherit;
box-sizing: inherit;
padding: inherit;
font-family: inherit;
transition: none;
line-height: inherit;
&:focus {
border: inherit;
box-shadow: inherit;
}
}
textarea {
height: inherit;
}
select {
display: inherit;
height: inherit;
}
label {
font-size: inherit;
font-weight: inherit;
color: inherit;
}
table {
border-collapse: collapse;
width: auto;
}
td,
th {
padding: 0;
display: table-cell;
text-align: left;
vertical-align: inherit;
border-radius: inherit;
}
}

View File

@ -1,72 +0,0 @@
.jsoneditor {
&-search {
font-family: $jse-font;
position: absolute;
right: 4px;
top: 4px;
border-collapse: collapse;
border-spacing: 0;
display: flex;
input {
color: $jse-content-color;
width: 120px;
border: none;
outline: none;
margin: 1px;
line-height: 20px;
font-family: $jse-font;
}
button {
width: 16px;
height: 24px;
padding: 0;
margin: 0;
border: none;
background: url($jse-icons);
vertical-align: top;
&:hover {
background-color: transparent;
}
&.jsoneditor-refresh {
width: 18px;
background-position: -99px -73px;
}
&.jsoneditor-next {
cursor: pointer;
background-position: -124px -73px;
&:hover {
background-position: -124px -49px;
}
}
&.jsoneditor-previous {
cursor: pointer;
background-position: -148px -73px;
margin-right: 2px;
&:hover {
background-position: -148px -49px;
}
}
}
}
&-results {
font-family: $jse-font;
color: $jse-white;
padding-right: 5px;
line-height: 26px;
}
&-frame {
border: 1px solid transparent;
background-color: $jse-white;
padding: 0 2px;
margin: 0;
}
}

View File

@ -1,48 +0,0 @@
.jsoneditor-statusbar {
line-height: 26px;
height: 26px;
color: $jse-readonly;
background-color: $jse-bar-bg;
border-top: 1px solid $jse-bar-border;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
font-size: $jse-font-size;
& > .jsoneditor-curserinfo-val {
margin-right: 12px;
}
& > .jsoneditor-curserinfo-count {
margin-left: 4px;
}
& > .jsoneditor-validation-error-icon {
float: right;
width: 24px;
height: 24px;
padding: 0;
margin-top: 1px;
background: url($jse-icons) -168px -48px;
cursor: pointer;
}
& > .jsoneditor-validation-error-count {
float: right;
margin: 0 4px 0 0;
cursor: pointer;
}
& > .jsoneditor-parse-error-icon {
float: right;
width: 24px;
height: 24px;
padding: 0;
margin: 1px;
background: url($jse-icons) -25px 0px;
}
.jsoneditor-array-info {
a {
color: inherit;
}
}
}
div.jsoneditor-statusbar > .jsoneditor-curserinfo-label,
div.jsoneditor-statusbar > .jsoneditor-size-info {
margin: 0 4px;
}

View File

@ -1,48 +0,0 @@
.jsoneditor-treepath {
padding: 0 5px;
overflow: hidden;
white-space: nowrap;
outline: none;
&.show-all {
word-wrap: break-word;
white-space: normal;
position: absolute;
background-color: $jse-light-bg;
z-index: 1;
box-shadow: $jse-box-shadow;
span {
&.jsoneditor-treepath-show-all-btn {
display: none;
}
}
}
div {
&.jsoneditor-contextmenu-root {
position: absolute;
left: 0;
}
}
.jsoneditor-treepath-show-all-btn {
position: absolute;
background-color: $jse-light-bg;
left: 0;
height: 20px;
padding: 0 3px;
cursor: pointer;
}
.jsoneditor-treepath-element {
margin: 1px;
font-family: $jse-font;
font-size: $jse-font-size;
}
.jsoneditor-treepath-seperator {
margin: 2px;
font-size: 9pt;
font-family: $jse-font;
}
}
.jsoneditor-treepath span.jsoneditor-treepath-element:hover,
.jsoneditor-treepath span.jsoneditor-treepath-seperator:hover {
cursor: pointer;
text-decoration: underline;
}

View File

@ -1,41 +0,0 @@
$jse-white: #ffffff !default;
$jse-grey:#999999 !default;
$jse-light-bg: #ebebeb !default;
$jse-blue: #3883fa !default;
$jse-content-color: #1a1a1a !default;
$jse-string: #006000 !default;
$jse-number: #ee422e !default;
$jse-boolean: #ff8c00 !default;
$jse-null: #004ed0 !default;
$jse-invalid: #000000 !default;
$jse-readonly: #808080 !default;
$jse-empty: #d3d3d3 !default;
$jse-preview: #f5f5f5 !default;
$jse-busy: #ffffab !default;
$jse-busy-border-color: #ffee00 !default;
$jse-error: #ee2e2e70 !default;
$jse-separator: #e5e5e5 !default;
$jse-highlight-bg: #ffee00 !default;
$jse-highlight-border-color: #ffc700 !default;
$jse-popover-bg: #4c4c4c !default;
$jse-bar-bg: $jse-light-bg !default;
$jse-bar-border: $jse-empty !default;
$jse-menu-color: $jse-empty !default;
$jse-contextmenu-color: #4d4d4d !default;
$jse-box-shadow: 2px 2px 12px rgba(128, 128, 128, 0.3) !default;
$jse-box-shadow-sm: 0 0 5px rgba(0, 0, 0, 0.4) !default;
$jse-box-shadow-inner: inset 0 0 10px rgba(128, 128, 128, 0.5) !default;
$jse-date: #a1a1a1 !default;
$jse-font: arial, sans-serif !default;
$jse-font-mono: "dejavu sans mono", "droid sans mono", consolas, monaco, "lucida console", "courier new", courier, monospace, sans-serif !default;
$jse-font-size: 10pt !default;;
$jse-icons: "./img/jsoneditor-icons.svg" !default;

67
src/utils.js Normal file
View File

@ -0,0 +1,67 @@
export const SEARCH_PROPERTY = Symbol('searchProperty')
export const SEARCH_VALUE = Symbol('searchValue')
export function getType (json) {
return Array.isArray(json)
? 'array'
: json && typeof json === 'object'
? 'object'
: 'value'
}
export function search (key, value, searchText) {
let results
if (typeof key === 'string' && containsCaseInsensitive(key, searchText)) {
results = createOrAdd(results, SEARCH_PROPERTY, true)
}
const type = getType(value)
if (type === 'array') {
value.forEach((item, index) => {
let childResults = search(index, item, searchText)
if (childResults) {
results = createOrAdd(results, index, childResults)
}
})
} else if (type === 'object') {
Object.keys(value).forEach(prop => {
let childResults = search(prop, value[prop], searchText)
if (childResults) {
results = createOrAdd(results, prop, childResults)
}
})
} else { // type === 'value'
if (containsCaseInsensitive(value, searchText)) {
results = createOrAdd(results, SEARCH_VALUE, true)
}
}
return results
}
function createOrAdd(object, key, value) {
if (object) {
object[key] = value
return object
// return {
// ...object,
// [key]: value
// }
} else {
return {
[key]: value
}
}
}
/**
* Do a case insensitive search for a search text in a text
* @param {String} text
* @param {String} searchText
* @return {boolean} Returns true if `search` is found in `text`
*/
export function containsCaseInsensitive (text, searchText) {
return String(text).toLowerCase().indexOf(searchText.toLowerCase()) !== -1
}

13
src/utils.test.js Normal file
View File

@ -0,0 +1,13 @@
import { getType } from './utils.js'
import assert from 'assert'
describe('utils', () => {
it('should test equality of positive values', function () {
assert.strictEqual(getType([]), 'array')
assert.strictEqual(getType({}), 'object')
assert.strictEqual(getType(null), 'value')
assert.strictEqual(getType(2), 'value')
assert.strictEqual(getType('hello'), 'value')
assert.strictEqual(getType('hello'), 'value')
})
})

View File

@ -1,285 +0,0 @@
import assert from 'assert'
import './setup'
import { Node } from '../src/js/Node'
describe('Node', () => {
describe('_findSchema', () => {
it('should find schema', () => {
const schema = {
type: 'object',
properties: {
child: {
type: 'string'
}
}
}
const path = ['child']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.child)
})
it('should find schema inside an array item', () => {
const schema = {
properties: {
job: {
type: 'array',
items: {
type: 'object',
properties: {
company: {
enum: ['test1', 'test2']
}
}
}
}
}
}
assert.strictEqual(Node._findSchema(schema, {}, []), schema)
assert.strictEqual(Node._findSchema(schema, {}, ['job']), schema.properties.job)
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0]),
schema.properties.job.items)
assert.strictEqual(Node._findSchema(schema, {}, ['job', 0, 'company']),
schema.properties.job.items.properties.company)
})
it('should find schema within multi-level object properties', () => {
const schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'object',
properties: {
bool: {
type: 'boolean'
}
}
}
}
}
}
}
let path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema)
path = ['levelTwo']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo)
path = ['levelTwo', 'levelThree']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree)
path = ['levelTwo', 'levelThree', 'bool']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool
)
})
it('should return null for path that has no schema', () => {
const schema = {
type: 'object',
properties: {
foo: {
type: 'object',
properties: {
baz: {
type: 'number'
}
}
}
}
}
let path = ['bar']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
path = ['foo', 'bar']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
})
describe('with $ref', () => {
it('should find a referenced schema', () => {
const schema = {
type: 'object',
properties: {
foo: {
$ref: 'foo'
}
}
}
const fooSchema = {
type: 'number',
title: 'Foo'
}
const path = ['foo']
assert.strictEqual(Node._findSchema(schema, { foo: fooSchema }, path), fooSchema)
})
})
describe('with pattern properties', () => {
it('should find schema', () => {
const schema = {
type: 'object',
properties: {
str: {
title: 'str',
type: 'boolean'
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-] pattern property',
type: 'string'
}
}
}
let path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')
path = ['str']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.str, 'normal property')
})
it('should find schema within multi-level object properties', () => {
const schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'object',
properties: {
bool: {
title: 'bool',
type: 'boolean'
}
}
}
}
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
}
}
}
let path = []
assert.strictEqual(Node._findSchema(schema, {}, path), schema, 'top level')
path = ['levelTwo']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo, 'level two')
path = ['levelTwo', 'levelThree']
assert.strictEqual(Node._findSchema(schema, {}, path), schema.properties.levelTwo.properties.levelThree, 'level three')
path = ['levelTwo', 'levelThree', 'bool']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.properties.levelTwo.properties.levelThree.properties.bool,
'normal property'
)
})
it('should find schema for pattern properties', () => {
const schema = {
type: 'object',
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'string'
}
}
}
let path = ['foo1']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^foo[0-9]'],
'first pattern property'
)
path = ['bar5']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^bar[0-9]'],
'second pattern property'
)
})
it('should find schema for multi-level pattern properties', () => {
const schema = {
type: 'object',
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'object',
properties: {
fooChild: {
type: 'object',
properties: {
fooChild2: {
type: 'string'
}
}
}
}
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'object',
properties: {
barChild: {
type: 'string'
}
}
}
}
}
let path = ['foo1', 'fooChild', 'fooChild2']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^foo[0-9]'].properties.fooChild.properties.fooChild2,
'first pattern property child of child'
)
path = ['bar5', 'barChild']
assert.strictEqual(
Node._findSchema(schema, {}, path),
schema.patternProperties['^bar[0-9]'].properties.barChild,
'second pattern property child'
)
})
it('should return null for path that has no schema', () => {
const schema = {
type: 'object',
properties: {
levelTwo: {
type: 'object',
properties: {
levelThree: {
type: 'number'
}
}
}
},
patternProperties: {
'^foo[0-9]': {
title: 'foo[0-9] pattern property',
type: 'string'
},
'^bar[0-9]': {
title: 'bar[0-9] pattern property',
type: 'string'
}
}
}
let path = ['not-in-schema']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
path = ['levelOne', 'not-in-schema']
assert.strictEqual(Node._findSchema(schema, {}, path), null)
})
})
})
})

View File

@ -1,100 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>CouchDB Document Editor</title>
<meta name="description" content="CouchDB Document Editor">
<meta name="keywords" content="json, editor, couchdb, online, javascript, javascript object notation, treeview, open source, free">
<meta name="author" content="Jos de Jong">
<link rel="shortcut icon" href="../app/web/favicon.ico">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script src="../jsoneditor/js/jsoneditor.js"></script>
<link rel="stylesheet" type="text/css" href="../jsoneditor/css/jsoneditor.css">
<style type="text/css">
body, html {
font-family: arial, verdana;
font-size: 11pt;
width: 100%;
height: 100%;
margin: 0px;
padding: 0px;
}
h1 {
color: gray;
}
input[type=text] {
border: 1px solid gray;
}
</style>
<script>
var editor = null;
function init() {
var container = document.getElementById('jsoneditor');
editor = new JSONEditor(container);
document.getElementById('url').focus();
}
function load() {
var url = document.getElementById("url").value;
$.ajax({
'type': 'GET',
'url': url,
'dataType': 'json',
'success': function (doc) {
editor.set(doc);
}
});
}
function save() {
var doc = editor.get();
var url = document.getElementById("url").value;
$.ajax({
'type': 'PUT',
'url': url,
'data': JSON.stringify(doc),
'success': function (response) {
load();
}
});
}
</script>
</head>
<body onload="init();">
<table align="center" width="790px" height="100%">
<tr>
<td style="height: 50px;"><h1>CouchDB Document Editor</h1></td>
</tr>
<tr>
<td style="height: 30px;">
<table width="100%">
<col width="100px"></col>
<col ></col>
<col width="50px"></col>
<col width="50px"></col>
<tr>
<td>Document Url:</td>
<td><input type="text" id="url" style="width: 100%;" value="http://localhost:5984/test/jos"></td>
<td><input type="button" value="Load" onclick="load();" /></td>
<td><input type="button" value="Save" onclick="save();" /></td>
</tr>
</table>
</td>
</tr>
<tr>
<td id="jsoneditor"></td>
</tr>
</table>
</body>
</html>

View File

@ -1,95 +0,0 @@
import assert from 'assert'
import { stringifyPartial, containsArray } from '../src/js/jsonUtils'
describe('jsonUtils', () => {
it('should stringify a small object', () => {
const json = {
a: 2,
b: 'foo',
c: null,
d: false,
e: [1, 2, 3],
f: { g: 'h' }
}
assert.strictEqual(stringifyPartial(json), '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}')
})
it('should stringify a small object with formatting', () => {
const json = {
a: 2,
b: 'foo',
c: null,
d: false,
e: [1, 2, 3],
f: { g: 'h' }
}
assert.strictEqual(stringifyPartial(json, 2),
'{\n' +
' "a": 2,\n' +
' "b": "foo",\n' +
' "c": null,\n' +
' "d": false,\n' +
' "e": [\n' +
' 1,\n' +
' 2,\n' +
' 3\n' +
' ],\n' +
' "f": {\n' +
' "g": "h"\n' +
' }\n' +
'}')
assert.strictEqual(stringifyPartial(json, ' '), '{\n' +
' "a": 2,\n' +
' "b": "foo",\n' +
' "c": null,\n' +
' "d": false,\n' +
' "e": [\n' +
' 1,\n' +
' 2,\n' +
' 3\n' +
' ],\n' +
' "f": {\n' +
' "g": "h"\n' +
' }\n' +
'}')
})
it('should limit stringified output', () => {
const json = {
a: 2,
b: 'foo',
c: null,
d: false,
e: [1, 2, 3],
f: { g: 'h' }
}
const all = '{"a":2,"b":"foo","c":null,"d":false,"e":[1,2,3],"f":{"g":"h"}}'
const limit = 20
assert.strictEqual(stringifyPartial(json, undefined, limit),
all.slice(0, limit) + '...')
assert.strictEqual(stringifyPartial(json, undefined, all.length), all)
assert.strictEqual(stringifyPartial([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], undefined, 10),
'[1,2,3,4,5...')
assert.strictEqual(stringifyPartial(12345678, undefined, 4), '1234...')
})
it('should count array items', () => {
// assert.strictEqual(countArrayItems('[1,2,3]'), 3)
assert.strictEqual(containsArray('[]'), true)
assert.strictEqual(containsArray(' []'), true)
assert.strictEqual(containsArray(' \t []'), true)
assert.strictEqual(containsArray(' \t\n []'), true)
assert.strictEqual(containsArray('"["'), false)
assert.strictEqual(containsArray('2'), false)
assert.strictEqual(containsArray('null'), false)
assert.strictEqual(containsArray('{}'), false)
})
})

View File

@ -1,22 +0,0 @@
import { JSDOM } from 'jsdom'
/**
* Set up the test environment by simulating browser globals.
* @param {string} [locale=en] A locale to set in navigator.language
* @return {void}
*/
function setUpTestEnvironment (locale) {
if (!locale) {
locale = 'en'
}
const dom = new JSDOM('...')
global.window = dom.window
global.document = dom.window.document
global.navigator = dom.window.navigator
// JSDom has no setter defined for navigator.language, so defineProperty is necessary in order to override it
Object.defineProperty(navigator, 'language', { value: locale })
};
setUpTestEnvironment()

View File

@ -1,54 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css">
<!--<link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha/css/bootstrap.min.css" rel="stylesheet" type="text/css">-->
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Test with bootstrap
</p>
<div id="jsoneditor"></div>
<script>
var container = document.getElementById('jsoneditor');
var options = {
"mode": "tree",
"modes": [
"tree",
"form",
"code",
"text",
"view"
],
"history": false,
onChange: function () {
if (editor) {
// if you comment out the next line of code, the problem is solved
// editor.get() throws an exception when the editor does not
// contain valid JSON.
console.log('change', editor.get())
}
}
};
var editor = new JSONEditor(container, options);
var text = "[{\"test\":\"test\"}]";
var json = JSON.parse(text);
editor.set(json);
</script>
</body>
</html>

View File

@ -1,119 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<form>
<div id="jsoneditor"></div>
</form>
<p>
<button id="update">Update</button>
</p>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onChange: function () {
console.log('change');
},
onChangeJSON: function (json) {
console.log('onChangeJSON', json);
},
onChangeText: function (text) {
console.log('onChangeText', text);
},
onFocus: function(event) {
console.log("Focus : ",event);
},
onBlur: function(event) {
console.log("Blur : ",event);
},
indentation: 4,
escapeUnicode: true
};
json = {
"array": [1, 2, [3,4,5]],
"boolean": true,
"color": "#82b92c",
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"timestamp": 1534952749890,
"url": "http://jsoneditoronline.org"
};
editorTest = new JSONEditor(container, options, json);
console.log('json', json);
console.log('string', JSON.stringify(json));
const update = document.getElementById('update')
update.onclick = function () {
const json2 = {
"array": [1, 2, [3,4,5]],
"array2": [1, 2, [3,4,5]],
"url": "http://jsoneditoronline.org",
"boolean": true,
"color": "#82b92c",
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"timestamp": 1534952749890
};
editorTest.update(json2)
}
</script>
</body>
</html>

View File

@ -1,63 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.min.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<div id="jsoneditor"></div>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
}
};
json = {
"array": [1, 2, 3],
"boolean": true,
"color": "#82b92c",
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World"
};
editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -1,89 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<form>
<div id="jsoneditor"></div>
</form>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'code',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onChange: function () {
console.log('change');
},
onChangeJSON: function (json) {
console.log('onChangeJSON', json);
},
onChangeText: function (text) {
console.log('onChangeText', text);
},
indentation: 4,
escapeUnicode: true
};
var json = [];
for (var i = 0; i < 10000; i++) {
var longitude = 4 + i / 10000;
var latitude = 51 + i / 10000;
json.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude: longitude,
longitude: latitude,
coordinates: [longitude, latitude]
},
random: Math.random()
});
}
editorTest = new JSONEditor(container, options, json);
// console.log('json', json);
console.log('stringified size: ', Math.round(JSON.stringify(json).length / 1024 / 1024), 'MB');
</script>
</body>
</html>

View File

@ -1,92 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Test color picker firing onChange on every change instead of onDone.
</p>
<form>
<div id="jsoneditor"></div>
</form>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
onChangeJSON: function (json) {
console.log('onChangeJSON', json);
},
onColorPicker: function (parent, color, onChange) {
new JSONEditor.VanillaPicker({
parent: parent,
color: color,
popup: 'bottom',
onChange: function (color) {
console.log('onChange', color)
var alpha = color.rgba[3]
var hex = (alpha === 1)
? color.hex.substr(0, 7) // return #RRGGBB
: color.hex // return #RRGGBBAA
onChange(hex)
},
onDone: function (color) {
console.log('onDone', color)
},
onClose: function (color) {
console.log('onClose', color)
}
}).show();
}
};
json = {
"array": [1, 2, [3,4,5]],
"boolean": true,
"color": "#82b92c",
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"timestamp": 1534952749890,
"url": "http://jsoneditoronline.org"
};
editor = new JSONEditor(container, options, json);
console.log('json', json);
console.log('string', JSON.stringify(json));
</script>
</body>
</html>

View File

@ -1,87 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<form>
<div id="jsoneditor"></div>
</form>
<p>
<button id="destroy">Destroy</button>
</p>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onChange: function () {
console.log('change');
},
indentation: 4,
escapeUnicode: true
};
json = {
"array": [1, 2, [3,4,5]],
"boolean": true,
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"url": "http://jsoneditoronline.org"
};
editor = new JSONEditor(container, options, json);
console.log('json', json);
console.log('string', JSON.stringify(json));
document.querySelector('#destroy').onclick = function () {
editor.destroy();
editor = null;
}
</script>
</body>
</html>

View File

@ -1,100 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
.input {
display: block;
margin: 5px;
}
.input:focus {
border: 5px solid red;
}
.input:blur {
border: unset;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<input type="text" class="input">
<input type="text" class="input">
<form>
<div id="jsoneditor"></div>
</form>
<input type="text" class="input">
<input type="text" class="input">
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onFocus: function(event) {
container.style.border = '5px solid red';
console.log("Focus : ",event);
},
onBlur: function(event) {
container.style.border = 'unset';
console.log("Blur : ",event);
},
indentation: 4,
escapeUnicode: true
};
json = {
"array": [1, 2, [3,4,5]],
"boolean": true,
"color": "#82b92c",
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"timestamp": 1534952749890,
"url": "http://jsoneditoronline.org"
};
editorTest = new JSONEditor(container, options, json);
console.log('json', json);
console.log('string', JSON.stringify(json));
</script>
</body>
</html>

View File

@ -1,84 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<div id="jsoneditor"></div>
<script>
var container = document.getElementById('jsoneditor');
var options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
console.error(err);
alert(err.toString());
},
onChange: function () {
console.log('change');
},
indentation: 4,
escapeUnicode: true
};
var json = {
empty: [],
numbers: [],
abc: 123,
array: [],
object: { a: 2, b: 3}
};
for (var i = 0; i < 10000; i++) {
var longitude = 4 + i / 10000;
var latitude = 51 + i / 10000;
json.numbers.push(i);
json.array.push({
name: 'Item ' + i,
id: String(i),
index: i,
time: new Date().toISOString(),
location: {
latitude: longitude,
longitude: latitude,
coordinates: [longitude, latitude]
},
random: Math.random()
});
}
var editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -1,82 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<!-- materialize css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style>
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
padding-left: 40px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<form>
<div id="jsoneditor"></div>
</form>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
},
onChange: function () {
console.log('change');
},
indentation: 4,
escapeUnicode: true
};
json = {
"array": [1, 2, [3,4,5]],
"boolean": true,
"htmlcode": '&quot;',
"escaped_unicode": '\\u20b9',
"unicode": '\u20b9,\uD83D\uDCA9',
"return": '\n',
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World",
"url": "http://jsoneditoronline.org"
};
editor = new JSONEditor(container, options, json);
console.log('json', json);
console.log('string', JSON.stringify(json));
</script>
</body>
</html>

View File

@ -1,63 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.min.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor-minimalist.min.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 500px;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<div id="jsoneditor"></div>
<script>
var container, options, json, editor;
container = document.getElementById('jsoneditor');
options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
alert(err.toString());
}
};
json = {
"array": [1, 2, 3],
"boolean": true,
"color": "#82b92c",
"null": null,
"number": 123,
"object": {"a": "b", "c": "d"},
"string": "Hello World"
};
editor = new JSONEditor(container, options, json);
</script>
</body>
</html>

View File

@ -1,146 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | JSON schema validation</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
width: 600px;
font: 11pt sans-serif;
}
#anchor {
position: relative;
}
#jsoneditor {
width: 320px;
height: 300px;
position: relative;
overflow: hidden;
}
/* custom bold styling for non-default JSON schema values */
.jsoneditor-is-not-default {
font-weight: bold;
}
</style>
</head>
<body>
<h1>Test custom tooltip anchor</h1>
<p>
The JSON Schema error tooltips and the color picker should have correct placing and overflow the editor, also in combination with scrolling.
</p>
<div id="anchor">
<div id="jsoneditor"></div>
</div>
<div style="height: 2000px"></div>
<script>
const schema = {
"title": "Employee",
"description": "Object containing employee details",
"type": "object",
"properties": {
"firstName": {
"title": "First Name",
"description": "The given name.",
"examples": [
"John"
],
"type": "string"
},
"lastName": {
"title": "Last Name",
"description": "The family name.",
"examples": [
"Smith"
],
"type": "string"
},
"gender": {
"title": "Gender",
"enum": ["male", "female"]
},
"availableToHire": {
"type": "boolean",
"default": false
},
"age": {
"description": "Age in years",
"type": "integer",
"minimum": 0,
"examples": [28, 32]
},
"job": {
"$ref": "job"
}
},
"required": ["firstName", "lastName"]
}
const job = {
"title": "Job description",
"type": "object",
"required": ["address"],
"properties": {
"company": {
"type": "string",
"examples": [
"ACME",
"Dexter Industries"
]
},
"role": {
"description": "Job title.",
"type": "string",
"examples": [
"Human Resources Coordinator",
"Software Developer"
],
"default": "Software Developer"
},
"address": {
"type": "string"
},
"salary": {
"type": "number",
"minimum": 120,
"examples": [100, 110, 120]
}
}
}
const json = {
firstName: 'John',
lastName: 'Doe',
gender: null,
age: "28",
availableToHire: true,
favoriteColor: 'red',
job: {
company: 'freelance',
role: 'developer',
salary: 100
}
}
const options = {
schema: schema,
schemaRefs: {"job": job},
mode: 'tree',
modes: ['code', 'text', 'tree', 'preview'],
popupAnchor: document.getElementById('anchor')
}
// create the editor
const container = document.getElementById('jsoneditor')
const editor = new JSONEditor(container, options, json)
editor.expandAll()
</script>
</body>
</html>

View File

@ -1,77 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Preview mode load and save</title>
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<script src="https://bgrins.github.io/filereader.js/filereader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2014-11-29/FileSaver.min.js"></script>
<style>
html, body {
font: 11pt sans-serif;
}
#jsoneditor {
width: 500px;
height: 500px;
}
</style>
</head>
<body>
<h1>Load and save JSON documents in Preview mode</h1>
<p>
This examples uses HTML5 to load/save local files.
Powered by <a href="http://bgrins.github.io/filereader.js/">FileReader.js</a> and
<a href="https://github.com/eligrey/FileSaver.js">FileSaver.js</a>.<br>
Only supported on modern browsers (Chrome, FireFox, IE10+, Safari 6.1+, Opera 15+).
</p>
<p>
Load a JSON document: <input type="file" id="loadDocument" value="Load"/>
</p>
<p>
Save a JSON document: <input type="button" id="saveDocument" value="Save" />
</p>
<div id="jsoneditor"></div>
<script>
// create the editor
var editor = new JSONEditor(document.getElementById('jsoneditor'), {
mode: 'preview'
});
// Load a JSON document
FileReaderJS.setupInput(document.getElementById('loadDocument'), {
readAsDefault: 'Text',
on: {
load: function (event, file) {
editor.setText(event.target.result);
}
}
});
// Save a JSON document
document.getElementById('saveDocument').onclick = function () {
// Save Dialog
fname = window.prompt("Save as...");
// Check json extension in file name
if(fname.indexOf(".")==-1){
fname = fname + ".json";
}else{
if(fname.split('.').pop().toLowerCase() == "json"){
// Nothing to do
}else{
fname = fname.split('.')[0] + ".json";
}
}
var blob = new Blob([editor.getText()], {type: 'application/json;charset=utf-8'});
saveAs(blob, fname);
};
</script>
</body>
</html>

View File

@ -1,109 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 600px;
padding-left: 40px;
}
html, body {
width: 100%;
padding: 0;
box-sizing: border-box;
}
code {
background-color: #f5f5f5;
}
#jsoneditor {
max-width: 600px;
width: 90%;
height: 500px;
}
</style>
</head>
<body>
<p>
Switch editor mode using the mode box.
Note that the mode can be changed programmatically as well using the method
<code>editor.setMode(mode)</code>, try it in the console of your browser.
</p>
<div id="jsoneditor"></div>
<script>
var container = document.getElementById('jsoneditor');
var schema = {
"title": "User",
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
},
"gender": {
"enum": ["male", "female"]
},
"age": {
"description": "Age in years",
"examples": [18, 65],
"type": "integer",
"minimum": 0
},
"hobbies": {
"$ref": "hobbies.json"
}
},
"required": ["firstName", "lastName"]
};
var hobbiesSchema = {
"type": "array",
"items": {
"type": "string"
}
};
var options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
onError: function (err) {
console.error(err);
},
schema: schema,
schemaRefs: {"hobbies.json": hobbiesSchema}
};
var json = {
"firstName": "Jos",
"lastName": "de Jong",
gender: null,
"age": 34.2,
"hobbies": [
"programming",
"movies",
"bicycling"
]
};
var editor = new JSONEditor(container, options, json);
console.log('json', json);
console.log('schema', schema);
</script>
</body>
</html>

View File

@ -1,98 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>JSONEditor | Update JSON</title>
<!-- when using the mode "code", it's important to specify charset utf-8 -->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
<script src="../dist/jsoneditor.js"></script>
<style type="text/css">
body, html {
font: 10.5pt arial;
color: #4d4d4d;
line-height: 150%;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
.main {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
}
.main > div {
margin: 10px;
}
#jsoneditor {
flex: 1;
width: 100%;
max-width: 500px;
height: 200px;
}
</style>
</head>
<body>
<div class="main">
<div>
<button id="update">update JSON</button>
<button id="updateText">update JSON Text</button>
</div>
<div id="jsoneditor"></div>
</div>
<script>
var container = document.getElementById('jsoneditor');
var updateButton = document.getElementById('update');
var updateTextButton = document.getElementById('updateText');
var options = {
mode: 'tree',
modes: ['code', 'form', 'text', 'tree', 'view', 'preview'] // allowed modes
};
var json = {
"arrayToObject": [1, 2, 3],
"arrayGrow": [1, 2, 3],
"arrayShrink": [1, 2, 3],
"autoToArray": 123,
"arrayToAuto": [1, 2, 3],
"objectGrow": {"a": "b", "c": "d"},
"objectShrink": {"a": "b", "c": "d"},
"objectToArray": {"a": "b", "c": "d"},
"removeField": "old"
};
var updatedJson = {
"arrayToObject": {"a": "b", "c": "d"},
"arrayGrow": [1, 2, 3, 4, 5],
"arrayShrink": [1, 3],
"autoToArray": [1, 2, 3],
"arrayToAuto": 123,
"objectGrow": {"a": "b", "c": "ddd", "e": "f"},
"objectShrink": {"c": "d"},
"objectToArray": [1, 2, 3],
"newField": "new"
};
var editor = new JSONEditor(container, options, json);
editor.expandAll();
updateButton.onclick = function () {
editor.update(updatedJson);
};
updateTextButton.onclick = function () {
editor.updateText(JSON.stringify(updatedJson, null, 2));
};
</script>
</body>
</html>

View File

@ -1,561 +0,0 @@
import assert from 'assert'
import {
compileJSONPointer,
findUniqueName,
formatSize,
get,
getChildPaths,
getIndexForPosition,
isObject,
isTimestamp,
limitCharacters,
makeFieldTooltip,
parsePath,
parseString,
repair,
sort,
sortObjectKeys,
stringifyPath,
isValidationErrorChanged
} from '../src/js/util'
describe('util', () => {
describe('repair', () => {
it('should leave valid JSON as is', () => {
assert.strictEqual(repair('{"a":2}'), '{"a":2}')
})
it('should replace JavaScript with JSON', () => {
assert.strictEqual(repair('{a:2}'), '{"a":2}')
assert.strictEqual(repair('{a: 2}'), '{"a": 2}')
assert.strictEqual(repair('{\n a: 2\n}'), '{\n "a": 2\n}')
assert.strictEqual(repair('{\'a\':2}'), '{"a":2}')
assert.strictEqual(repair('{a:\'foo\'}'), '{"a":"foo"}')
assert.strictEqual(repair('{a:\'foo\',b:\'bar\'}'), '{"a":"foo","b":"bar"}')
// should leave string content untouched
assert.strictEqual(repair('"{a:b}"'), '"{a:b}"')
})
it('should add/remove escape characters', () => {
assert.strictEqual(repair('"foo\'bar"'), '"foo\'bar"')
assert.strictEqual(repair('"foo\\"bar"'), '"foo\\"bar"')
assert.strictEqual(repair('\'foo"bar\''), '"foo\\"bar"')
assert.strictEqual(repair('\'foo\\\'bar\''), '"foo\'bar"')
assert.strictEqual(repair('"foo\\\'bar"'), '"foo\'bar"')
})
it('should replace special white characters', () => {
assert.strictEqual(repair('{"a":\u00a0"foo\u00a0bar"}'), '{"a": "foo\u00a0bar"}')
assert.strictEqual(repair('{"a":\u2009"foo"}'), '{"a": "foo"}')
})
it('should escape unescaped control characters', () => {
assert.strictEqual(repair('"hello\bworld"'), '"hello\\bworld"')
assert.strictEqual(repair('"hello\fworld"'), '"hello\\fworld"')
assert.strictEqual(repair('"hello\nworld"'), '"hello\\nworld"')
assert.strictEqual(repair('"hello\rworld"'), '"hello\\rworld"')
assert.strictEqual(repair('"hello\tworld"'), '"hello\\tworld"')
assert.strictEqual(repair('{"value\n": "dc=hcm,dc=com"}'), '{"value\\n": "dc=hcm,dc=com"}')
})
it('should replace left/right quotes', () => {
assert.strictEqual(repair('\u2018foo\u2019'), '"foo"')
assert.strictEqual(repair('\u201Cfoo\u201D'), '"foo"')
assert.strictEqual(repair('\u0060foo\u00B4'), '"foo"')
})
it('remove comments', () => {
assert.strictEqual(repair('/* foo */ {}'), ' {}')
assert.strictEqual(repair('/* foo */ {}'), ' {}')
assert.strictEqual(repair('{a:\'foo\',/*hello*/b:\'bar\'}'), '{"a":"foo","b":"bar"}')
assert.strictEqual(repair('{\na:\'foo\',//hello\nb:\'bar\'\n}'), '{\n"a":"foo",\n"b":"bar"\n}')
// should not remove comments in string
assert.strictEqual(repair('{"str":"/* foo */"}'), '{"str":"/* foo */"}')
})
it('should strip JSONP notation', () => {
// matching
assert.strictEqual(repair('callback_123({});'), '{}')
assert.strictEqual(repair('callback_123([]);'), '[]')
assert.strictEqual(repair('callback_123(2);'), '2')
assert.strictEqual(repair('callback_123("foo");'), '"foo"')
assert.strictEqual(repair('callback_123(null);'), 'null')
assert.strictEqual(repair('callback_123(true);'), 'true')
assert.strictEqual(repair('callback_123(false);'), 'false')
assert.strictEqual(repair('/* foo bar */ callback_123 ({})'), '{}')
assert.strictEqual(repair('/* foo bar */ callback_123 ({})'), '{}')
assert.strictEqual(repair('/* foo bar */\ncallback_123({})'), '{}')
assert.strictEqual(repair('/* foo bar */ callback_123 ( {} )'), ' {} ')
assert.strictEqual(repair(' /* foo bar */ callback_123 ({}); '), '{}')
assert.strictEqual(repair('\n/* foo\nbar */\ncallback_123 ({});\n\n'), '{}')
// non-matching
assert.strictEqual(repair('callback {}'), 'callback {}')
assert.strictEqual(repair('callback({}'), 'callback({}')
})
it('should strip trailing commas', () => {
// matching
assert.strictEqual(repair('[1,2,3,]'), '[1,2,3]')
assert.strictEqual(repair('[1,2,3,\n]'), '[1,2,3\n]')
assert.strictEqual(repair('[1,2,3, \n ]'), '[1,2,3 \n ]')
assert.strictEqual(repair('{"a":2,}'), '{"a":2}')
// not matching
assert.strictEqual(repair('"[1,2,3,]"'), '"[1,2,3,]"')
assert.strictEqual(repair('"{a:2,}"'), '"{a:2,}"')
})
it('should strip MongoDB data types', () => {
const mongoDocument = '{\n' +
' "_id" : ObjectId("123"),\n' +
' "isoDate" : ISODate("2012-12-19T06:01:17.171Z"),\n' +
' "regularNumber" : 67,\n' +
' "long" : NumberLong("2"),\n' +
' "long2" : NumberLong(2),\n' +
' "int" : NumberInt("3"),\n' +
' "int2" : NumberInt(3),\n' +
' "decimal" : NumberDecimal("4"),\n' +
' "decimal2" : NumberDecimal(4)\n' +
'}'
const expectedJson = '{\n' +
' "_id" : "123",\n' +
' "isoDate" : "2012-12-19T06:01:17.171Z",\n' +
' "regularNumber" : 67,\n' +
' "long" : "2",\n' +
' "long2" : 2,\n' +
' "int" : "3",\n' +
' "int2" : 3,\n' +
' "decimal" : "4",\n' +
' "decimal2" : 4\n' +
'}'
assert.strictEqual(repair(mongoDocument), expectedJson)
})
it('should replace Python constants None, True, False', () => {
const pythonDocument = '{\n' +
' "null": None,\n' +
' "true": True,\n' +
' "false": False\n' +
' "array": [1, foo, None, True, False]\n' +
'}'
const expectedJson = '{\n' +
' "null": null,\n' +
' "true": true,\n' +
' "false": false\n' +
' "array": [1, "foo", null, true, false]\n' +
'}'
assert.strictEqual(repair(pythonDocument), expectedJson)
})
it('should repair missing comma between objects', () => {
const text = '{"aray": [{}{}]}'
const expected = '{"aray": [{},{}]}'
assert.strictEqual(repair(text), expected)
})
it('should not repair normal array with comma separated objects', () => {
const text = '[\n{},\n{}\n]'
assert.strictEqual(repair(text), text)
})
it('should repair line separated json (for example from robo3t', () => {
const text = '' +
'/* 1 */\n' +
'{}\n' +
'\n' +
'/* 2 */\n' +
'{}\n' +
'\n' +
'/* 3 */\n' +
'{}\n'
const expected = '[\n{},\n\n{},\n\n{}\n\n]'
assert.strictEqual(repair(text), expected)
})
})
describe('jsonPath', () => {
it('should stringify an array of paths', () => {
assert.deepStrictEqual(stringifyPath([]), '')
assert.deepStrictEqual(stringifyPath(['foo']), '.foo')
assert.deepStrictEqual(stringifyPath(['foo', 'bar']), '.foo.bar')
assert.deepStrictEqual(stringifyPath(['foo', 2]), '.foo[2]')
assert.deepStrictEqual(stringifyPath(['foo', 2, 'bar']), '.foo[2].bar')
assert.deepStrictEqual(stringifyPath(['foo', 2, 'bar_baz']), '.foo[2].bar_baz')
assert.deepStrictEqual(stringifyPath([2]), '[2]')
assert.deepStrictEqual(stringifyPath(['foo', 'prop-with-hyphens']), '.foo["prop-with-hyphens"]')
assert.deepStrictEqual(stringifyPath(['foo', 'prop with spaces']), '.foo["prop with spaces"]')
})
it('should parse a json path', () => {
assert.deepStrictEqual(parsePath(''), [])
assert.deepStrictEqual(parsePath('.foo'), ['foo'])
assert.deepStrictEqual(parsePath('.foo.bar'), ['foo', 'bar'])
assert.deepStrictEqual(parsePath('.foo[2]'), ['foo', 2])
assert.deepStrictEqual(parsePath('.foo[2].bar'), ['foo', 2, 'bar'])
assert.deepStrictEqual(parsePath('.foo["prop with spaces"]'), ['foo', 'prop with spaces'])
assert.deepStrictEqual(parsePath('.foo[\'prop with single quotes as outputted by ajv library\']'), ['foo', 'prop with single quotes as outputted by ajv library'])
assert.deepStrictEqual(parsePath('.foo["prop with . dot"]'), ['foo', 'prop with . dot'])
assert.deepStrictEqual(parsePath('.foo["prop with ] character"]'), ['foo', 'prop with ] character'])
assert.deepStrictEqual(parsePath('.foo[*].bar'), ['foo', '*', 'bar'])
assert.deepStrictEqual(parsePath('[2]'), [2])
})
it('should throw an exception in case of an invalid path', () => {
assert.throws(() => { parsePath('.') }, /Invalid JSON path: property name expected at index 1/)
assert.throws(() => { parsePath('[') }, /Invalid JSON path: unexpected end, character ] expected/)
assert.throws(() => { parsePath('[]') }, /Invalid JSON path: array value expected at index 1/)
assert.throws(() => { parsePath('.foo[ ]') }, /Invalid JSON path: array value expected at index 7/)
assert.throws(() => { parsePath('.[]') }, /Invalid JSON path: property name expected at index 1/)
assert.throws(() => { parsePath('["23]') }, /Invalid JSON path: unexpected end, character " expected/)
assert.throws(() => { parsePath('.foo bar') }, /Invalid JSON path: unexpected character " " at index 4/)
})
})
describe('getIndexForPosition', () => {
const el = {
value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
}
it('happy flows - row and column in range', () => {
assert.strictEqual(getIndexForPosition(el, 1, 1), 0)
assert.strictEqual(getIndexForPosition(el, 2, 1), 124)
assert.strictEqual(getIndexForPosition(el, 3, 8), 239)
assert.strictEqual(getIndexForPosition(el, 4, 22), 356)
})
it('if range exceeds it should be considered as if it is last row or column length', () => {
assert.strictEqual(getIndexForPosition(el, 1, 100000), 123)
assert.strictEqual(getIndexForPosition(el, 100000, 1), 335)
assert.strictEqual(getIndexForPosition(el, 100000, 100000), 445)
})
it('missing or wrong input sould return -1', () => {
assert.strictEqual(getIndexForPosition(el), -1)
assert.strictEqual(getIndexForPosition(el, undefined, 1), -1)
assert.strictEqual(getIndexForPosition(el, 1, undefined), -1)
assert.strictEqual(getIndexForPosition(el, -2, -2), -1)
})
})
describe('isValidationErrorChanged', () => {
const err1 = { keyword: 'enum', dataPath: '.gender', schemaPath: '#/properties/gender/enum', params: { allowedValues: ['male', 'female'] }, message: 'should be equal to one of: "male", "female"', schema: ['male', 'female'], parentSchema: { title: 'Gender', enum: ['male', 'female'] }, data: null, type: 'validation' }
const err2 = { keyword: 'type', dataPath: '.age', schemaPath: '#/properties/age/type', params: { type: 'integer' }, message: 'should be integer', schema: 'integer', parentSchema: { description: 'Age in years', type: 'integer', minimum: 0, examples: [28, 32] }, data: '28', type: 'validation' }
const err3 = { dataPath: '.gender', message: 'Member must be an object with properties "name" and "age"' }
it('empty value for both current and previoues error should return false', () => {
assert.strictEqual(isValidationErrorChanged(), false)
})
it('empty value for one of current and previoues error should return true', () => {
assert.strictEqual(isValidationErrorChanged([err1]), true)
assert.strictEqual(isValidationErrorChanged(undefined, [err1]), true)
})
it('different length of current and previoues errors should return true', () => {
assert.strictEqual(isValidationErrorChanged([err1], []), true)
assert.strictEqual(isValidationErrorChanged([err1], [err1, err2]), true)
})
it('same values for current and previoues errors should return false', () => {
assert.strictEqual(isValidationErrorChanged([err1, err2, err3], [err2, err3, err1]), false)
})
it('different values for current and previoues errors should return true', () => {
assert.strictEqual(isValidationErrorChanged([err1, err2], [err3, err1]), true)
})
})
describe('get', () => {
it('should get a nested property from an object', () => {
const obj = {
a: {
b: 2
},
c: 3,
d: null,
e: undefined
}
assert.strictEqual(get(obj, ['a', 'b']), 2)
assert.strictEqual(get(obj, ['c']), 3)
assert.deepStrictEqual(get(obj, ['a']), { b: 2 })
assert.strictEqual(get(obj, ['a', 'foo']), undefined)
assert.strictEqual(get(obj, ['a', 'foo', 'bar']), undefined)
assert.strictEqual(get(obj, ['d']), null)
assert.strictEqual(get(obj, ['d', 'foo', 'bar']), null)
assert.strictEqual(get(obj, ['e']), undefined)
})
})
describe('makeFieldTooltip', () => {
it('should return empty string when the schema is missing all relevant fields', () => {
assert.strictEqual(makeFieldTooltip({}), '')
assert.strictEqual(makeFieldTooltip({ additionalProperties: false }), '')
assert.strictEqual(makeFieldTooltip(), '')
})
it('should make tooltips with only title', () => {
assert.strictEqual(makeFieldTooltip({ title: 'foo' }), 'foo')
})
it('should make tooltips with only description', () => {
assert.strictEqual(makeFieldTooltip({ description: 'foo' }), 'foo')
})
it('should make tooltips with only default', () => {
assert.strictEqual(makeFieldTooltip({ default: 'foo' }), 'Default\n"foo"')
})
it('should make tooltips with only examples', () => {
assert.strictEqual(makeFieldTooltip({ examples: ['foo', 'bar'] }), 'Examples\n"foo"\n"bar"')
})
it('should make tooltips with title and description', () => {
assert.strictEqual(makeFieldTooltip({ title: 'foo', description: 'bar' }), 'foo\nbar')
const longTitle = 'Lorem Ipsum Dolor'
const longDescription = 'Duis id elit non ante gravida vestibulum non nec est. ' +
'Proin vitae ligula at elit dapibus tempor. ' +
'Etiam lacinia augue vel condimentum interdum. '
assert.strictEqual(
makeFieldTooltip({ title: longTitle, description: longDescription }),
longTitle + '\n' + longDescription
)
})
it('should make tooltips with title, description, and examples', () => {
assert.strictEqual(
makeFieldTooltip({ title: 'foo', description: 'bar', examples: ['baz'] }),
'foo\nbar\n\nExamples\n"baz"'
)
})
it('should make tooltips with title, description, default, and examples', () => {
assert.strictEqual(
makeFieldTooltip({ title: 'foo', description: 'bar', default: 'bat', examples: ['baz'] }),
'foo\nbar\n\nDefault\n"bat"\n\nExamples\n"baz"'
)
})
it('should handle empty fields', () => {
assert.strictEqual(makeFieldTooltip({ title: '', description: 'bar' }), 'bar')
assert.strictEqual(makeFieldTooltip({ title: 'foo', description: '' }), 'foo')
assert.strictEqual(makeFieldTooltip({ description: 'bar', examples: [] }), 'bar')
assert.strictEqual(makeFieldTooltip({ description: 'bar', examples: [''] }), 'bar\n\nExamples\n""')
})
it('should internationalize "Defaults" correctly', () => {
assert.strictEqual(makeFieldTooltip({ default: 'foo' }, 'pt-BR'), 'Revelia\n"foo"')
})
it('should internationalize "Examples" correctly', () => {
assert.strictEqual(makeFieldTooltip({ examples: ['foo'] }, 'pt-BR'), 'Exemplos\n"foo"')
})
})
describe('getChildPaths', () => {
it('should extract all child paths of an array containing objects', () => {
const json = [
{ name: 'A', location: { latitude: 1, longitude: 2 } },
{ name: 'B', location: { latitude: 1, longitude: 2 } },
{ name: 'C', timestamp: 0 }
]
assert.deepStrictEqual(getChildPaths(json), [
'.location.latitude',
'.location.longitude',
'.name',
'.timestamp'
])
})
it('should extract all child paths of an array containing objects, including objects', () => {
const json = [
{ name: 'A', location: { latitude: 1, longitude: 2 } },
{ name: 'B', location: { latitude: 1, longitude: 2 } },
{ name: 'C', timestamp: 0 }
]
assert.deepStrictEqual(getChildPaths(json, true), [
'',
'.location',
'.location.latitude',
'.location.longitude',
'.name',
'.timestamp'
])
})
it('should extract all child paths of an array containing values', () => {
const json = [1, 2, 3]
assert.deepStrictEqual(getChildPaths(json), [
''
])
})
it('should extract all child paths of a non-array', () => {
assert.deepStrictEqual(getChildPaths({ a: 2, b: { c: 3 } }), [''])
assert.deepStrictEqual(getChildPaths('foo'), [''])
assert.deepStrictEqual(getChildPaths(123), [''])
})
})
it('should test whether something is an object', () => {
assert.strictEqual(isObject({}), true)
assert.strictEqual(isObject(new Date()), true)
assert.strictEqual(isObject([]), false)
assert.strictEqual(isObject(2), false)
assert.strictEqual(isObject(null), false)
assert.strictEqual(isObject(undefined), false)
assert.strictEqual(isObject(), false)
})
describe('sort', () => {
it('should sort an array', () => {
const array = [4, 1, 10, 2]
assert.deepStrictEqual(sort(array), [1, 2, 4, 10])
assert.deepStrictEqual(sort(array, '.', 'desc'), [10, 4, 2, 1])
})
it('should sort an array containing objects', () => {
const array = [
{ value: 4 },
{ value: 1 },
{ value: 10 },
{ value: 2 }
]
assert.deepStrictEqual(sort(array, '.value'), [
{ value: 1 },
{ value: 2 },
{ value: 4 },
{ value: 10 }
])
assert.deepStrictEqual(sort(array, '.value', 'desc'), [
{ value: 10 },
{ value: 4 },
{ value: 2 },
{ value: 1 }
])
})
})
describe('sortObjectKeys', () => {
it('should sort the keys of an object', () => {
const object = {
c: 'c',
a: 'a',
b: 'b'
}
assert.strictEqual(JSON.stringify(object), '{"c":"c","a":"a","b":"b"}')
assert.strictEqual(JSON.stringify(sortObjectKeys(object)), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(sortObjectKeys(object, 'asc')), '{"a":"a","b":"b","c":"c"}')
assert.strictEqual(JSON.stringify(sortObjectKeys(object, 'desc')), '{"c":"c","b":"b","a":"a"}')
})
})
it('should parse a string', () => {
assert.strictEqual(parseString('foo'), 'foo')
assert.strictEqual(parseString('234foo'), '234foo')
assert.strictEqual(parseString(' 234'), 234)
assert.strictEqual(parseString('234 '), 234)
assert.strictEqual(parseString('2.3'), 2.3)
assert.strictEqual(parseString('null'), null)
assert.strictEqual(parseString('true'), true)
assert.strictEqual(parseString('false'), false)
assert.strictEqual(parseString('+1'), 1)
assert.strictEqual(parseString(' '), ' ')
assert.strictEqual(parseString(''), '')
assert.strictEqual(parseString('"foo"'), '"foo"')
assert.strictEqual(parseString('"2"'), '"2"')
assert.strictEqual(parseString('\'foo\''), '\'foo\'')
})
it('should find a unique name', () => {
assert.strictEqual(findUniqueName('other', [
'a',
'b',
'c'
]), 'other')
assert.strictEqual(findUniqueName('b', [
'a',
'b',
'c'
]), 'b (copy)')
assert.strictEqual(findUniqueName('b', [
'a',
'b',
'c',
'b (copy)'
]), 'b (copy 2)')
assert.strictEqual(findUniqueName('b', [
'a',
'b',
'c',
'b (copy)',
'b (copy 2)'
]), 'b (copy 3)')
assert.strictEqual(findUniqueName('b (copy)', [
'a',
'b',
'b (copy)',
'b (copy 2)',
'c'
]), 'b (copy 3)')
assert.strictEqual(findUniqueName('b (copy 2)', [
'a',
'b',
'b (copy)',
'b (copy 2)',
'c'
]), 'b (copy 3)')
})
it('should format a document size in a human readable way', () => {
assert.strictEqual(formatSize(500), '500 B')
assert.strictEqual(formatSize(900), '0.9 KB')
assert.strictEqual(formatSize(77.89 * 1000), '77.9 KB')
assert.strictEqual(formatSize(950 * 1000), '0.9 MB')
assert.strictEqual(formatSize(7.22 * 1000 * 1000), '7.2 MB')
assert.strictEqual(formatSize(945.4 * 1000 * 1000), '0.9 GB')
assert.strictEqual(formatSize(22.37 * 1000 * 1000 * 1000), '22.4 GB')
assert.strictEqual(formatSize(1000 * 1000 * 1000 * 1000), '1.0 TB')
})
it('should limit characters', () => {
assert.strictEqual(limitCharacters('hello world', 11), 'hello world')
assert.strictEqual(limitCharacters('hello world', 5), 'hello...')
assert.strictEqual(limitCharacters('hello world', 100), 'hello world')
})
it('should compile a JSON pointer', () => {
assert.strictEqual(compileJSONPointer(['foo', 'bar']), '/foo/bar')
assert.strictEqual(compileJSONPointer(['foo', '/~ ~/']), '/foo/~1~0 ~0~1')
assert.strictEqual(compileJSONPointer(['']), '/')
assert.strictEqual(compileJSONPointer([]), '')
})
it('should test whether a field is a timestamp', () => {
assert.strictEqual(isTimestamp('foo', 1574809200000), true)
assert.strictEqual(isTimestamp('foo', 1574809200000.2), false)
})
// TODO: thoroughly test all util methods
})