Restructured search data model
This commit is contained in:
parent
198e8edf85
commit
100efb35ae
12
README.md
12
README.md
|
@ -152,3 +152,15 @@ jsoneditor:
|
||||||
|
|
||||||
This will update `./dist/jsoneditor.js` on every change in the source code,
|
This will update `./dist/jsoneditor.js` on every change in the source code,
|
||||||
but it will **NOT** update the minimalist version.
|
but it will **NOT** update the minimalist version.
|
||||||
|
|
||||||
|
- Run unit tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
or to watch for changes and re-run tests automatically:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run watch:test
|
||||||
|
```
|
||||||
|
|
10
package.json
10
package.json
|
@ -21,7 +21,8 @@
|
||||||
"start": "gulp watch",
|
"start": "gulp watch",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"flow": "flow; test $? -eq 0 -o $? -eq 2",
|
"flow": "flow; test $? -eq 0 -o $? -eq 2",
|
||||||
"test": "ava test/*.test.js test/**/*.test.js --verbose"
|
"test": "ava --verbose",
|
||||||
|
"watch:test": "ava --verbose --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ajv": "4.9.2",
|
"ajv": "4.9.2",
|
||||||
|
@ -55,9 +56,16 @@
|
||||||
"webpack": "1.14.0"
|
"webpack": "1.14.0"
|
||||||
},
|
},
|
||||||
"ava": {
|
"ava": {
|
||||||
|
"files": [
|
||||||
|
"test/**/*.test.js"
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"./src/**/*"
|
||||||
|
],
|
||||||
"require": [
|
"require": [
|
||||||
"babel-register"
|
"babel-register"
|
||||||
],
|
],
|
||||||
|
"concurrency": 4,
|
||||||
"babel": "inherit"
|
"babel": "inherit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ import { escapeHTML, unescapeHTML } from '../utils/stringUtils'
|
||||||
import { getInnerText, insideRect } from '../utils/domUtils'
|
import { getInnerText, insideRect } from '../utils/domUtils'
|
||||||
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
import { stringConvert, valueType, isUrl } from '../utils/typeUtils'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
PropertyData, ObjectData, ArrayData, JSONData,
|
||||||
|
SearchResult, SearchResultStatus } from '../types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {JSONNode | null} activeContextMenu singleton holding the JSONNode having
|
* @type {JSONNode | null} activeContextMenu singleton holding the JSONNode having
|
||||||
* the active (visible) context menu
|
* the active (visible) context menu
|
||||||
|
@ -36,13 +40,13 @@ export default class JSONNode extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONObject ({prop, data, options, events}) {
|
renderJSONObject ({prop, index, data, options, events}) {
|
||||||
const childCount = data.props.length
|
const childCount = data.props.length
|
||||||
const contents = [
|
const contents = [
|
||||||
h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-object'}, [
|
h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-object'}, [
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
this.renderActionMenuButton(),
|
this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, data, options),
|
this.renderProperty(prop, index, data, options),
|
||||||
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`),
|
this.renderReadonly(`{${childCount}}`, `Array containing ${childCount} items`),
|
||||||
this.renderError(data.error)
|
this.renderError(data.error)
|
||||||
])
|
])
|
||||||
|
@ -54,7 +58,7 @@ export default class JSONNode extends Component {
|
||||||
return h(this.constructor, {
|
return h(this.constructor, {
|
||||||
key: prop.name,
|
key: prop.name,
|
||||||
parent: this,
|
parent: this,
|
||||||
prop: prop.name,
|
prop: prop,
|
||||||
data: prop.value,
|
data: prop.value,
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
|
@ -73,13 +77,13 @@ export default class JSONNode extends Component {
|
||||||
return h('li', {}, contents)
|
return h('li', {}, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONArray ({prop, data, options, events}) {
|
renderJSONArray ({prop, index, data, options, events}) {
|
||||||
const childCount = data.items.length
|
const childCount = data.items.length
|
||||||
const contents = [
|
const contents = [
|
||||||
h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-array'}, [
|
h('div', {key: 'node', className: 'jsoneditor-node jsoneditor-array'}, [
|
||||||
this.renderExpandButton(),
|
this.renderExpandButton(),
|
||||||
this.renderActionMenuButton(),
|
this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, data, options),
|
this.renderProperty(prop, index, data, options),
|
||||||
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`),
|
this.renderReadonly(`[${childCount}]`, `Array containing ${childCount} items`),
|
||||||
this.renderError(data.error)
|
this.renderError(data.error)
|
||||||
])
|
])
|
||||||
|
@ -91,7 +95,7 @@ export default class JSONNode extends Component {
|
||||||
return h(this.constructor, {
|
return h(this.constructor, {
|
||||||
key: index,
|
key: index,
|
||||||
parent: this,
|
parent: this,
|
||||||
prop: index,
|
index,
|
||||||
data: child,
|
data: child,
|
||||||
options,
|
options,
|
||||||
events
|
events
|
||||||
|
@ -109,14 +113,14 @@ export default class JSONNode extends Component {
|
||||||
return h('li', {}, contents)
|
return h('li', {}, contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderJSONValue ({prop, data, options}) {
|
renderJSONValue ({prop, index, data, options}) {
|
||||||
return h('li', {},
|
return h('li', {},
|
||||||
h('div', {className: 'jsoneditor-node'}, [
|
h('div', {className: 'jsoneditor-node'}, [
|
||||||
this.renderPlaceholder(),
|
this.renderPlaceholder(),
|
||||||
this.renderActionMenuButton(),
|
this.renderActionMenuButton(),
|
||||||
this.renderProperty(prop, data, options),
|
this.renderProperty(prop, index, data, options),
|
||||||
this.renderSeparator(),
|
this.renderSeparator(),
|
||||||
this.renderValue(data.value, data.searchValue, options),
|
this.renderValue(data.value, data.searchResult, options),
|
||||||
this.renderError(data.error)
|
this.renderError(data.error)
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
@ -145,8 +149,10 @@ export default class JSONNode extends Component {
|
||||||
return h('div', {key: 'readonly', className: 'jsoneditor-readonly', title}, text)
|
return h('div', {key: 'readonly', className: 'jsoneditor-readonly', title}, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderProperty (prop, data, options) {
|
renderProperty (prop: ?PropertyData, index: ?number, data: JSONData, options) {
|
||||||
if (prop === null) {
|
const isIndex = typeof index === 'number'
|
||||||
|
|
||||||
|
if (!prop && !isIndex) {
|
||||||
// root node
|
// root node
|
||||||
const rootName = JSONNode.getRootName(data, options)
|
const rootName = JSONNode.getRootName(data, options)
|
||||||
|
|
||||||
|
@ -159,29 +165,27 @@ export default class JSONNode extends Component {
|
||||||
}, rootName)
|
}, rootName)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isIndex = typeof prop === 'number' // FIXME: pass an explicit prop isIndex or editable
|
|
||||||
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.getPath()))
|
const editable = !isIndex && (!options.isPropertyEditable || options.isPropertyEditable(this.getPath()))
|
||||||
|
|
||||||
const emptyClassName = (prop.length === 0 ? ' jsoneditor-empty' : '')
|
const emptyClassName = (prop && prop.name.length === 0) ? ' jsoneditor-empty' : ''
|
||||||
const searchClassName = data.searchProperty ? ' jsoneditor-highlight': '';
|
const searchClassName = prop ? JSONNode.getSearchResultClass(prop.searchResult) : ''
|
||||||
|
const escapedPropName = prop ? escapeHTML(prop.name, options.escapeUnicode) : null
|
||||||
|
|
||||||
if (editable) {
|
if (editable) {
|
||||||
const escapedProp = escapeHTML(prop, options.escapeUnicode)
|
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
key: 'property',
|
key: 'property',
|
||||||
className: 'jsoneditor-property' + emptyClassName + searchClassName,
|
className: 'jsoneditor-property' + emptyClassName + searchClassName,
|
||||||
contentEditable: 'true',
|
contentEditable: 'true',
|
||||||
spellCheck: 'false',
|
spellCheck: 'false',
|
||||||
onBlur: this.handleChangeProperty
|
onBlur: this.handleChangeProperty
|
||||||
}, escapedProp)
|
}, escapedPropName)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
key: 'property',
|
key: 'property',
|
||||||
className: 'jsoneditor-property jsoneditor-readonly' + searchClassName,
|
className: 'jsoneditor-property jsoneditor-readonly' + searchClassName,
|
||||||
spellCheck: 'false'
|
spellCheck: 'false'
|
||||||
}, prop)
|
}, isIndex ? index : escapedPropName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,7 +193,7 @@ export default class JSONNode extends Component {
|
||||||
return h('div', {key: 'separator', className: 'jsoneditor-separator'}, ':')
|
return h('div', {key: 'separator', className: 'jsoneditor-separator'}, ':')
|
||||||
}
|
}
|
||||||
|
|
||||||
renderValue (value, searchValue, options) {
|
renderValue (value, searchResult, options) {
|
||||||
const escapedValue = escapeHTML(value, options.escapeUnicode)
|
const escapedValue = escapeHTML(value, options.escapeUnicode)
|
||||||
const type = valueType (value)
|
const type = valueType (value)
|
||||||
const itsAnUrl = isUrl(value)
|
const itsAnUrl = isUrl(value)
|
||||||
|
@ -200,7 +204,8 @@ export default class JSONNode extends Component {
|
||||||
return h('div', {
|
return h('div', {
|
||||||
key: 'value',
|
key: 'value',
|
||||||
ref: 'value',
|
ref: 'value',
|
||||||
className: JSONNode.getValueClass(type, itsAnUrl, isEmpty, searchValue),
|
className: JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
||||||
|
JSONNode.getSearchResultClass(searchResult),
|
||||||
contentEditable: 'true',
|
contentEditable: 'true',
|
||||||
spellCheck: 'false',
|
spellCheck: 'false',
|
||||||
onBlur: this.handleChangeValue,
|
onBlur: this.handleChangeValue,
|
||||||
|
@ -283,7 +288,10 @@ export default class JSONNode extends Component {
|
||||||
target = target.parentNode
|
target = target.parentNode
|
||||||
}
|
}
|
||||||
|
|
||||||
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty)
|
console.log('value', this.props)
|
||||||
|
|
||||||
|
target.className = JSONNode.getValueClass(type, itsAnUrl, isEmpty) +
|
||||||
|
JSONNode.getSearchResultClass(this.props.data.searchResult)
|
||||||
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
target.title = itsAnUrl ? JSONNode.URL_TITLE : ''
|
||||||
|
|
||||||
// remove all classNames from childs (needed for IE and Edge)
|
// remove all classNames from childs (needed for IE and Edge)
|
||||||
|
@ -295,18 +303,29 @@ export default class JSONNode extends Component {
|
||||||
* @param {string} type
|
* @param {string} type
|
||||||
* @param {boolean} isUrl
|
* @param {boolean} isUrl
|
||||||
* @param {boolean} isEmpty
|
* @param {boolean} isEmpty
|
||||||
* @param {'normal' | 'active'} [searchValue]
|
|
||||||
* @return {string}
|
* @return {string}
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
static getValueClass (type, isUrl, isEmpty, searchValue) {
|
static getValueClass (type, isUrl, isEmpty) {
|
||||||
return 'jsoneditor-value ' +
|
return 'jsoneditor-value ' +
|
||||||
'jsoneditor-' + type +
|
'jsoneditor-' + type +
|
||||||
(isUrl ? ' jsoneditor-url' : '') +
|
(isUrl ? ' jsoneditor-url' : '') +
|
||||||
(isEmpty ? ' jsoneditor-empty' : '') +
|
(isEmpty ? ' jsoneditor-empty' : '')
|
||||||
(searchValue === 'active'
|
}
|
||||||
? ' jsoneditor-highlight-active'
|
|
||||||
: (searchValue ? ' jsoneditor-highlight' : ''))
|
/**
|
||||||
|
* Get the css style given a search result type
|
||||||
|
*/
|
||||||
|
static getSearchResultClass (searchResultStatus: ?SearchResultStatus) {
|
||||||
|
if (searchResultStatus === 'active') {
|
||||||
|
return ' jsoneditor-highlight-active'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (searchResultStatus === 'normal') {
|
||||||
|
return ' jsoneditor-highlight'
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -400,7 +419,7 @@ export default class JSONNode extends Component {
|
||||||
/** @private */
|
/** @private */
|
||||||
handleChangeProperty = (event) => {
|
handleChangeProperty = (event) => {
|
||||||
const parentPath = this.props.parent.getPath()
|
const parentPath = this.props.parent.getPath()
|
||||||
const oldProp = this.props.prop
|
const oldProp = this.props.prop.name
|
||||||
const newProp = unescapeHTML(getInnerText(event.target))
|
const newProp = unescapeHTML(getInnerText(event.target))
|
||||||
|
|
||||||
if (newProp !== oldProp) {
|
if (newProp !== oldProp) {
|
||||||
|
@ -525,8 +544,12 @@ export default class JSONNode extends Component {
|
||||||
? this.props.parent.getPath()
|
? this.props.parent.getPath()
|
||||||
: []
|
: []
|
||||||
|
|
||||||
if (this.props.prop !== null) {
|
if (typeof this.props.index === 'number') {
|
||||||
path.push(this.props.prop)
|
path.push(String(this.props.index))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.props.prop) {
|
||||||
|
path.push(this.props.prop.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -124,7 +124,7 @@ export default class TreeMode extends Component {
|
||||||
// data = addFocus(data, searchResults[0]) // TODO: change to using focus from state
|
// data = addFocus(data, searchResults[0]) // TODO: change to using focus from state
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('data', data)
|
// console.log('data', data)
|
||||||
|
|
||||||
return h('div', {
|
return h('div', {
|
||||||
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
className: `jsoneditor jsoneditor-mode-${props.mode}`,
|
||||||
|
|
|
@ -516,17 +516,18 @@ export function addErrors (data, errors) {
|
||||||
export function search (data, text): SearchResult[] {
|
export function search (data, text): SearchResult[] {
|
||||||
let results: SearchResult[] = []
|
let results: SearchResult[] = []
|
||||||
|
|
||||||
traverse(data, function (value, path, root) {
|
traverse(data, function (value, path) {
|
||||||
// check property name
|
// check property name
|
||||||
const prop = last(path)
|
if (path.length > 0) {
|
||||||
|
const prop = last(path)
|
||||||
if (typeof prop === 'string' && containsCaseInsensitive(prop, text)) {
|
if (containsCaseInsensitive(prop, text)) {
|
||||||
// only add search result when this is an object property name,
|
// only add search result when this is an object property name,
|
||||||
// don't add search result for array indices
|
// don't add search result for array indices
|
||||||
const parentPath = path.slice(0, path.length - 1)
|
const parentPath = allButLast(path)
|
||||||
const parent = getIn(root, toDataPath(data, parentPath))
|
const parent = getIn(data, toDataPath(data, parentPath))
|
||||||
if (parent.type === 'Object') {
|
if (parent.type === 'Object') {
|
||||||
results.push({ dataPath: path, type: 'property' })
|
results.push({ dataPath: path, type: 'property' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,15 +551,16 @@ export function addSearchResults (data, searchResults: SearchResult[], activeSea
|
||||||
if (searchResults) {
|
if (searchResults) {
|
||||||
searchResults.forEach(function (searchResult) {
|
searchResults.forEach(function (searchResult) {
|
||||||
if (searchResult.type === 'value') {
|
if (searchResult.type === 'value') {
|
||||||
const dataPath = toDataPath(data, searchResult.dataPath).concat('searchValue')
|
const dataPath = toDataPath(data, searchResult.dataPath).concat('searchResult')
|
||||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||||
updatedData = setIn(updatedData, dataPath, value)
|
updatedData = setIn(updatedData, dataPath, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchResult.type === 'property') {
|
if (searchResult.type === 'property') {
|
||||||
const dataPath = toDataPath(data, searchResult.dataPath).concat('searchProperty')
|
const valueDataPath = toDataPath(data, searchResult.dataPath)
|
||||||
|
const propertyDataPath = allButLast(valueDataPath).concat('searchResult')
|
||||||
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
const value = isEqual(searchResult, activeSearchResult) ? 'active' : 'normal'
|
||||||
updatedData = setIn(updatedData, dataPath, value)
|
updatedData = setIn(updatedData, propertyDataPath, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -591,7 +593,7 @@ export function addFocus (data, focusOn) {
|
||||||
* @param {String} search
|
* @param {String} search
|
||||||
* @return {boolean} Returns true if `search` is found in `text`
|
* @return {boolean} Returns true if `search` is found in `text`
|
||||||
*/
|
*/
|
||||||
export function containsCaseInsensitive (text, search) {
|
export function containsCaseInsensitive (text: string, search: string): boolean {
|
||||||
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
|
return String(text).toLowerCase().indexOf(search.toLowerCase()) !== -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -799,6 +801,13 @@ export function compileJSONPointer (path) {
|
||||||
/**
|
/**
|
||||||
* Returns the last item of an array
|
* Returns the last item of an array
|
||||||
*/
|
*/
|
||||||
function last (array: Array): any {
|
function last (array: []): any {
|
||||||
return array[array.length - 1]
|
return array[array.length - 1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the array having the last item removed
|
||||||
|
*/
|
||||||
|
function allButLast (array: []): any {
|
||||||
|
return array.slice(0, array.length - 1)
|
||||||
|
}
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* type: 'Array',
|
|
||||||
* expanded: boolean?,
|
|
||||||
* props: Array.<{name: string, value: JSONData}>?
|
|
||||||
* }} ObjectData
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* type: 'Object',
|
|
||||||
* expanded: boolean?,
|
|
||||||
* items: JSONData[]?
|
|
||||||
* }} ArrayData
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* type: 'value' | 'string',
|
|
||||||
* value: *?
|
|
||||||
* }} ValueData
|
|
||||||
*
|
|
||||||
* @typedef {Array.<string>} Path
|
|
||||||
*
|
|
||||||
* @typedef {ObjectData | ArrayData | ValueData} JSONData
|
|
||||||
*
|
|
||||||
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
|
|
||||||
*
|
|
||||||
* @typedef {Array.<{op: string, path?: string, from?: string, value?: *}>} JSONPatch
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* patch: JSONPatch,
|
|
||||||
* revert: JSONPatch,
|
|
||||||
* error: null | Error
|
|
||||||
* }} JSONPatchResult
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* dataPath: string,
|
|
||||||
* message: string
|
|
||||||
* }} JSONSchemaError
|
|
||||||
*
|
|
||||||
* @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)?
|
|
||||||
* isValueEditable: function (Path)?,
|
|
||||||
* escapeUnicode: boolean?,
|
|
||||||
* ajv: Object?
|
|
||||||
* ace: Object?
|
|
||||||
* }} Options
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* expand: function (path: Path)?
|
|
||||||
* }} SetOptions
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* expand: function (path: Path)?
|
|
||||||
* }} PatchOptions
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* dataPath: Path,
|
|
||||||
* property: boolean?,
|
|
||||||
* value: boolean?
|
|
||||||
* }} SearchResult
|
|
||||||
* // TODO: SearchResult.dataPath is an array, JSONSchemaError.dataPath is a string -> make this consistent
|
|
||||||
*/
|
|
83
src/types.js
83
src/types.js
|
@ -1,29 +1,9 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
|
||||||
* type: 'Array',
|
|
||||||
* expanded: boolean?,
|
|
||||||
* props: Array.<{name: string, value: JSONData}>?
|
|
||||||
* }} ObjectData
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* type: 'Object',
|
|
||||||
* expanded: boolean?,
|
|
||||||
* items: JSONData[]?
|
|
||||||
* }} ArrayData
|
|
||||||
*
|
|
||||||
* @typedef {{
|
|
||||||
* type: 'value' | 'string',
|
|
||||||
* value: *?
|
|
||||||
* }} ValueData
|
|
||||||
*
|
|
||||||
* @typedef {Array.<string>} Path
|
|
||||||
*
|
|
||||||
* @typedef {ObjectData | ArrayData | ValueData} JSONData
|
|
||||||
*
|
*
|
||||||
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
|
* @typedef {'Object' | 'Array' | 'value' | 'string'} JSONDataType
|
||||||
|
*
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* patch: JSONPatch,
|
* patch: JSONPatch,
|
||||||
* revert: JSONPatch,
|
* revert: JSONPatch,
|
||||||
|
@ -57,36 +37,63 @@
|
||||||
* expand: function (path: Path)?
|
* expand: function (path: Path)?
|
||||||
* }} PatchOptions
|
* }} PatchOptions
|
||||||
*
|
*
|
||||||
* @typedef {{
|
|
||||||
* dataPath: Path,
|
|
||||||
* property: boolean?,
|
|
||||||
* value: boolean?
|
|
||||||
* }} SearchResult
|
|
||||||
* // TODO: SearchResult.dataPath is an array, JSONSchemaError.dataPath is a string -> make this consistent
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type JSONType = | string | number | boolean | null | JSONObjectType | JSONArrayType;
|
|
||||||
type JSONObjectType = { [key:string]: JSON };
|
/**************************** GENERIC JSON TYPES ******************************/
|
||||||
type JSONArrayType = Array<JSON>;
|
|
||||||
|
export type JSONType = | string | number | boolean | null | JSONObjectType | JSONArrayType;
|
||||||
|
export type JSONObjectType = { [key:string]: JSONType };
|
||||||
|
export type JSONArrayType = Array<JSONType>;
|
||||||
|
|
||||||
|
|
||||||
|
/********************** TYPES FOR THE JSON DATA MODEL *************************/
|
||||||
|
|
||||||
|
export type SearchResultStatus = 'normal' | 'active'
|
||||||
|
export type SearchResultType = 'value' | 'property'
|
||||||
|
|
||||||
|
export type PropertyData = {
|
||||||
|
name: string,
|
||||||
|
value: JSONData,
|
||||||
|
searchResult: ?SearchResultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ObjectData = {
|
||||||
|
type: 'Object',
|
||||||
|
expanded: ?boolean,
|
||||||
|
props: PropertyData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArrayData = {
|
||||||
|
type: 'Array',
|
||||||
|
expanded: ?boolean,
|
||||||
|
items: ?JSONData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ValueData = {
|
||||||
|
type: 'value' | 'string',
|
||||||
|
value: ?any,
|
||||||
|
searchResult: ?SearchResultStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JSONData = ObjectData | ArrayData | ValueData
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type Path = string[]
|
export type Path = string[]
|
||||||
|
|
||||||
export type SearchResult = {
|
export type SearchResult = {
|
||||||
dataPath: Path,
|
dataPath: Path,
|
||||||
type: 'value' | 'property'
|
type: SearchResultType
|
||||||
}
|
}
|
||||||
|
// TODO: SearchResult.dataPath is an array, JSONSchemaError.dataPath is a string -> make this consistent
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: remove SetOptions, merge into Options (everywhere in the public API)
|
||||||
export type SetOptions = {
|
export type SetOptions = {
|
||||||
expand?: (path: Path) => boolean
|
expand?: (path: Path) => boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type JSONEditorMode = {
|
|
||||||
setSchema: (schema?: Object) => void,
|
|
||||||
set: (JSON) => void,
|
|
||||||
setText: (text: string) => void,
|
|
||||||
getText: () => string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type JSONPatchAction = {
|
export type JSONPatchAction = {
|
||||||
op: string, // TODO: define allowed ops
|
op: string, // TODO: define allowed ops
|
||||||
path?: string,
|
path?: string,
|
||||||
|
|
|
@ -277,9 +277,9 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
||||||
name: 'last',
|
name: 'last',
|
||||||
value: {
|
value: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
value: 4,
|
value: 4
|
||||||
searchProperty: 'active'
|
},
|
||||||
}
|
searchResult: 'active'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
||||||
value: {
|
value: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
value: 'hello world',
|
value: 'hello world',
|
||||||
searchValue: 'normal'
|
searchResult: 'normal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -302,18 +302,18 @@ const JSON_DATA_EXAMPLE_SEARCH_L = {
|
||||||
value: {
|
value: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
value: null,
|
value: null,
|
||||||
searchProperty: 'normal',
|
searchResult: 'normal'
|
||||||
searchValue: 'normal'
|
},
|
||||||
}
|
searchResult: 'normal'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bool',
|
name: 'bool',
|
||||||
value: {
|
value: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
value: false,
|
value: false,
|
||||||
searchProperty: 'normal',
|
searchResult: 'normal'
|
||||||
searchValue: 'normal'
|
},
|
||||||
}
|
searchResult: 'normal'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -935,7 +935,7 @@ test('search', t => {
|
||||||
|
|
||||||
const activeSearchResult = searchResults[0]
|
const activeSearchResult = searchResults[0]
|
||||||
const updatedData = addSearchResults(JSON_DATA_EXAMPLE, searchResults, activeSearchResult)
|
const updatedData = addSearchResults(JSON_DATA_EXAMPLE, searchResults, activeSearchResult)
|
||||||
//console.log(JSON.stringify(updatedData, null, 2))
|
// console.log(JSON.stringify(updatedData, null, 2))
|
||||||
|
|
||||||
t.deepEqual(updatedData, JSON_DATA_EXAMPLE_SEARCH_L)
|
t.deepEqual(updatedData, JSON_DATA_EXAMPLE_SEARCH_L)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue