Skip to content
Open
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
"jsonlint": "josdejong/jsonlint#85a19d7"
},
"devDependencies": {
"chai": "^3.0.0",
"chai-spies": "^0.6.0",
"gulp": "^3.8.11",
"gulp-concat-css": "^2.0.0",
"gulp-minify-css": "^0.4.5",
"gulp-shell": "^0.3.0",
"gulp-util": "^3.0.3",
"mkdirp": "^0.5.0",
"mocha": "^2.1.0",
"mocha": "^2.2.5",
"uglify-js": "^2.4.16",
"webpack": "^1.5.3"
}
Expand Down
4 changes: 4 additions & 0 deletions src/js/History.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ History.prototype.canRedo = function () {

/**
* Undo the last action
* @returns applied history entry see {@link #add}
*/
History.prototype.undo = function () {
if (this.canUndo()) {
Expand All @@ -191,10 +192,12 @@ History.prototype.undo = function () {
// fire onchange event
this.onChange();
}
return obj || null;
};

/**
* Redo the last action
* @returns applied history entry see {@link #add}
*/
History.prototype.redo = function () {
if (this.canRedo()) {
Expand All @@ -217,6 +220,7 @@ History.prototype.redo = function () {
// fire onchange event
this.onChange();
}
return obj || null;
};

module.exports = History;
14 changes: 11 additions & 3 deletions src/js/textmode.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ textmode.create = function (container, options) {
if (options.change) {
// register onchange event
editor.on('change', function () {
options.change();
options.change(replaceRootJSONPatch(me));
});
}
}
Expand All @@ -170,13 +170,13 @@ textmode.create = function (container, options) {
// register onchange event
if (this.textarea.oninput === null) {
this.textarea.oninput = function () {
options.change();
options.change(replaceRootJSONPatch(me));
}
}
else {
// oninput is undefined. For IE8-
this.textarea.onchange = function () {
options.change();
options.change(replaceRootJSONPatch(me));
}
}
}
Expand Down Expand Up @@ -337,6 +337,14 @@ textmode.setText = function(jsonText) {
}
};

function replaceRootJSONPatch(textmode){
return {
op: "replace",
path: "",
value: textmode.get()
};
}

// define modes
module.exports = [
{
Expand Down
128 changes: 117 additions & 11 deletions src/js/treemode.js
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ treemode._onAction = function (action, params) {
// trigger the onChange callback
if (this.options.change) {
try {
this.options.change();
this.options.change(translateChangeToJSONPatch(action, params));
}
catch (err) {
util.log('Error in change callback: ', err);
Expand Down Expand Up @@ -580,11 +580,13 @@ treemode._createFrame = function () {
treemode._onUndo = function () {
if (this.history) {
// undo last action
this.history.undo();
var historyEntry = this.history.undo();

// trigger change callback
if (this.options.change) {
this.options.change();
// trigger change callback if anything have changed
if (this.options.change && historyEntry) {
this.options.change(
translateChangeToJSONPatch(historyEntry.action, historyEntry.params)
);
}
}
};
Expand All @@ -596,11 +598,12 @@ treemode._onUndo = function () {
treemode._onRedo = function () {
if (this.history) {
// redo last action
this.history.redo();

// trigger change callback
if (this.options.change) {
this.options.change();
var historyEntry = this.history.redo();
// trigger change callback if anything have changed
if (this.options.change && historyEntry) {
this.options.change(
translateChangeToJSONPatch(historyEntry.action, historyEntry.params)
);
}
}
};
Expand Down Expand Up @@ -724,6 +727,109 @@ treemode._createTable = function () {
this.frame.appendChild(contentOuter);
};

/**
* Translate our internal change info into JSON-Patch format
* @see http://tools.ietf.org/html/rfc6902
* @param {String} action JSONEditor action
* @param {Object} params JSONEditor params
* @return {Object} single JSON-Patch entry
*/
function translateChangeToJSONPatch(action, params){
/**
* Get path to node in JSON Pointer format
* (http://tools.ietf.org/html/rfc6901)
* _Almost_ like params.node.path().join("/")
* @param {Node} node jsoneditor node in question
* @returns {String} path
*/
function JSONPointer(node){
var path = "";
while (node.parent) {
var field = node.field != undefined ? node.field : node.index;
switch(typeof field){
case "string":
path = "/" + escapePathComponent(field) + path;
break;
case "number":
path = "/" + field + path;
break;
}
node = node.parent;
}
return path;

}
/**
* Escape `/` and `~`, according to JSON-Pointer rules.
* @param {String} str string to escape
* @returns {String} escaped string
*/
function escapePathComponent(str) {
if (str.indexOf('/') === -1 && str.indexOf('~') === -1)
return str;
return str.replace(/~/g, '~0').replace(/\//g, '~1');
}
var patch;
switch(action){
case "duplicateNode":
console.warn("duplicateNode->copy Is not supported yet, as currently new node with same name is created, what violates JSON-Patch");
break;
case "changeType":
(params.node.type == "array" || params.node.type == "object") && console.warn("changeType->replace may behave strange, as even if new node is created with specified type, its `node.value==\"\"`")
patch = {
op: "replace",
path: JSONPointer(params.node),
value: params.node.value
}
break;
case "editValue":
patch = {
op: "replace",
path: JSONPointer(params.node),
value: params.newValue
}
break;
case "removeNode":
patch = {
op: "remove",
path: JSONPointer(params.node)
}
break;
case "insertAfterNode":
case "insertBeforeNode":
case "appendNode":
console.warn("insertBeforeNode,appendNode->add may behave strange, as even if new node is created with specified type, its `node.value==\"\"`",
"also when inserting item into an array, new path is given, and there is no info about previous indexes, so it's hard to distinguish whether to use `-`, old index, or if it is not an array item, so we should stick to the given path");
patch = {
op: "add",
path: JSONPointer(params.node),
value: params.node.value
}
break;
case "moveNode":
console.warn("moveNode->move Still does not cover moving array items, as their name was already changed to `\"\"` which is fully valid object key, so we cannot distinguish it");
if(params.startParent !== params.endParent){
patch = {
op: "move",
from: JSONPointer(params.startParent) + "/" + params.node.field,
path: JSONPointer(params.node)
}
}
break;
case "editField":
patch = {
op: "move",
from: JSONPointer(params.node.parent) + "/" + params.oldValue,
path: JSONPointer(params.node)
}
break;
case "sort":
// no changes to the JSON itself
break;
}
return patch;
}

// define modes
module.exports = [
{
Expand All @@ -741,4 +847,4 @@ module.exports = [
mixin: treemode,
data: 'json'
}
];
];
Loading