diff --git a/examples/08_custom_ace.html b/examples/08_custom_ace.html new file mode 100644 index 0000000..39813fa --- /dev/null +++ b/examples/08_custom_ace.html @@ -0,0 +1,61 @@ + + + + JSONEditor | Custom Ace Editor + + + + + + + + + + + + + + + +

+ In this example, the we use the minimalist version of jsoneditor and load + and configure Ace editor our selves. +

+ +
+ + + + diff --git a/gulpfile.js b/gulpfile.js index 0e2e1f2..f38e373 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -81,8 +81,8 @@ var compilerMinimalist = webpack({ }, plugins: [ bannerPlugin, - new webpack.NormalModuleReplacementPlugin(new RegExp('^brace$'), EMPTY), - new webpack.NormalModuleReplacementPlugin(new RegExp('^ajv'), EMPTY), + new webpack.NormalModuleReplacementPlugin(new RegExp('^./assets/ace$'), EMPTY), + new webpack.NormalModuleReplacementPlugin(new RegExp('^ajv$'), EMPTY), new webpack.optimize.UglifyJsPlugin() ], module: { @@ -165,4 +165,4 @@ gulp.task(WATCH, ['bundle'], function() { }) // The default task (called when you run `gulp`) -gulp.task('default', [ 'bundle', 'bundle-minimalist' ]) +gulp.task('default', [ 'bundle', 'bundle-minimalist', 'copy' ]) diff --git a/package.json b/package.json index eb20cfc..15d81e7 100644 --- a/package.json +++ b/package.json @@ -23,18 +23,18 @@ "test": "ava test/*.test.js test/**/*.test.js --verbose" }, "dependencies": { - "ajv": "4.7.5", + "ajv": "4.7.7", "brace": "0.8.0", "javascript-natural-sort": "0.7.1", - "lodash": "4.16.2", - "preact": "6.1.0" + "lodash": "4.16.4", + "preact": "6.3.0" }, "devDependencies": { "ava": "0.16.0", - "babel-core": "6.16.0", + "babel-core": "6.17.0", "babel-loader": "6.2.5", - "babel-preset-stage-2": "6.16.0", - "babel-preset-stage-3": "6.16.0", + "babel-preset-stage-2": "6.17.0", + "babel-preset-stage-3": "6.17.0", "browser-sync": "2.17.3", "css-loader": "0.25.0", "gulp": "3.9.1", diff --git a/src/CodeMode.js b/src/CodeMode.js new file mode 100644 index 0000000..02981d6 --- /dev/null +++ b/src/CodeMode.js @@ -0,0 +1,127 @@ +import { h } from 'preact' +import TextMode from './TextMode' +import ace from './assets/ace' + +/** + * CodeMode (powered by Ace editor) + * + * Usage: + * + * + * + * Methods: + * + * setText(text) + * getText() : text + * set(json : JSON) + * get() : JSON + * patch(actions: JSONPatch) + * format() + * compact() + * destroy() + * + */ +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 + } + + render (props, state) { + return h('div', {class: 'jsoneditor jsoneditor-mode-code'}, [ + this.renderMenu(), + + h('div', {class: 'jsoneditor-contents', id: this.id}) + ]) + } + + componentDidMount () { + const options = this.props.options || {} + + 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) + 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 = () => { + // TODO: handle changes + // console.log('ace editor changed') + } + + /** + * Set a string containing a JSON document + * @param {string} text + */ + setText (text) { + this.aceEditor.setValue(text, -1) + } + + /** + * Get the JSON document as text + * @return {string} text + */ + getText () { + return this.aceEditor.getValue() + } +} \ No newline at end of file diff --git a/src/TextMode.js b/src/TextMode.js index d7d0136..7aa165d 100644 --- a/src/TextMode.js +++ b/src/TextMode.js @@ -3,8 +3,30 @@ import { parseJSON } from './utils/jsonUtils' import { jsonToData, dataToJson, patchData } from './jsonData' import ModeButton from './menu/ModeButton' +/** + * TextMode + * + * Usage: + * + * + * + * Methods: + * + * setText(text) + * getText() : text + * set(json : JSON) + * get() : JSON + * patch(actions: JSONPatch) + * format() + * compact() + * destroy() + * + */ export default class TextMode extends Component { - // TODO: define propTypes constructor (props) { super(props) @@ -16,29 +38,7 @@ export default class TextMode extends Component { render (props, state) { return h('div', {class: 'jsoneditor jsoneditor-mode-text'}, [ - h('div', {class: 'jsoneditor-menu'}, [ - h('button', { - class: 'jsoneditor-format', - title: 'Format the JSON document', - onClick: this.handleFormat - }), - h('button', { - class: 'jsoneditor-compact', - title: 'Compact the JSON document', - onClick: this.handleCompact - }), - - // TODO: implement a button "Fix JSON" - - h('div', {class: 'jsoneditor-vertical-menu-separator'}), - - this.props.options.modes && h(ModeButton, { - modes: this.props.options.modes, - mode: this.props.mode, - onMode: this.props.onMode, - onError: this.handleError - }) - ]), + this.renderMenu(), h('div', {class: 'jsoneditor-contents'}, [ h('textarea', { @@ -50,10 +50,37 @@ export default class TextMode extends Component { ]) } + /** @protected */ + renderMenu () { + return h('div', {class: 'jsoneditor-menu'}, [ + h('button', { + class: 'jsoneditor-format', + title: 'Format the JSON document', + onClick: this.handleFormat + }), + h('button', { + class: 'jsoneditor-compact', + title: 'Compact the JSON document', + onClick: this.handleCompact + }), + + // TODO: implement a button "Repair" + + h('div', {class: 'jsoneditor-vertical-menu-separator'}), + + this.props.options.modes && h(ModeButton, { + modes: this.props.options.modes, + mode: this.props.mode, + onChangeMode: this.props.onChangeMode, + onError: this.handleError + }) + ]) + } + /** * Get the configured indentation * @return {number} - * @private + * @protected */ getIndentation () { return this.props.options && this.props.options.indentation || 2 @@ -62,15 +89,13 @@ export default class TextMode extends Component { /** * handle changed text input in the textarea * @param {Event} event - * @private + * @protected */ handleChange = (event) => { - this.setState({ - text: event.target.value - }) + this.setText(event.target.value) } - /** @private */ + /** @protected */ handleFormat = () => { try { this.format() @@ -80,7 +105,7 @@ export default class TextMode extends Component { } } - /** @private */ + /** @protected */ handleCompact = () => { try { this.compact() @@ -90,7 +115,7 @@ export default class TextMode extends Component { } } - /** @private */ + /** @protected */ handleError = (err) => { if (this.props.options && this.props.options.onError) { this.props.options.onError(err) @@ -145,9 +170,7 @@ export default class TextMode extends Component { * @param {Object | Array | string | number | boolean | null} json JSON data */ set (json) { - this.setState({ - text: JSON.stringify(json, null, this.getIndentation()) - }) + this.setText(JSON.stringify(json, null, this.getIndentation())) } /** @@ -155,7 +178,7 @@ export default class TextMode extends Component { * @returns {Object | Array | string | number | boolean | null} json */ get () { - return parseJSON(this.state.text) + return parseJSON(this.getText()) } /** @@ -173,4 +196,11 @@ export default class TextMode extends Component { getText () { return this.state.text } + + /** + * Destroy the editor + */ + destroy () { + + } } \ No newline at end of file diff --git a/src/TreeMode.js b/src/TreeMode.js index df07443..2dafd7e 100644 --- a/src/TreeMode.js +++ b/src/TreeMode.js @@ -48,7 +48,7 @@ export default class TreeMode extends Component { } render (props, state) { - // TODO: make mode tree dynamic + // TODO: make mode tree dynamic: can be 'tree', 'form', 'view' return h('div', { class: 'jsoneditor jsoneditor-mode-tree', 'data-jsoneditor': 'true' @@ -104,7 +104,7 @@ export default class TreeMode extends Component { this.props.options.modes && h(ModeButton, { modes: this.props.options.modes, mode: this.props.mode, - onMode: this.props.onMode, + onChangeMode: this.props.onChangeMode, onError: this.handleError }) ]) @@ -365,6 +365,13 @@ export default class TreeMode extends Component { }) } + /** + * Destroy the editor + */ + destroy () { + + } + /** * Default function to determine whether or not to expand a node initially * diff --git a/src/assets/ace/index.js b/src/assets/ace/index.js new file mode 100644 index 0000000..ebd2f5d --- /dev/null +++ b/src/assets/ace/index.js @@ -0,0 +1,8 @@ +// load brace +import ace from 'brace' + +// load required ace plugins +import 'brace/mode/json' +import 'brace/ext/searchbox' + +export default ace diff --git a/src/assets/ace/theme-jsoneditor.js b/src/assets/ace/theme-jsoneditor.js new file mode 100644 index 0000000..6cec3a6 --- /dev/null +++ b/src/assets/ace/theme-jsoneditor.js @@ -0,0 +1,144 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +ace.define('ace/theme/jsoneditor', ['require', 'exports', 'module', 'ace/lib/dom'], function(acequire, exports, module) { + +exports.isDark = false; +exports.cssClass = "ace-jsoneditor"; +exports.cssText = ".ace-jsoneditor .ace_gutter {\ +background: #ebebeb;\ +color: #333\ +}\ +\ +.ace-jsoneditor.ace_editor {\ +font-family: droid sans mono, consolas, monospace, courier new, courier, sans-serif;\ +line-height: 1.3;\ +}\ +.ace-jsoneditor .ace_print-margin {\ +width: 1px;\ +background: #e8e8e8\ +}\ +.ace-jsoneditor .ace_scroller {\ +background-color: #FFFFFF\ +}\ +.ace-jsoneditor .ace_text-layer {\ +color: gray\ +}\ +.ace-jsoneditor .ace_variable {\ +color: #1a1a1a\ +}\ +.ace-jsoneditor .ace_cursor {\ +border-left: 2px solid #000000\ +}\ +.ace-jsoneditor .ace_overwrite-cursors .ace_cursor {\ +border-left: 0px;\ +border-bottom: 1px solid #000000\ +}\ +.ace-jsoneditor .ace_marker-layer .ace_selection {\ +background: lightgray\ +}\ +.ace-jsoneditor.ace_multiselect .ace_selection.ace_start {\ +box-shadow: 0 0 3px 0px #FFFFFF;\ +border-radius: 2px\ +}\ +.ace-jsoneditor .ace_marker-layer .ace_step {\ +background: rgb(255, 255, 0)\ +}\ +.ace-jsoneditor .ace_marker-layer .ace_bracket {\ +margin: -1px 0 0 -1px;\ +border: 1px solid #BFBFBF\ +}\ +.ace-jsoneditor .ace_marker-layer .ace_active-line {\ +background: #FFFBD1\ +}\ +.ace-jsoneditor .ace_gutter-active-line {\ +background-color : #dcdcdc\ +}\ +.ace-jsoneditor .ace_marker-layer .ace_selected-word {\ +border: 1px solid lightgray\ +}\ +.ace-jsoneditor .ace_invisible {\ +color: #BFBFBF\ +}\ +.ace-jsoneditor .ace_keyword,\ +.ace-jsoneditor .ace_meta,\ +.ace-jsoneditor .ace_support.ace_constant.ace_property-value {\ +color: #AF956F\ +}\ +.ace-jsoneditor .ace_keyword.ace_operator {\ +color: #484848\ +}\ +.ace-jsoneditor .ace_keyword.ace_other.ace_unit {\ +color: #96DC5F\ +}\ +.ace-jsoneditor .ace_constant.ace_language {\ +color: darkorange\ +}\ +.ace-jsoneditor .ace_constant.ace_numeric {\ +color: red\ +}\ +.ace-jsoneditor .ace_constant.ace_character.ace_entity {\ +color: #BF78CC\ +}\ +.ace-jsoneditor .ace_invalid {\ +color: #FFFFFF;\ +background-color: #FF002A;\ +}\ +.ace-jsoneditor .ace_fold {\ +background-color: #AF956F;\ +border-color: #000000\ +}\ +.ace-jsoneditor .ace_storage,\ +.ace-jsoneditor .ace_support.ace_class,\ +.ace-jsoneditor .ace_support.ace_function,\ +.ace-jsoneditor .ace_support.ace_other,\ +.ace-jsoneditor .ace_support.ace_type {\ +color: #C52727\ +}\ +.ace-jsoneditor .ace_string {\ +color: green\ +}\ +.ace-jsoneditor .ace_comment {\ +color: #BCC8BA\ +}\ +.ace-jsoneditor .ace_entity.ace_name.ace_tag,\ +.ace-jsoneditor .ace_entity.ace_other.ace_attribute-name {\ +color: #606060\ +}\ +.ace-jsoneditor .ace_markup.ace_underline {\ +text-decoration: underline\ +}\ +.ace-jsoneditor .ace_indent-guide {\ +background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAACCAYAAACZgbYnAAAAE0lEQVQImWP4////f4bLly//BwAmVgd1/w11/gAAAABJRU5ErkJggg==\") right repeat-y\ +}"; + +var dom = acequire("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass); +}); diff --git a/src/develop.html b/src/develop.html index 5d9bfb6..3d93dde 100644 --- a/src/develop.html +++ b/src/develop.html @@ -7,7 +7,7 @@ - +