coloring fields, urls, and a refactoring
This commit is contained in:
parent
89657a539f
commit
f011e3f107
|
@ -41,15 +41,47 @@ ul.jsoneditor-list {
|
|||
}
|
||||
|
||||
.jsoneditor-separator {
|
||||
color: gray;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.jsoneditor-readonly {
|
||||
color: gray;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.jsoneditor-readonly:focus,
|
||||
.jsoneditor-readonly:hover {
|
||||
border-color: transparent;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
||||
.jsoneditor-value.jsoneditor-string {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.jsoneditor-value.jsoneditor-object,
|
||||
.jsoneditor-value.jsoneditor-array {
|
||||
min-width: 16px;
|
||||
color: #808080;
|
||||
}
|
||||
|
||||
.jsoneditor-value.jsoneditor-number {
|
||||
color: #ee422e;
|
||||
}
|
||||
|
||||
.jsoneditor-value.jsoneditor-boolean {
|
||||
color: #ff8c00;
|
||||
}
|
||||
|
||||
.jsoneditor-value.jsoneditor-null {
|
||||
color: #004ED0;
|
||||
}
|
||||
|
||||
.jsoneditor-value.jsoneditor-invalid {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
div.jsoneditor-value.jsoneditor-url {
|
||||
color: green;
|
||||
text-decoration: underline;
|
||||
}
|
142
src/JSONNode.js
142
src/JSONNode.js
|
@ -4,83 +4,131 @@ import escapeHTML from './utils/escapeHTML'
|
|||
import unescapeHTML from './utils/unescapeHTML'
|
||||
import getInnerText from './utils/getInnerText'
|
||||
import stringConvert from './utils/stringConvert'
|
||||
import valueType, {isUrl} from './utils/valueType'
|
||||
|
||||
export default class JSONNode extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.onBlurField = this.onBlurField.bind(this)
|
||||
this.onBlurValue = this.onBlurValue.bind(this)
|
||||
this.onChangeField = this.onChangeField.bind(this)
|
||||
this.onChangeValue = this.onChangeValue.bind(this)
|
||||
this.onClickUrl = this.onClickUrl.bind(this)
|
||||
this.onKeyDownUrl = this.onKeyDownUrl.bind(this)
|
||||
}
|
||||
|
||||
render (props) {
|
||||
if (Array.isArray(props.value)) {
|
||||
return this.renderArray(props)
|
||||
return this.renderJSONArray(props)
|
||||
}
|
||||
else if (isObject(props.value)) {
|
||||
return this.renderObject(props)
|
||||
return this.renderJSONObject(props)
|
||||
}
|
||||
else {
|
||||
return this.renderValue(props)
|
||||
return this.renderJSONValue(props)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: reorganize the render methods, they are too large now
|
||||
|
||||
renderObject ({parent, field, value, onChangeValue, onChangeField}) {
|
||||
renderJSONObject ({parent, field, value, onChangeValue, onChangeField}) {
|
||||
//console.log('JSONObject', field,value)
|
||||
const hasParent = parent !== null
|
||||
|
||||
const childs = Object.keys(value).map(f => {
|
||||
return h(JSONNode, {
|
||||
parent: this,
|
||||
field: f,
|
||||
value: value[f],
|
||||
onChangeValue,
|
||||
onChangeField
|
||||
})
|
||||
})
|
||||
|
||||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node jsoneditor-object'}, [
|
||||
h('div', {class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'), contentEditable: hasParent, onBlur: this.onBlurField}, hasParent ? escapeHTML(field) : 'object'),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {class: 'jsoneditor-readonly', contentEditable: false}, '{' + Object.keys(value).length + '}')
|
||||
this.renderField(field, parent, this.onChangeField),
|
||||
this.renderSeparator(),
|
||||
this.renderReadonly('{' + Object.keys(value).length + '}')
|
||||
]),
|
||||
h('ul',
|
||||
{class: 'jsoneditor-list'},
|
||||
Object
|
||||
.keys(value)
|
||||
.map(f => h(JSONNode, {parent: this, field: f, value: value[f], onChangeValue, onChangeField})))
|
||||
h('ul', {class: 'jsoneditor-list'}, childs)
|
||||
])
|
||||
}
|
||||
|
||||
renderArray ({parent, field, value, onChangeValue, onChangeField}) {
|
||||
const hasParent = parent !== null
|
||||
renderJSONArray ({parent, field, value, onChangeValue, onChangeField}) {
|
||||
const childs = value.map((v, i) => {
|
||||
return h(JSONNode, {
|
||||
parent: this,
|
||||
index: i,
|
||||
value: v,
|
||||
onChangeValue,
|
||||
onChangeField
|
||||
})
|
||||
})
|
||||
|
||||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node jsoneditor-array'}, [
|
||||
h('div', {class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'), contentEditable: hasParent, onBlur: this.onBlurField}, hasParent ? escapeHTML(field) : 'array'),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {class: 'jsoneditor-readonly', contentEditable: false}, '{' + value.length + '}')
|
||||
this.renderField(field, parent, this.onChangeField),
|
||||
this.renderSeparator(),
|
||||
this.renderReadonly('{' + value.length + '}')
|
||||
]),
|
||||
h('ul',
|
||||
{class: 'jsoneditor-list'},
|
||||
value
|
||||
.map((v, i) => h(JSONNode, {parent: this, index: i, value: v, onChangeValue, onChangeField})))
|
||||
h('ul', {class: 'jsoneditor-list'}, childs)
|
||||
])
|
||||
}
|
||||
|
||||
renderValue ({parent, index, field, value}) {
|
||||
const hasParent = parent !== null
|
||||
renderJSONValue ({parent, index, field, value}) {
|
||||
//console.log('JSONValue', field, value)
|
||||
|
||||
return h('li', {}, [
|
||||
h('div', {class: 'jsoneditor-node'}, [
|
||||
index !== undefined
|
||||
? h('div', {class: 'jsoneditor-readonly', contentEditable: false}, index)
|
||||
: h('div', {class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'), contentEditable: hasParent, onBlur: this.onBlurField}, hasParent ? escapeHTML(field) : 'value'),
|
||||
h('div', {class: 'jsoneditor-separator'}, ':'),
|
||||
h('div', {class: 'jsoneditor-value', contentEditable: true, onBlur: this.onBlurValue}, escapeHTML(value))
|
||||
? this.renderReadonly(index)
|
||||
: this.renderField(field, parent, this.onChangeField),
|
||||
this.renderSeparator(),
|
||||
this.renderValue(value, this.onChangeValue)
|
||||
])
|
||||
])
|
||||
}
|
||||
|
||||
renderReadonly (text) {
|
||||
return h('div', {class: 'jsoneditor-readonly', contentEditable: false}, text)
|
||||
}
|
||||
|
||||
renderField (field, parent, onChangeField) {
|
||||
const hasParent = parent !== null
|
||||
const content = hasParent ? escapeHTML(field) : 'value'
|
||||
|
||||
return h('div', {
|
||||
class: 'jsoneditor-field' + (hasParent ? '' : ' jsoneditor-readonly'),
|
||||
contentEditable: hasParent,
|
||||
spellCheck: "false", // FIXME: turning off spellcheck doesn't work
|
||||
onBlur: onChangeField
|
||||
}, content)
|
||||
}
|
||||
|
||||
renderSeparator() {
|
||||
return h('div', {class: 'jsoneditor-separator'}, ':')
|
||||
}
|
||||
|
||||
renderValue (value, onChangeValue) {
|
||||
const type = valueType (value)
|
||||
const _isUrl = isUrl(value)
|
||||
const valueClass = 'jsoneditor-value jsoneditor-' + type + (_isUrl ? ' jsoneditor-url' : '')
|
||||
|
||||
return h('div', {
|
||||
class: valueClass,
|
||||
contentEditable: true,
|
||||
spellCheck: "false", // FIXME: turning off spellcheck doesn't work
|
||||
onInput: onChangeValue,
|
||||
onClick: _isUrl ? this.onClickUrl : null,
|
||||
onKeyDown: _isUrl ? this.onKeyDownUrl: null,
|
||||
title: _isUrl ? 'Ctrl+Click or ctrl+Enter to open url' : null
|
||||
}, escapeHTML(value))
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return nextProps.field !== this.props.field || nextProps.value !== this.props.value
|
||||
}
|
||||
|
||||
onBlurField (event) {
|
||||
onChangeField (event) {
|
||||
const path = this.props.parent.getPath()
|
||||
const newField = unescapeHTML(getInnerText(event.target))
|
||||
const oldField = this.props.field
|
||||
|
@ -89,7 +137,7 @@ export default class JSONNode extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onBlurValue (event) {
|
||||
onChangeValue (event) {
|
||||
const path = this.getPath()
|
||||
const value = stringConvert(unescapeHTML(getInnerText(event.target)))
|
||||
if (value !== this.props.value) {
|
||||
|
@ -97,6 +145,28 @@ export default class JSONNode extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onClickUrl (event) {
|
||||
if (event.ctrlKey && event.button === 0) { // Ctrl+Left click
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
this.openUrl()
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDownUrl (event) {
|
||||
if (event.ctrlKey && event.which === 13) { // Ctrl+Enter
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
this.openUrl()
|
||||
}
|
||||
}
|
||||
|
||||
openUrl () {
|
||||
window.open(this.props.value, '_blank')
|
||||
}
|
||||
|
||||
getPath () {
|
||||
const path = []
|
||||
|
||||
|
@ -111,12 +181,4 @@ export default class JSONNode extends Component {
|
|||
|
||||
return path
|
||||
}
|
||||
|
||||
getRoot () {
|
||||
let node = this
|
||||
while (node && node.props.parent) {
|
||||
node = node.props.parent
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,12 +10,13 @@ export default function escapeHTML (text, escapeUnicode = false) {
|
|||
}
|
||||
else {
|
||||
var htmlEscaped = String(text)
|
||||
.replace(/&/g, '&') // must be replaced first!
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/ /g, ' ') // replace double space with an nbsp and space
|
||||
.replace(/^ /, ' ') // space at start
|
||||
.replace(/ $/, ' '); // space at end
|
||||
// TODO: cleanup redundant character replacements
|
||||
// .replace(/&/g, '&') // must be replaced first!
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/>/g, '>')
|
||||
.replace(/ /g, ' \u00a0') // replace double space with an nbsp and space
|
||||
.replace(/^ /, '\u00a0') // space at start
|
||||
.replace(/ $/, '\u00a0') // space at end
|
||||
|
||||
var json = JSON.stringify(htmlEscaped)
|
||||
var html = json.substring(1, json.length - 1)
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
/**
|
||||
* Get the type of a value
|
||||
* @param {*} value
|
||||
* @return {String} type
|
||||
*/
|
||||
export default function valueType(value) {
|
||||
if (value === null) {
|
||||
return 'null'
|
||||
}
|
||||
if (value === undefined) {
|
||||
return 'undefined'
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
return 'number'
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
return 'string'
|
||||
}
|
||||
if (typeof value === 'boolean') {
|
||||
return 'boolean'
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return 'regexp'
|
||||
}
|
||||
if (exports.isArray(value)) {
|
||||
return 'array'
|
||||
}
|
||||
|
||||
return 'object'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test whether a text contains a url (matches when a string starts
|
||||
* with 'http://*' or 'https://*' and has no whitespace characters)
|
||||
* @param {String} text
|
||||
*/
|
||||
var isUrlRegex = /^https?:\/\/\S+$/
|
||||
export function isUrl (text) {
|
||||
return (typeof text === 'string') && isUrlRegex.test(text)
|
||||
}
|
Loading…
Reference in New Issue