@json-editor/json-editor 2.5.4 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build.yml +12 -3
- package/CHANGELOG.md +51 -14
- package/README.md +159 -20
- package/dist/jsoneditor.js +2 -2
- package/dist/nonmin/jsoneditor.js +5483 -3610
- package/dist/nonmin/jsoneditor.js.map +1 -1
- package/docs/cleave.html +1 -1
- package/docs/datetime.html +1 -1
- package/docs/describedby.html +1 -1
- package/docs/form-submission.html +76 -0
- package/docs/index.html +2 -2
- package/docs/materialize_css.html +1 -1
- package/docs/meta_schema.json +0 -1
- package/docs/radio.html +1 -1
- package/docs/select2.html +1 -1
- package/docs/selectize.html +1 -1
- package/docs/starrating.html +1 -1
- package/docs/wysiwyg.html +1 -1
- package/package.json +28 -26
- package/release-notes.md +5 -3
- package/src/core.js +36 -37
- package/src/defaults.js +9 -2
- package/src/editor.js +6 -2
- package/src/editors/array.js +12 -1
- package/src/editors/autocomplete.js +4 -3
- package/src/editors/button.js +1 -1
- package/src/editors/multiple.js +2 -0
- package/src/editors/multiselect.js +14 -5
- package/src/editors/object.js +10 -6
- package/src/editors/radio.js +6 -1
- package/src/editors/string.js +7 -1
- package/src/editors/table.js +21 -2
- package/src/editors/upload.js +1 -1
- package/src/editors/uuid.js +2 -12
- package/src/iconlib.js +1 -1
- package/src/schemaloader.js +232 -109
- package/src/style.css +3 -0
- package/src/style.css.js +1 -1
- package/src/theme.js +5 -4
- package/src/themes/bootstrap3.js +3 -2
- package/src/themes/bootstrap4.js +7 -0
- package/src/themes/spectre.js +2 -1
- package/src/utilities.js +18 -0
- package/src/validator.js +65 -47
- package/tests/codeceptjs/codecept.json +1 -1
- package/tests/codeceptjs/core_test.js +98 -0
- package/tests/codeceptjs/editors/advanced_test.js +1 -1
- package/tests/codeceptjs/editors/array_test.js +74 -0
- package/tests/codeceptjs/editors/autocomplete_test.js +16 -0
- package/tests/codeceptjs/editors/button_test.js +11 -4
- package/tests/codeceptjs/editors/integer_test.js +7 -2
- package/tests/codeceptjs/editors/jodit_test.js +3 -3
- package/tests/codeceptjs/editors/object_test.js +84 -13
- package/tests/codeceptjs/editors/range_test.js +12 -0
- package/tests/codeceptjs/editors/stepper_test.js +12 -0
- package/tests/codeceptjs/editors/uuid_test.js +31 -4
- package/tests/codeceptjs/editors/validation_test.js +1 -1
- package/tests/docker-compose.yml +1 -1
- package/tests/fixtures/definitions.json +22 -0
- package/tests/fixtures/properties.json +20 -0
- package/tests/fixtures/validation.json +207 -0
- package/tests/pages/array-checkboxes-infotext.html +52 -0
- package/tests/pages/array-move-events.html +4 -2
- package/tests/pages/array-unique-items-sort.html +78 -0
- package/tests/pages/autocomplete.html +69 -0
- package/tests/pages/button-icons.html +38 -0
- package/tests/pages/core.html +4 -2
- package/tests/pages/error-messages.html +47 -0
- package/tests/pages/grid-strict.html +6 -10
- package/tests/pages/grid.html +0 -4
- package/tests/pages/issues/issue-gh-812.html +4 -2
- package/tests/pages/meta_schema.json +14 -1
- package/tests/pages/object-required-properties.html +37 -14
- package/tests/pages/object-show-opt-in.html +110 -0
- package/tests/pages/object-with-dependencies-array.html +29 -19
- package/tests/pages/range.html +60 -0
- package/tests/pages/ready.html +43 -0
- package/tests/pages/references.html +162 -0
- package/tests/pages/stepper-manual.html +57 -0
- package/tests/pages/string-simplemde-editor.html +81 -0
- package/tests/pages/table-move-events.html +4 -1
- package/tests/pages/urn.html +11 -8
- package/tests/pages/uuid.html +89 -50
- package/tests/pages/validation-messages.json +705 -0
- package/tests/unit/core.spec.js +79 -66
- package/tests/unit/editor.spec.js +20 -8
- package/tests/unit/editors/array.spec.js +3 -2
- package/tests/unit/editors/object.spec.js +3 -1
- package/tests/unit/editors/table.spec.js +4 -2
- package/tests/unit/schemaloader.spec.js +77 -105
- package/tests/unit/validator.spec.js +10 -0
package/src/editors/radio.js
CHANGED
|
@@ -2,7 +2,6 @@ import { SelectEditor } from './select.js'
|
|
|
2
2
|
|
|
3
3
|
export class RadioEditor extends SelectEditor {
|
|
4
4
|
preBuild () {
|
|
5
|
-
this.schema.required = true /* force editor into required mode to prevent creation of empty radio button */
|
|
6
5
|
super.preBuild()
|
|
7
6
|
}
|
|
8
7
|
|
|
@@ -22,6 +21,12 @@ export class RadioEditor extends SelectEditor {
|
|
|
22
21
|
this.onChange(true)
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
if (!this.isRequired()) {
|
|
25
|
+
this.enum_display.shift()
|
|
26
|
+
this.enum_options.shift()
|
|
27
|
+
this.enum_values.shift()
|
|
28
|
+
}
|
|
29
|
+
|
|
25
30
|
for (let i = 0; i < this.enum_values.length; i++) {
|
|
26
31
|
/* form radio elements */
|
|
27
32
|
this.input = this.theme.getFormRadio({
|
package/src/editors/string.js
CHANGED
|
@@ -6,12 +6,14 @@ export class StringEditor extends AbstractEditor {
|
|
|
6
6
|
super.register()
|
|
7
7
|
if (!this.input) return
|
|
8
8
|
this.input.setAttribute('name', this.formname)
|
|
9
|
+
this.input.setAttribute('aria-label', this.formname)
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
unregister () {
|
|
12
13
|
super.unregister()
|
|
13
14
|
if (!this.input) return
|
|
14
15
|
this.input.removeAttribute('name')
|
|
16
|
+
this.input.removeAttribute('aria-label')
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
setValue (value, initial, fromTemplate) {
|
|
@@ -201,7 +203,7 @@ export class StringEditor extends AbstractEditor {
|
|
|
201
203
|
input = this.theme.getRangeControl(this.input, this.theme.getRangeOutput(this.input, this.schema.default || Math.max(this.schema.minimum || 0, 0)))
|
|
202
204
|
}
|
|
203
205
|
|
|
204
|
-
this.control = this.theme.getFormControl(this.label, input, this.description, this.infoButton)
|
|
206
|
+
this.control = this.theme.getFormControl(this.label, input, this.description, this.infoButton, this.formname)
|
|
205
207
|
this.container.appendChild(this.control)
|
|
206
208
|
|
|
207
209
|
/* Any special formatting that needs to happen after the input is added to the dom */
|
|
@@ -211,6 +213,10 @@ export class StringEditor extends AbstractEditor {
|
|
|
211
213
|
/* it will generate an error trying to append it to the missing parentNode */
|
|
212
214
|
if (this.input.parentNode) this.afterInputReady()
|
|
213
215
|
if (this.adjust_height) this.adjust_height(this.input)
|
|
216
|
+
if (this.format === 'range') {
|
|
217
|
+
const output = this.control.querySelector('output')
|
|
218
|
+
output.value = this.input.value
|
|
219
|
+
}
|
|
214
220
|
})
|
|
215
221
|
|
|
216
222
|
/* Compile and store the template */
|
package/src/editors/table.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ArrayEditor } from './array.js'
|
|
2
|
-
import { extend, trigger } from '../utilities.js'
|
|
2
|
+
import { extend, generateUUID, trigger } from '../utilities.js'
|
|
3
3
|
|
|
4
4
|
export class TableEditor extends ArrayEditor {
|
|
5
5
|
register () {
|
|
@@ -99,6 +99,7 @@ export class TableEditor extends ArrayEditor {
|
|
|
99
99
|
|
|
100
100
|
/* Row Controls column */
|
|
101
101
|
this.controls_header_cell = this.theme.getTableHeaderCell(' ')
|
|
102
|
+
this.controls_header_cell.setAttribute('aria-hidden', 'true')
|
|
102
103
|
this.header_row.appendChild(this.controls_header_cell)
|
|
103
104
|
|
|
104
105
|
/* Add controls */
|
|
@@ -346,6 +347,7 @@ export class TableEditor extends ArrayEditor {
|
|
|
346
347
|
|
|
347
348
|
_createCopyButton (i, holder) {
|
|
348
349
|
const button = this.getButton('', 'copy', 'button_copy_row_title_short')
|
|
350
|
+
const schema = this.schema
|
|
349
351
|
button.classList.add('copy', 'json-editor-btntype-copy')
|
|
350
352
|
button.setAttribute('data-i', i)
|
|
351
353
|
button.addEventListener('click', e => {
|
|
@@ -354,8 +356,25 @@ export class TableEditor extends ArrayEditor {
|
|
|
354
356
|
const j = e.currentTarget.getAttribute('data-i') * 1
|
|
355
357
|
const value = this.getValue()
|
|
356
358
|
|
|
357
|
-
|
|
359
|
+
let newValue = value[j]
|
|
360
|
+
|
|
361
|
+
/* On copy, recreate uuid if needed. */
|
|
362
|
+
if (schema.items.type === 'string' && schema.items.format === 'uuid') {
|
|
363
|
+
newValue = generateUUID()
|
|
364
|
+
} else if (schema.items.type === 'object' && schema.items.properties) {
|
|
365
|
+
value.forEach((row, i) => {
|
|
366
|
+
if (j === i) {
|
|
367
|
+
for (const key of Object.keys(row)) {
|
|
368
|
+
if (schema.items.properties && schema.items.properties[key] && schema.items.properties[key].format === 'uuid') {
|
|
369
|
+
newValue = Object.assign({}, value[j])
|
|
370
|
+
newValue[key] = generateUUID()
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
}
|
|
358
376
|
|
|
377
|
+
value.splice(j + 1, 0, newValue)
|
|
359
378
|
this.setValue(value)
|
|
360
379
|
this.onChange(true)
|
|
361
380
|
this.jsoneditor.trigger('copyRow', this.rows[j + 1])
|
package/src/editors/upload.js
CHANGED
|
@@ -257,7 +257,7 @@ export class UploadEditor extends AbstractEditor {
|
|
|
257
257
|
|
|
258
258
|
if (this.options.auto_upload) {
|
|
259
259
|
uploadButton.dispatchEvent(new window.MouseEvent('click'))
|
|
260
|
-
|
|
260
|
+
uploadButton.parentNode.removeChild(uploadButton)
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
package/src/editors/uuid.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { generateUUID } from '../utilities.js'
|
|
1
2
|
import { StringEditor } from './string.js'
|
|
2
3
|
|
|
3
4
|
export class UuidEditor extends StringEditor {
|
|
@@ -36,18 +37,7 @@ export class UuidEditor extends StringEditor {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
getUuid () {
|
|
39
|
-
|
|
40
|
-
let d = new Date().getTime()
|
|
41
|
-
|
|
42
|
-
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
|
43
|
-
d += performance.now() /* use high-precision timer if available */
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
47
|
-
const r = (d + Math.random() * 16) % 16 | 0
|
|
48
|
-
d = Math.floor(d / 16)
|
|
49
|
-
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
|
|
50
|
-
})
|
|
40
|
+
return generateUUID()
|
|
51
41
|
}
|
|
52
42
|
|
|
53
43
|
testUuid (value) {
|
package/src/iconlib.js
CHANGED
package/src/schemaloader.js
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
import { extend, hasOwnProperty } from './utilities.js'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Handles loading Schema and tracking references.
|
|
5
|
+
*/
|
|
3
6
|
export class SchemaLoader {
|
|
4
7
|
constructor (options) {
|
|
8
|
+
/**
|
|
9
|
+
* @prop {object}
|
|
10
|
+
* Options of the schema. @see readme.
|
|
11
|
+
*/
|
|
5
12
|
this.options = options || {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @prop {object}
|
|
16
|
+
* The orginial schema to load
|
|
17
|
+
*/
|
|
18
|
+
this.schema = {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @prop {object}
|
|
22
|
+
* Storage of External ref. Exemple :
|
|
23
|
+
* refs = {
|
|
24
|
+
* "fully/realized/path/to/schema.json": { ... }
|
|
25
|
+
* "mylocalschema.json": { ... }
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
6
28
|
this.refs = this.options.refs || {}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @prop {object}
|
|
32
|
+
* Mapping between the schema and ref. Exemple:
|
|
33
|
+
* refs_with_info = {
|
|
34
|
+
* "#/counter/1": "fully/realized/path/to/schema.json"
|
|
35
|
+
* "#/counter/2": "mylocalschema.json"
|
|
36
|
+
* }
|
|
37
|
+
*/
|
|
7
38
|
this.refs_with_info = {}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @prop {string}
|
|
42
|
+
* String to eewrite external ref with.
|
|
43
|
+
*/
|
|
8
44
|
this.refs_prefix = '#/counter/'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @prop {int}
|
|
48
|
+
* Counter of ref
|
|
49
|
+
*/
|
|
9
50
|
this.refs_counter = 1
|
|
10
51
|
|
|
11
52
|
this._subSchema1 = {
|
|
@@ -80,23 +121,54 @@ export class SchemaLoader {
|
|
|
80
121
|
}
|
|
81
122
|
}
|
|
82
123
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Fully loads and expands JSON schema for a provided schema object and URL.
|
|
126
|
+
*
|
|
127
|
+
* The callback receives a expanded JSON Schema object with references
|
|
128
|
+
* replaced with loaded schemas.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} schema - A JSON Schema.
|
|
131
|
+
* @param {string} fetchUrl - Base path from which to store the definitions.
|
|
132
|
+
* Typically the URI of the schema.
|
|
133
|
+
* @param {*} location - The base URL from which to load relative paths.
|
|
134
|
+
* @returns {object} A JSON Schema with references expanded.
|
|
135
|
+
*/
|
|
136
|
+
async load (schema, fetchUrl, location) {
|
|
137
|
+
this.schema = schema
|
|
138
|
+
await this._asyncloadExternalRefs(schema, fetchUrl, this._getFileBase(location), true)
|
|
139
|
+
return this.expandRefs(schema)
|
|
88
140
|
}
|
|
89
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Recursively expands loaded references in a provided schema.
|
|
144
|
+
*
|
|
145
|
+
* @param {object} schema - A JSON Schema with references already loaded.
|
|
146
|
+
* @param {boolean} recurseAllOf - Set true to recurse allOf properties.
|
|
147
|
+
* @returns {object} A JSON Schema with references expanded.
|
|
148
|
+
*/
|
|
90
149
|
expandRefs (schema, recurseAllOf) {
|
|
91
150
|
const _schema = extend({}, schema)
|
|
92
|
-
if (!_schema.$ref) return _schema
|
|
93
151
|
|
|
94
|
-
|
|
152
|
+
if (!_schema.$ref) return _schema
|
|
153
|
+
// This split the ref to get the Json point if it exists
|
|
154
|
+
// exemple #/counter/1#/definition/address +
|
|
155
|
+
// [1] -> /counter/1
|
|
156
|
+
// [2] -> /definition/address
|
|
157
|
+
const refWithPointerSplit = _schema.$ref.split('#')
|
|
158
|
+
// If local ref
|
|
159
|
+
if (refWithPointerSplit.length === 2 && !this.refs_with_info[_schema.$ref]) {
|
|
160
|
+
const sub = this.expandRecursivePointer(this.schema, refWithPointerSplit[1])
|
|
161
|
+
return this.extendSchemas(_schema, this.expandSchema(sub))
|
|
162
|
+
}
|
|
163
|
+
const refObj = (refWithPointerSplit.length > 2)
|
|
164
|
+
? this.refs_with_info['#' + refWithPointerSplit[1]]
|
|
165
|
+
: this.refs_with_info[_schema.$ref]
|
|
95
166
|
delete _schema.$ref
|
|
96
167
|
const fetchUrl = refObj.$ref.startsWith('#')
|
|
97
168
|
? refObj.fetchUrl
|
|
98
169
|
: ''
|
|
99
170
|
const ref = this._getRef(fetchUrl, refObj)
|
|
171
|
+
|
|
100
172
|
if (!this.refs[ref]) { /* if reference not found */
|
|
101
173
|
// eslint-disable-next-line no-console
|
|
102
174
|
console.warn(`reference:'${ref}' not found!`)
|
|
@@ -106,11 +178,41 @@ export class SchemaLoader {
|
|
|
106
178
|
allOf[key] = this.expandRefs(allOf[key], true)
|
|
107
179
|
})
|
|
108
180
|
}
|
|
181
|
+
if (refWithPointerSplit.length > 2) {
|
|
182
|
+
return this.extendSchemas(_schema, this.expandSchema(this.expandRecursivePointer(this.refs[ref], refWithPointerSplit[2])))
|
|
183
|
+
} else {
|
|
184
|
+
return this.extendSchemas(_schema, this.expandSchema(this.refs[ref]))
|
|
185
|
+
}
|
|
186
|
+
}
|
|
109
187
|
|
|
110
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Returns a subschema based on a JSON Pointer path.
|
|
190
|
+
* @param {object} schema - Schema too into
|
|
191
|
+
* @param {string} pointer - path to look for
|
|
192
|
+
* @param {object} original_schema - the Original schema
|
|
193
|
+
* @returns the subschema pointed to by the path
|
|
194
|
+
*/
|
|
195
|
+
expandRecursivePointer (schema, pointer) {
|
|
196
|
+
let subschema = schema
|
|
197
|
+
pointer.split('/').slice(1).forEach(i => {
|
|
198
|
+
if (subschema[i]) {
|
|
199
|
+
subschema = subschema[i]
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
// If the result is a pointer, let's go for another turn
|
|
203
|
+
if (subschema.$refs && subschema.$refs.startsWith('#')) {
|
|
204
|
+
return this.expandRecursivePointer(schema, subschema.$refs)
|
|
205
|
+
}
|
|
206
|
+
return subschema
|
|
111
207
|
}
|
|
112
208
|
|
|
113
|
-
|
|
209
|
+
/**
|
|
210
|
+
* Expands a JSON schema and its references.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} schema - A JSON Schema with references already loaded.
|
|
213
|
+
* @returns {object} A JSON Schema with references expanded.
|
|
214
|
+
*/
|
|
215
|
+
expandSchema (schema) {
|
|
114
216
|
Object.entries(this._subSchema1).forEach(([key, func]) => {
|
|
115
217
|
if (schema[key]) {
|
|
116
218
|
func.call(this, schema)
|
|
@@ -136,34 +238,59 @@ export class SchemaLoader {
|
|
|
136
238
|
|
|
137
239
|
_expandSubSchema (subschema) {
|
|
138
240
|
/* Array of types */
|
|
139
|
-
if (Array.isArray(subschema)) return subschema.map(m => typeof
|
|
241
|
+
if (Array.isArray(subschema)) return subschema.map(m => typeof m === 'object' ? this.expandSchema(m) : m)
|
|
140
242
|
|
|
141
243
|
/* Schema */
|
|
142
244
|
return this.expandSchema(subschema)
|
|
143
245
|
}
|
|
144
246
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
247
|
+
/**
|
|
248
|
+
* Rewrite the passed schema's JSON pointers to prepend with the current reference's path, so that it will be converted to a reference "counter".
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* In file "../otherreferencedfile.json", referenced from "schema.json":
|
|
252
|
+
*
|
|
253
|
+
* "$ref": "#/definitions/myschema" => "$ref": "../path/to/my/referenced/schemafile.json#/definitions/myschema"
|
|
254
|
+
* ...which will then be parsed later in loadExternalReferences() to "$ref": "#/counter/1#/definitions/myschema"
|
|
255
|
+
*
|
|
256
|
+
* @param {object} schema - A JSON Schema with the definitions key present.
|
|
257
|
+
* @param {string} path - Base path from which to store the definitions in refs. (exemple ../path/to/my/referenced/schemafile.json)
|
|
258
|
+
* Typically the URI of the schema.
|
|
259
|
+
*/
|
|
260
|
+
_manageRecursivePointer (schema, path) {
|
|
261
|
+
Object.keys(schema).forEach(i => {
|
|
262
|
+
if (schema[i].$ref && schema[i].$ref.indexOf('#') === 0) {
|
|
263
|
+
schema[i].$ref = path + schema[i].$ref
|
|
264
|
+
}
|
|
265
|
+
})
|
|
154
266
|
}
|
|
155
267
|
|
|
156
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Recursively parse a (sub)schema to populate loader reference info.
|
|
270
|
+
*
|
|
271
|
+
* @param {object} schema - A JSON Schema
|
|
272
|
+
* @param {string} fetchUrl - Base path from which to store the definitions.
|
|
273
|
+
* @param {boolean} firstIteration - Is it the first time we load this function? Help making difference between external ref vs internal pointer
|
|
274
|
+
* @returns {array} Refs in the format of uri => true if external.
|
|
275
|
+
*/
|
|
276
|
+
_getExternalRefs (schema, fetchUrl, firstIteration = false) {
|
|
277
|
+
if (!firstIteration) this._manageRecursivePointer(schema, fetchUrl)
|
|
157
278
|
const refs = {}
|
|
158
279
|
const mergeRefs = newrefs => Object.keys(newrefs).forEach(i => { refs[i] = true })
|
|
159
|
-
|
|
160
|
-
|
|
280
|
+
if (schema.$ref && typeof schema.$ref !== 'object' && !(schema.$ref.indexOf('#') === 0 && firstIteration)) {
|
|
281
|
+
let refBase = schema.$ref
|
|
282
|
+
let pointer = ''
|
|
283
|
+
// Strip any JSON pointers found for external refs.
|
|
284
|
+
if (refBase.indexOf('#') > 0) refBase = refBase.substr(0, refBase.indexOf('#'))
|
|
285
|
+
if (refBase !== schema.$ref) pointer = schema.$ref.substr(schema.$ref.indexOf('#'))
|
|
286
|
+
// We use a fragment idenfier to track json pointer in top of our pointer
|
|
161
287
|
const refCounter = this.refs_prefix + this.refs_counter++
|
|
288
|
+
const refPointer = refCounter + pointer
|
|
162
289
|
if (schema.$ref.substr(0, 1) !== '#' && !this.refs[schema.$ref]) {
|
|
163
|
-
refs[
|
|
290
|
+
refs[refBase] = true
|
|
164
291
|
}
|
|
165
|
-
this.refs_with_info[refCounter] = { fetchUrl, $ref:
|
|
166
|
-
schema.$ref =
|
|
292
|
+
this.refs_with_info[refCounter] = { fetchUrl, $ref: refBase }
|
|
293
|
+
schema.$ref = refPointer
|
|
167
294
|
}
|
|
168
295
|
|
|
169
296
|
Object.values(schema).forEach(value => {
|
|
@@ -171,11 +298,14 @@ export class SchemaLoader {
|
|
|
171
298
|
if (Array.isArray(value)) {
|
|
172
299
|
Object.values(value).forEach(e => {
|
|
173
300
|
if (e && typeof e === 'object') {
|
|
174
|
-
mergeRefs(this._getExternalRefs(e, fetchUrl))
|
|
301
|
+
mergeRefs(this._getExternalRefs(e, fetchUrl, firstIteration))
|
|
175
302
|
}
|
|
176
303
|
})
|
|
177
304
|
} else {
|
|
178
|
-
|
|
305
|
+
// Merge Ref if it's not a Pointer
|
|
306
|
+
if (!value.$ref || !(typeof value.$ref === 'string' && value.$ref.startsWith('#'))) {
|
|
307
|
+
mergeRefs(this._getExternalRefs(value, fetchUrl, firstIteration))
|
|
308
|
+
}
|
|
179
309
|
}
|
|
180
310
|
})
|
|
181
311
|
|
|
@@ -184,7 +314,6 @@ export class SchemaLoader {
|
|
|
184
314
|
} else if (schema.$id && typeof schema.$id === 'string' && schema.$id.substr(0, 4) === 'urn:') {
|
|
185
315
|
this.refs[schema.$id] = schema
|
|
186
316
|
}
|
|
187
|
-
|
|
188
317
|
return refs
|
|
189
318
|
}
|
|
190
319
|
|
|
@@ -225,17 +354,31 @@ export class SchemaLoader {
|
|
|
225
354
|
return uri.substr(0, 4) === 'urn:'
|
|
226
355
|
}
|
|
227
356
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
357
|
+
/**
|
|
358
|
+
* Loads external references via AJAX.
|
|
359
|
+
*
|
|
360
|
+
* Will fail if this.options.ajax is not set to true.
|
|
361
|
+
*
|
|
362
|
+
* @param {object} schema - JSON Schema with external references.
|
|
363
|
+
* @param {string} fetchUrl - Base path from which to store the definitions.
|
|
364
|
+
* Typically the URI of the schema.
|
|
365
|
+
* @param {string} fileBase - The base URL from which to load relative paths.
|
|
366
|
+
* Typically the URI of the schema minus filename, with trailing slash.
|
|
367
|
+
* @param {boolean} firstIteration - Is it the first time we load this function? Help making difference between external ref vs internal pointer
|
|
368
|
+
*
|
|
369
|
+
* @return {boolean}
|
|
370
|
+
* @throws Error
|
|
371
|
+
*/
|
|
372
|
+
async _asyncloadExternalRefs (schema, fetchUrl, fileBase, firstIteration = false) {
|
|
373
|
+
const refs = this._getExternalRefs(schema, fetchUrl, firstIteration)
|
|
374
|
+
let waiting = 0
|
|
375
|
+
// Loop into all schema references
|
|
376
|
+
for (const uri of Object.keys(refs)) {
|
|
377
|
+
if (typeof uri === 'undefined') continue
|
|
378
|
+
if (this.refs[uri]) continue
|
|
235
379
|
if (this._isUniformResourceName(uri)) {
|
|
236
380
|
this.refs[uri] = 'loading'
|
|
237
381
|
waiting++
|
|
238
|
-
|
|
239
382
|
const urnResolver = this.options.urn_resolver
|
|
240
383
|
let urn = uri
|
|
241
384
|
if (typeof urnResolver !== 'function') {
|
|
@@ -247,95 +390,75 @@ export class SchemaLoader {
|
|
|
247
390
|
if (urn.indexOf('#') > 0) urn = urn.substr(0, urn.indexOf('#'))
|
|
248
391
|
let response
|
|
249
392
|
try {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
callback()
|
|
267
|
-
}
|
|
268
|
-
}, uri, '/')
|
|
269
|
-
})
|
|
393
|
+
let externalSchema
|
|
394
|
+
response = await urnResolver(urn)
|
|
395
|
+
try {
|
|
396
|
+
externalSchema = JSON.parse(response)
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// eslint-disable-next-line no-console
|
|
399
|
+
console.log(e)
|
|
400
|
+
throw new Error(`Failed to parse external ref ${urn}`)
|
|
401
|
+
}
|
|
402
|
+
if (!(typeof externalSchema === 'boolean' || typeof externalSchema === 'object') || externalSchema === null || Array.isArray(externalSchema)) {
|
|
403
|
+
throw new Error(`External ref does not contain a valid schema - ${urn}`)
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.refs[uri] = externalSchema
|
|
407
|
+
|
|
408
|
+
await this._asyncloadExternalRefs(externalSchema, uri, fileBase)
|
|
270
409
|
} catch (e) {
|
|
271
410
|
// eslint-disable-next-line no-console
|
|
272
411
|
console.log(e)
|
|
273
412
|
throw new Error(`Failed to parse external ref ${urn}`)
|
|
274
413
|
}
|
|
275
414
|
|
|
276
|
-
if (typeof response
|
|
415
|
+
if (typeof response === 'boolean') {
|
|
277
416
|
throw new Error(`External ref does not contain a valid schema - ${urn}`)
|
|
278
|
-
} else if (response !== true) {
|
|
279
|
-
throw new Error(`External ref did not resolve - ${urn}`)
|
|
280
417
|
}
|
|
281
|
-
|
|
282
|
-
return
|
|
418
|
+
continue
|
|
283
419
|
}
|
|
284
|
-
|
|
285
420
|
if (!this.options.ajax) throw new Error(`Must set ajax option to true to load external ref ${uri}`)
|
|
286
|
-
this.refs[uri] = 'loading'
|
|
287
421
|
waiting++
|
|
288
|
-
let url = this._joinUrl(uri, fileBase)
|
|
289
|
-
|
|
290
|
-
const r = new XMLHttpRequest()
|
|
291
|
-
r.overrideMimeType('application/json')
|
|
292
|
-
r.open('GET', url, true)
|
|
293
|
-
if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
|
|
294
|
-
r.onreadystatechange = () => {
|
|
295
|
-
if (r.readyState !== 4) return
|
|
296
|
-
/* Request succeeded */
|
|
297
|
-
if (r.status === 200) {
|
|
298
|
-
let schema
|
|
299
|
-
try {
|
|
300
|
-
schema = JSON.parse(r.responseText)
|
|
301
|
-
} catch (e) {
|
|
302
|
-
// eslint-disable-next-line no-console
|
|
303
|
-
console.log(e)
|
|
304
|
-
throw new Error(`Failed to parse external ref ${url}`)
|
|
305
|
-
}
|
|
306
|
-
if (!(typeof schema === 'boolean' || typeof schema === 'object') || schema === null || Array.isArray(schema)) {
|
|
307
|
-
throw new Error(`External ref does not contain a valid schema - ${url}`)
|
|
308
|
-
}
|
|
309
422
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
waiting--
|
|
322
|
-
if (done && !waiting) {
|
|
323
|
-
callback()
|
|
324
|
-
}
|
|
325
|
-
}, url, fileBase)
|
|
326
|
-
} else {
|
|
327
|
-
/* Request failed */
|
|
328
|
-
// eslint-disable-next-line no-console
|
|
329
|
-
console.log(r)
|
|
330
|
-
throw new Error(`Failed to fetch ref via ajax - ${uri}`)
|
|
423
|
+
let url = this._joinUrl(uri, fileBase)
|
|
424
|
+
const response = await new Promise(resolve => {
|
|
425
|
+
const r = new XMLHttpRequest()
|
|
426
|
+
if (this.options.ajaxCredentials) r.withCredentials = this.options.ajaxCredentials
|
|
427
|
+
r.overrideMimeType('application/json')
|
|
428
|
+
r.open('GET', url, true)
|
|
429
|
+
r.onload = () => {
|
|
430
|
+
resolve(r)
|
|
431
|
+
}
|
|
432
|
+
r.onerror = (e) => {
|
|
433
|
+
resolve(undefined)
|
|
331
434
|
}
|
|
435
|
+
r.send()
|
|
436
|
+
})
|
|
437
|
+
if (typeof response === 'undefined') throw new Error(`Failed to fetch ref via ajax - ${uri}`)
|
|
438
|
+
let externalSchema
|
|
439
|
+
try {
|
|
440
|
+
externalSchema = JSON.parse(response.responseText)
|
|
441
|
+
} catch (e) {
|
|
442
|
+
// eslint-disable-next-line no-console
|
|
443
|
+
console.log(e)
|
|
444
|
+
throw new Error(`Failed to parse external ref ${url}`)
|
|
332
445
|
}
|
|
333
|
-
r.send()
|
|
334
|
-
})
|
|
335
446
|
|
|
336
|
-
|
|
447
|
+
if (!(typeof externalSchema === 'boolean' || typeof externalSchema === 'object') || externalSchema === null || Array.isArray(externalSchema)) {
|
|
448
|
+
throw new Error(`External ref does not contain a valid schema - ${url}`)
|
|
449
|
+
}
|
|
450
|
+
this.refs[uri] = externalSchema
|
|
451
|
+
const newfileBase = this._getFileBaseFromFileLocation(url)
|
|
452
|
+
|
|
453
|
+
// Add leading slash.
|
|
454
|
+
if (url !== uri) {
|
|
455
|
+
const pathItems = url.split('/')
|
|
456
|
+
url = (uri.substr(0, 1) === '/' ? '/' : '') + pathItems.pop()
|
|
457
|
+
}
|
|
458
|
+
await this._asyncloadExternalRefs(externalSchema, url, newfileBase)
|
|
459
|
+
}
|
|
337
460
|
if (!waiting) {
|
|
338
|
-
|
|
461
|
+
return true
|
|
339
462
|
}
|
|
340
463
|
}
|
|
341
464
|
|
package/src/style.css
CHANGED
package/src/style.css.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
|
-
export default {".je-float-right-linkholder":"float:right;margin-left:10px",".je-modal":"background-color:white;border:1px%20solid%20black;box-shadow:3px%203px%20black;position:absolute;z-index:10",".je-infobutton-icon":"font-size:16px;font-weight:bold;padding:0.25rem;position:relative;display:inline-block",".je-infobutton-tooltip":"font-size:12px;font-weight:normal;font-family:sans-serif;visibility:hidden;background-color:rgba(50%2C%2050%2C%2050%2C%200.75);margin:0%200.25rem;color:%23fafafa;padding:0.5rem%201rem;border-radius:0.25rem;width:20rem;position:absolute",".je-header":"display:inline-block",".je-upload-preview img":"float:left;margin:0%200.5rem%200.5rem%200;max-width:100%25;max-height:5rem",".je-checkbox":"display:inline-block;width:auto",".je-checkbox-control--compact":"display:inline-block;margin-right:1rem",".je-radio":"display:inline-block;width:auto",".je-radio-control--compact":"display:inline-block;margin-right:1rem",".je-switcher":"background-color:transparent;display:inline-block;font-style:italic;font-weight:normal;height:auto;width:auto;margin-bottom:0;margin-left:5px;padding:0%200%200%203px",".je-textarea":"width:100%25;height:300px;box-sizing:border-box",".je-range-control":"text-align:center",".je-indented-panel":"padding-left:10px;margin-left:10px;border-left:1px%20solid%20%23ccc",".je-indented-panel--top":"padding-left:10px;margin-left:10px",".je-tabholder":"float:left;width:130px",".je-tabholder .content":"margin-left:120px",".je-tabholder--top":"margin-left:10px",".je-tabholder--clear":"clear:both",".je-tab":"border:1px%20solid%20%23ccc;border-width:1px%200%201px%201px;text-align:center;line-height:30px;border-radius:5px;border-bottom-right-radius:0;border-top-right-radius:0;font-weight:bold;cursor:pointer",".je-tab--top":"float:left;border:1px%20solid%20%23ccc;border-width:1px%201px%200px%201px;text-align:center;line-height:30px;border-radius:5px;padding-left:5px;padding-right:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;font-weight:bold;cursor:pointer",".je-block-link":"display:block",".je-media":"width:100%25"}
|
|
2
|
+
export default {".je-float-right-linkholder":"float:right;margin-left:10px",".je-modal":"background-color:white;border:1px%20solid%20black;box-shadow:3px%203px%20black;position:absolute;z-index:10",".je-infobutton-icon":"font-size:16px;font-weight:bold;padding:0.25rem;position:relative;display:inline-block",".je-infobutton-tooltip":"font-size:12px;font-weight:normal;font-family:sans-serif;visibility:hidden;background-color:rgba(50%2C%2050%2C%2050%2C%200.75);margin:0%200.25rem;color:%23fafafa;padding:0.5rem%201rem;border-radius:0.25rem;width:20rem;position:absolute",".je-not-loaded":"pointer-events:none",".je-header":"display:inline-block",".je-upload-preview img":"float:left;margin:0%200.5rem%200.5rem%200;max-width:100%25;max-height:5rem",".je-checkbox":"display:inline-block;width:auto",".je-checkbox-control--compact":"display:inline-block;margin-right:1rem",".je-radio":"display:inline-block;width:auto",".je-radio-control--compact":"display:inline-block;margin-right:1rem",".je-switcher":"background-color:transparent;display:inline-block;font-style:italic;font-weight:normal;height:auto;width:auto;margin-bottom:0;margin-left:5px;padding:0%200%200%203px",".je-textarea":"width:100%25;height:300px;box-sizing:border-box",".je-range-control":"text-align:center",".je-indented-panel":"padding-left:10px;margin-left:10px;border-left:1px%20solid%20%23ccc",".je-indented-panel--top":"padding-left:10px;margin-left:10px",".je-tabholder":"float:left;width:130px",".je-tabholder .content":"margin-left:120px",".je-tabholder--top":"margin-left:10px",".je-tabholder--clear":"clear:both",".je-tab":"border:1px%20solid%20%23ccc;border-width:1px%200%201px%201px;text-align:center;line-height:30px;border-radius:5px;border-bottom-right-radius:0;border-top-right-radius:0;font-weight:bold;cursor:pointer",".je-tab--top":"float:left;border:1px%20solid%20%23ccc;border-width:1px%201px%200px%201px;text-align:center;line-height:30px;border-radius:5px;padding-left:5px;padding-right:5px;border-bottom-right-radius:0;border-bottom-left-radius:0;font-weight:bold;cursor:pointer",".je-block-link":"display:block",".je-media":"width:100%25"}
|
|
3
3
|
/* eslint-enable */
|
package/src/theme.js
CHANGED
|
@@ -299,8 +299,6 @@ export class AbstractTheme {
|
|
|
299
299
|
|
|
300
300
|
getRangeOutput (input, startvalue) {
|
|
301
301
|
const output = document.createElement('output')
|
|
302
|
-
output.value = startvalue || 0
|
|
303
|
-
|
|
304
302
|
const updateOutput = e => { output.value = e.currentTarget.value }
|
|
305
303
|
input.addEventListener('change', updateOutput, false)
|
|
306
304
|
input.addEventListener('input', updateOutput, false)
|
|
@@ -325,10 +323,13 @@ export class AbstractTheme {
|
|
|
325
323
|
|
|
326
324
|
}
|
|
327
325
|
|
|
328
|
-
getFormControl (label, input, description, infoText) {
|
|
326
|
+
getFormControl (label, input, description, infoText, formName) {
|
|
329
327
|
const el = document.createElement('div')
|
|
330
328
|
el.classList.add('form-control')
|
|
331
|
-
if (label)
|
|
329
|
+
if (label) {
|
|
330
|
+
el.appendChild(label)
|
|
331
|
+
if (formName) label.setAttribute('for', formName)
|
|
332
|
+
}
|
|
332
333
|
if ((input.type === 'checkbox' || input.type === 'radio') && label) {
|
|
333
334
|
input.style.width = 'auto'
|
|
334
335
|
label.insertBefore(input, label.firstChild)
|