Implemented JSON Schema support for mode `text` and `code`
This commit is contained in:
parent
18d63fcd9a
commit
68a5b1476f
|
@ -4,4 +4,3 @@ downloads
|
|||
node_modules
|
||||
*.zip
|
||||
npm-debug.log
|
||||
yarn.lock
|
||||
|
|
|
@ -59,12 +59,14 @@
|
|||
}
|
||||
|
||||
var options = {
|
||||
schema: schema
|
||||
modes: ['code', 'tree']
|
||||
}
|
||||
|
||||
// create the editor
|
||||
var container = document.getElementById('jsoneditor')
|
||||
var editor = jsoneditor(container, options, json)
|
||||
var editor = jsoneditor(container, options)
|
||||
editor.setSchema(schema)
|
||||
editor.set(json)
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -35,7 +35,7 @@
|
|||
var container = document.getElementById('jsoneditor')
|
||||
var options = {
|
||||
mode: 'code',
|
||||
onLoadAce: function (aceEditor, container, options) {
|
||||
onLoadAce: function (aceEditor, container) {
|
||||
// we can adjust configuration of the ace editor,
|
||||
// or create a completely new instance of ace editor.
|
||||
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import { h, Component } from 'preact'
|
||||
import ace from '../assets/ace'
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
*
|
||||
* <Ace value={"{}"}
|
||||
* ace={Object}
|
||||
* indentation={2}
|
||||
* onChange={function(value: String)}
|
||||
* onLoadAce={function(aceEditor, container)} />
|
||||
*
|
||||
*/
|
||||
export default class Ace extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.aceEditor = null
|
||||
}
|
||||
|
||||
render (props, state) {
|
||||
return h('div', {id: this.id, class: 'jsoneditor-code'})
|
||||
}
|
||||
|
||||
shouldComponentUpdate () {
|
||||
// always prevent rerendering, that would destroy the DOM of the Ace editor
|
||||
return false
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const container = this.base
|
||||
|
||||
// use ace from bundle, and if not available
|
||||
// try to use from options or else from global
|
||||
const _ace = ace || this.props.ace || window['ace']
|
||||
|
||||
let aceEditor = null
|
||||
if (_ace && _ace.edit) {
|
||||
// create ace editor
|
||||
aceEditor = _ace.edit(container)
|
||||
|
||||
// bundle and load jsoneditor theme for ace editor
|
||||
require('../assets/ace/theme-jsoneditor')
|
||||
|
||||
// configure ace editor
|
||||
aceEditor.$blockScrolling = Infinity
|
||||
aceEditor.setTheme('ace/theme/jsoneditor')
|
||||
aceEditor.setShowPrintMargin(false)
|
||||
aceEditor.setFontSize(13)
|
||||
aceEditor.getSession().setMode('ace/mode/json')
|
||||
aceEditor.getSession().setTabSize(this.props.indentation || 2)
|
||||
aceEditor.getSession().setUseSoftTabs(true)
|
||||
aceEditor.getSession().setUseWrapMode(true)
|
||||
aceEditor.commands.bindKey('Ctrl-L', null) // disable Ctrl+L (is used by the browser to select the address bar)
|
||||
aceEditor.commands.bindKey('Command-L', null) // disable Ctrl+L (is used by the browser to select the address bar)
|
||||
}
|
||||
else {
|
||||
// ace is excluded from the bundle.
|
||||
}
|
||||
|
||||
// allow changing the config or completely replacing aceEditor
|
||||
this.aceEditor = this.props.onLoadAce
|
||||
? this.props.onLoadAce(aceEditor, container) || aceEditor
|
||||
: aceEditor
|
||||
|
||||
// register onchange event
|
||||
this.aceEditor.on('change', this.handleChange)
|
||||
|
||||
// set value, the text contents for the editor
|
||||
this.aceEditor.setValue(this.props.value || '', -1)
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.value !== this.aceEditor.getValue()) {
|
||||
this.aceEditor.setValue(nextProps.value, -1)
|
||||
}
|
||||
|
||||
if (nextProps.indentation != undefined) {
|
||||
this.aceEditor.getSession().setTabSize(this.props.indentation)
|
||||
}
|
||||
|
||||
// TODO: only resize only when needed
|
||||
setTimeout(() => {
|
||||
this.aceEditor.resize(false);
|
||||
}, 0)
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
// neatly destroy ace editor, it has created a worker for validation
|
||||
this.aceEditor.destroy()
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
if (this.props && this.props.onChange) {
|
||||
// TODO: pass a diff
|
||||
this.props.onChange(this.aceEditor.getValue())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { h } from 'preact'
|
||||
import TextMode from './TextMode'
|
||||
import ace from '../assets/ace'
|
||||
import Ace from './Ace'
|
||||
|
||||
/**
|
||||
* CodeMode (powered by Ace editor)
|
||||
|
@ -11,7 +11,8 @@ import ace from '../assets/ace'
|
|||
* options={Object}
|
||||
* onChange={function(text: string)}
|
||||
* onChangeMode={function(mode: string)}
|
||||
* onLoadAce={function(aceEditor: Object, container: Element, options: Object) : Object}
|
||||
* onError={function(error: Error)}
|
||||
* onLoadAce={function(aceEditor: Object, container: Element) : Object}
|
||||
* />
|
||||
*
|
||||
* Methods:
|
||||
|
@ -30,81 +31,30 @@ export default class CodeMode extends TextMode {
|
|||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {}
|
||||
|
||||
this.id = 'id' + Math.round(Math.random() * 1e6) // unique enough id within the JSONEditor
|
||||
this.aceEditor = null
|
||||
this.state = {
|
||||
text: '{}'
|
||||
}
|
||||
}
|
||||
|
||||
render (props, state) {
|
||||
return h('div', {class: 'jsoneditor jsoneditor-mode-code'}, [
|
||||
this.renderMenu(),
|
||||
|
||||
h('div', {class: 'jsoneditor-contents', id: this.id})
|
||||
h('div', {class: 'jsoneditor-contents'}, h(Ace, {
|
||||
value: this.state.text,
|
||||
onChange: this.handleChange,
|
||||
onLoadAce: this.props.options.onLoadAce,
|
||||
indentation: this.props.options.indentation,
|
||||
ace: this.props.options.ace
|
||||
})),
|
||||
|
||||
this.renderSchemaErrors ()
|
||||
])
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const options = this.props.options || {}
|
||||
handleChange = (text) => {
|
||||
this.setState({ text })
|
||||
|
||||
const container = this.base.querySelector('#' + this.id)
|
||||
|
||||
// use ace from bundle, and if not available try to use from global
|
||||
const _ace = ace || window['ace']
|
||||
|
||||
let aceEditor = null
|
||||
if (_ace && _ace.edit) {
|
||||
// create ace editor
|
||||
aceEditor = _ace.edit(container)
|
||||
|
||||
// bundle and load jsoneditor theme for ace editor
|
||||
require('../assets/ace/theme-jsoneditor')
|
||||
|
||||
// configure ace editor
|
||||
aceEditor.$blockScrolling = Infinity
|
||||
aceEditor.setTheme('ace/theme/jsoneditor')
|
||||
aceEditor.setShowPrintMargin(false)
|
||||
aceEditor.setFontSize(13)
|
||||
aceEditor.getSession().setMode('ace/mode/json')
|
||||
aceEditor.getSession().setTabSize(options.indentation || 2)
|
||||
aceEditor.getSession().setUseSoftTabs(true)
|
||||
aceEditor.getSession().setUseWrapMode(true)
|
||||
aceEditor.commands.bindKey('Ctrl-L', null) // disable Ctrl+L (is used by the browser to select the address bar)
|
||||
aceEditor.commands.bindKey('Command-L', null) // disable Ctrl+L (is used by the browser to select the address bar)
|
||||
}
|
||||
else {
|
||||
// ace is excluded from the bundle.
|
||||
}
|
||||
|
||||
// allow changing the config or completely replacing aceEditor
|
||||
this.aceEditor = options.onLoadAce
|
||||
? options.onLoadAce(aceEditor, container, options) || aceEditor
|
||||
: aceEditor
|
||||
|
||||
// register onchange event
|
||||
this.aceEditor.on('change', this.handleChange)
|
||||
|
||||
// set initial text
|
||||
this.setText('{}')
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.destroy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor
|
||||
*/
|
||||
destroy () {
|
||||
// neatly destroy ace editor
|
||||
this.aceEditor.destroy()
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
// TODO: handle changes in props
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
if (this.props.options && this.props.options.onChangeText) {
|
||||
// TODO: pass a diff
|
||||
this.props.options.onChangeText()
|
||||
|
@ -116,7 +66,7 @@ export default class CodeMode extends TextMode {
|
|||
* @param {string} text
|
||||
*/
|
||||
setText (text) {
|
||||
this.aceEditor.setValue(text, -1)
|
||||
this.setState({text})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,6 +74,6 @@ export default class CodeMode extends TextMode {
|
|||
* @return {string} text
|
||||
*/
|
||||
getText () {
|
||||
return this.aceEditor.getValue()
|
||||
return this.state.text
|
||||
}
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
import { h, Component } from 'preact'
|
||||
import Ajv from 'ajv'
|
||||
import { parseJSON } from '../utils/jsonUtils'
|
||||
import { escapeUnicodeChars } from '../utils/stringUtils'
|
||||
import { enrichSchemaError, limitErrors } from '../utils/schemaUtils'
|
||||
import { jsonToData, dataToJson, patchData } from '../jsonData'
|
||||
import ModeButton from './menu/ModeButton'
|
||||
|
||||
|
@ -13,6 +15,7 @@ import ModeButton from './menu/ModeButton'
|
|||
* options={Object}
|
||||
* onChange={function(text: string)}
|
||||
* onChangeMode={function(mode: string)}
|
||||
* onError={function(error: Error)}
|
||||
* />
|
||||
*
|
||||
* Methods:
|
||||
|
@ -33,7 +36,8 @@ export default class TextMode extends Component {
|
|||
super(props)
|
||||
|
||||
this.state = {
|
||||
text: '{}'
|
||||
text: '{}',
|
||||
compiledSchema: null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +51,9 @@ export default class TextMode extends Component {
|
|||
value: this.state.text,
|
||||
onInput: this.handleChange
|
||||
})
|
||||
])
|
||||
]),
|
||||
|
||||
this.renderSchemaErrors ()
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -73,11 +79,86 @@ export default class TextMode extends Component {
|
|||
modes: this.props.options.modes,
|
||||
mode: this.props.mode,
|
||||
onChangeMode: this.props.onChangeMode,
|
||||
onError: this.handleError
|
||||
onError: this.props.onError
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
renderSchemaErrors () {
|
||||
// TODO: move the JSON Schema stuff into a separate Component?
|
||||
if (!this.state.compiledSchema) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: only validate again when json is changed since last validation
|
||||
const json = this.get(); // this can fail when there is no valid json
|
||||
const valid = this.state.compiledSchema(json)
|
||||
if (!valid) {
|
||||
const allErrors = this.state.compiledSchema.errors.map(enrichSchemaError)
|
||||
const limitedErrors = limitErrors(allErrors)
|
||||
|
||||
return h('table', {class: 'jsoneditor-text-errors'},
|
||||
h('tbody', {}, limitedErrors.map(TextMode.renderSchemaError))
|
||||
)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
// no valid JSON, don't validate
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a table row of a single JSON schema error
|
||||
* @param {Error | string} error
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
static renderSchemaError (error) {
|
||||
const icon = h('input', {type: 'button', class: 'jsoneditor-schema-error'})
|
||||
|
||||
if (typeof error === 'string') {
|
||||
return h('tr', {},
|
||||
h('td', {}, icon),
|
||||
h('td', {colSpan: 2}, h('pre', {}, error))
|
||||
)
|
||||
}
|
||||
else {
|
||||
return h('tr', {}, [
|
||||
h('td', {}, icon),
|
||||
h('td', {}, error.dataPath),
|
||||
h('td', {}, error.message)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a JSON schema for validation of the JSON object.
|
||||
* To remove the schema, call JSONEditor.setSchema(null)
|
||||
* @param {Object | null} schema
|
||||
*/
|
||||
setSchema (schema) {
|
||||
if (schema) {
|
||||
const ajv = this.props.options.ajv ||
|
||||
Ajv && Ajv({ allErrors: true, verbose: true })
|
||||
|
||||
if (!ajv) {
|
||||
throw new Error('Cannot validate JSON: ajv not available. ' +
|
||||
'Provide ajv via options or use a JSONEditor bundle including ajv.')
|
||||
}
|
||||
|
||||
this.setState({
|
||||
compiledSchema: ajv.compile(schema)
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
compiledSchema: null
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configured indentation
|
||||
* @return {number}
|
||||
|
@ -106,7 +187,7 @@ export default class TextMode extends Component {
|
|||
this.format()
|
||||
}
|
||||
catch (err) {
|
||||
this.handleError(err)
|
||||
this.props.onError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -116,17 +197,7 @@ export default class TextMode extends Component {
|
|||
this.compact()
|
||||
}
|
||||
catch (err) {
|
||||
this.handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
handleError = (err) => {
|
||||
if (this.props.options && this.props.options.onError) {
|
||||
this.props.options.onError(err)
|
||||
}
|
||||
else {
|
||||
console.error(err)
|
||||
this.props.onError(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,11 +276,4 @@ export default class TextMode extends Component {
|
|||
getText () {
|
||||
return this.state.text
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor
|
||||
*/
|
||||
destroy () {
|
||||
|
||||
}
|
||||
}
|
|
@ -112,7 +112,7 @@ export default class TreeMode extends Component {
|
|||
modes: this.props.options.modes,
|
||||
mode: this.props.mode,
|
||||
onChangeMode: this.props.onChangeMode,
|
||||
onError: this.handleError
|
||||
onError: this.props.onError
|
||||
})
|
||||
])
|
||||
}
|
||||
|
@ -213,16 +213,6 @@ export default class TreeMode extends Component {
|
|||
this.emitOnChange (actions, result.revert)
|
||||
}
|
||||
|
||||
/** @private */
|
||||
handleError = (err) => {
|
||||
if (this.props.options && this.props.options.onError) {
|
||||
this.props.options.onError(err)
|
||||
}
|
||||
else {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an onChange event when there is a listener for it.
|
||||
* @param {JSONPatch} patch
|
||||
|
@ -372,6 +362,16 @@ export default class TreeMode extends Component {
|
|||
return JSON.stringify(this.get(), null, indentation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a JSON schema for validation of the JSON object.
|
||||
* To remove the schema, call JSONEditor.setSchema(null)
|
||||
* @param {Object | null} schema
|
||||
*/
|
||||
setSchema (schema) {
|
||||
// TODO: implement setSchema for TreeMode
|
||||
console.error('setSchema not yet implemented for TreeMode')
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand one or multiple objects or arrays
|
||||
* @param {Path | function (path: Path) : boolean} callback
|
||||
|
@ -422,13 +422,6 @@ export default class TreeMode extends Component {
|
|||
: TreeMode.expandAll(path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the editor
|
||||
*/
|
||||
destroy () {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default function to determine whether or not to expand a node initially
|
||||
*
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
<style>
|
||||
#container {
|
||||
height: 500px;
|
||||
height: 400px;
|
||||
width: 100%;
|
||||
max-width : 800px;
|
||||
}
|
||||
|
@ -24,11 +24,13 @@
|
|||
<button id="setJSON">Set JSON</button>
|
||||
<button id="getJSON">Get JSON</button>
|
||||
|
||||
<button id="toggleSchema">Toggle JSON Schema</button>
|
||||
|
||||
<label for="mode">mode:
|
||||
<select id="mode">
|
||||
<option value="text">text</option>
|
||||
<option value="code">code</option>
|
||||
<option value="tree" selected>tree</option>
|
||||
<option value="code" selected>code</option>
|
||||
<option value="tree">tree</option>
|
||||
<option value="form">form</option>
|
||||
<option value="view">view</option>
|
||||
</select>
|
||||
|
@ -83,6 +85,28 @@
|
|||
}
|
||||
})
|
||||
|
||||
const schema = {
|
||||
"title": "Example Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"firstName": {
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"type": "string"
|
||||
},
|
||||
"gender": {
|
||||
"enum": ["male", "female"]
|
||||
},
|
||||
"age": {
|
||||
"description": "Age in years",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["firstName", "lastName"]
|
||||
}
|
||||
|
||||
// set json
|
||||
document.getElementById('setJSON').onclick = function () {
|
||||
editor.set(largeJson, {
|
||||
|
@ -92,6 +116,13 @@
|
|||
})
|
||||
}
|
||||
|
||||
// set schema
|
||||
let hasSchema = false
|
||||
document.getElementById('toggleSchema').onclick = function () {
|
||||
editor.setSchema(hasSchema ? null : schema)
|
||||
hasSchema = !hasSchema
|
||||
}
|
||||
|
||||
// get json
|
||||
document.getElementById('getJSON').onclick = function () {
|
||||
const json = editor.get()
|
||||
|
|
66
src/index.js
66
src/index.js
|
@ -1,4 +1,4 @@
|
|||
import { h, render } from 'preact'
|
||||
import { h, render, } from 'preact'
|
||||
import CodeMode from './components/CodeMode'
|
||||
import TextMode from './components/TextMode'
|
||||
import TreeMode from './components/TreeMode'
|
||||
|
@ -30,13 +30,9 @@ function jsoneditor (container, options = {}) {
|
|||
const editor = {
|
||||
isJSONEditor: true,
|
||||
|
||||
utils: {
|
||||
compileJSONPointer,
|
||||
parseJSONPointer
|
||||
},
|
||||
|
||||
_container: container,
|
||||
_options: options,
|
||||
_schema: null,
|
||||
_modes: modes,
|
||||
_mode: null,
|
||||
_element: null,
|
||||
|
@ -76,6 +72,16 @@ function jsoneditor (container, options = {}) {
|
|||
return editor._component.getText()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a JSON schema for validation of the JSON object.
|
||||
* To remove the schema, call JSONEditor.setSchema(null)
|
||||
* @param {Object | null} schema
|
||||
*/
|
||||
editor.setSchema = function (schema) {
|
||||
editor._schema = schema || null
|
||||
editor._component.setSchema(schema)
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand one or multiple objects or arrays.
|
||||
*
|
||||
|
@ -154,15 +160,33 @@ function jsoneditor (container, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleError (err) {
|
||||
if (editor._options && editor._options.onError) {
|
||||
editor._options.onError(err)
|
||||
}
|
||||
else {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// create new component
|
||||
element = render(
|
||||
h(constructor, {
|
||||
mode,
|
||||
options: editor._options,
|
||||
onChangeMode: handleChangeMode
|
||||
onChangeMode: handleChangeMode,
|
||||
onError: handleError
|
||||
}),
|
||||
editor._container)
|
||||
|
||||
// apply JSON schema (if any)
|
||||
try {
|
||||
element._component.setSchema(editor._schema)
|
||||
}
|
||||
catch (err) {
|
||||
handleError(err)
|
||||
}
|
||||
|
||||
// set JSON (this can throw an error)
|
||||
const text = editor._component ? editor._component.getText() : '{}'
|
||||
element._component.setText(text)
|
||||
|
@ -174,8 +198,7 @@ function jsoneditor (container, options = {}) {
|
|||
if (success) {
|
||||
// destroy previous component
|
||||
if (editor._element) {
|
||||
editor._element._component.destroy()
|
||||
editor._element.parentNode.removeChild(editor._element)
|
||||
unrender(container, editor._element)
|
||||
}
|
||||
|
||||
editor._mode = mode
|
||||
|
@ -185,7 +208,8 @@ function jsoneditor (container, options = {}) {
|
|||
else {
|
||||
// TODO: fall back to text mode when loading code mode failed?
|
||||
|
||||
// remove the just created component if any (where construction or setText failed)
|
||||
// remove the just created component if an error occurred during construction
|
||||
// (for example when construction or setText failed)
|
||||
const childCount = editor._container.children.length
|
||||
if (childCount !== initialChildCount) {
|
||||
editor._container.removeChild(editor._container.lastChild)
|
||||
|
@ -194,11 +218,31 @@ function jsoneditor (container, options = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: implement destroy
|
||||
/**
|
||||
* Remove the editor from the DOM and clean up workers
|
||||
*/
|
||||
editor.destroy = function () {
|
||||
unrender(container, editor._element)
|
||||
}
|
||||
|
||||
editor.setMode(options && options.mode || 'tree')
|
||||
|
||||
return editor
|
||||
}
|
||||
|
||||
// expose util functions
|
||||
jsoneditor.utils = {
|
||||
compileJSONPointer,
|
||||
parseJSONPointer
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a rendered preact component
|
||||
* @param container
|
||||
* @param root
|
||||
*/
|
||||
function unrender (container, root) {
|
||||
render('', container, root);
|
||||
}
|
||||
|
||||
module.exports = jsoneditor
|
||||
|
|
|
@ -567,3 +567,37 @@ textarea.jsoneditor-text {
|
|||
font-size: @fontSize;
|
||||
color: @black;
|
||||
}
|
||||
|
||||
div.jsoneditor-code {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: @contentsMinHeight;
|
||||
}
|
||||
|
||||
/* JSON schema errors displayed at the bottom of the editor in mode text and code */
|
||||
|
||||
.jsoneditor-text-errors {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: #ffef8b;
|
||||
border-top: 1px solid #ffd700;
|
||||
|
||||
font-family: @fontFamily;
|
||||
font-size: @fontSize;
|
||||
|
||||
td {
|
||||
padding: 3px 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.jsoneditor-schema-error {
|
||||
user-select: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
margin: 0 4px 0 0;
|
||||
background: url('img/jsoneditor-icons.svg') -168px -46px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,17 +32,19 @@
|
|||
*
|
||||
* @typedef {{
|
||||
* name: string?,
|
||||
* mode?: 'code' | 'form' | 'text' | 'tree' | 'view',
|
||||
* modes?: string[],
|
||||
* history?: boolean,
|
||||
* indentation?: number | string,
|
||||
* onChange?: function (patch: JSONPatch, revert: JSONPatch),
|
||||
* onChangeText?: function (),
|
||||
* onChangeMode?: function (mode: string, prevMode: string),
|
||||
* onError?: function (err: Error),
|
||||
* isPropertyEditable?: function (Path) : boolean
|
||||
* isValueEditable?: function (Path) : boolean,
|
||||
* escapeUnicode:? boolean
|
||||
* mode: 'code' | 'form' | 'text' | 'tree' | 'view'?,
|
||||
* modes: string[]?,
|
||||
* history: boolean?,
|
||||
* indentation: number | string?,
|
||||
* onChange: function (patch: JSONPatch, revert: JSONPatch)?,
|
||||
* onChangeText: function ()?,
|
||||
* onChangeMode: function (mode: string, prevMode: string)?,
|
||||
* onError: function (err: Error)?,
|
||||
* isPropertyEditable: function (Path)?
|
||||
* isValueEditable: function (Path)?,
|
||||
* escapeUnicode: boolean?,
|
||||
* ajv: Object?
|
||||
* ace: Object?
|
||||
* }} Options
|
||||
*
|
||||
* @typedef {{
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
// TODO: make MAX_ERRORS configurable
|
||||
export const MAX_ERRORS = 3; // maximum number of displayed errors at the bottom
|
||||
|
||||
/**
|
||||
* Enrich the error message of a JSON schema error
|
||||
* @param {Object} error
|
||||
* @return {Object} The improved error
|
||||
*/
|
||||
export function enrichSchemaError(error) {
|
||||
if (error.keyword === 'enum' && Array.isArray(error.schema)) {
|
||||
let enums = error.schema
|
||||
if (enums) {
|
||||
enums = enums.map(JSON.stringify)
|
||||
|
||||
if (enums.length > 5) {
|
||||
const more = ['(' + (enums.length - 5) + ' more...)']
|
||||
enums = enums.slice(0, 5)
|
||||
enums.push(more)
|
||||
}
|
||||
error.message = 'should be equal to one of: ' + enums.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
if (error.keyword === 'additionalProperties') {
|
||||
error.message = 'should NOT have additional property: ' + error.params.additionalProperty
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Limit the number of errors.
|
||||
* If the number of errors exceeds the maximum, the tail is removed and
|
||||
* a message that there are more errors is added
|
||||
* @param {Array} errors
|
||||
* @return {Array} Returns limited items
|
||||
*/
|
||||
export function limitErrors (errors) {
|
||||
if (errors.length > MAX_ERRORS) {
|
||||
const hidden = errors.length - MAX_ERRORS
|
||||
let limitedErrors = errors.slice(0, MAX_ERRORS)
|
||||
limitedErrors.push('(' + hidden + ' more errors...)')
|
||||
return limitedErrors
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
Loading…
Reference in New Issue