coloring fields, urls, and a refactoring

This commit is contained in:
jos 2016-07-12 21:07:50 +02:00
parent 89657a539f
commit f011e3f107
4 changed files with 185 additions and 48 deletions

View File

@ -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;
}

View File

@ -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
}
}

View File

@ -10,12 +10,13 @@ export default function escapeHTML (text, escapeUnicode = false) {
}
else {
var htmlEscaped = String(text)
.replace(/&/g, '&') // must be replaced first!
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/ /g, ' &nbsp;') // replace double space with an nbsp and space
.replace(/^ /, '&nbsp;') // space at start
.replace(/ $/, '&nbsp;'); // space at end
// TODO: cleanup redundant character replacements
// .replace(/&/g, '&amp;') // must be replaced first!
// .replace(/</g, '&lt;')
// .replace(/>/g, '&gt;')
.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)

42
src/utils/valueType.js Normal file
View File

@ -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)
}