Commit 36d0c096 authored by Jérome Perrin's avatar Jérome Perrin

update static version

parent be772200
...@@ -67,11 +67,11 @@ ...@@ -67,11 +67,11 @@
<h1>Dream Simulation</h1> <h1>Dream Simulation</h1>
<div data-role="controlgroup" data-type="horizontal" class="ui-btn-right"> <div data-role="controlgroup" data-type="horizontal" class="ui-btn-right">
<a data-icon="forward"
class="next_step_link ui-btn ui-icon-forward ui-btn-icon-right">Continue</a>
<!-- Next button to cycle through the results, similar to ERP5 navigation --> <!-- Next button to cycle through the results, similar to ERP5 navigation -->
<a data-icon="forward" <a data-icon="forward"
style="display: none"
style="display: none"
class="next_link ui-btn ui-icon-forward ui-btn-icon-right">Next</a> class="next_link ui-btn ui-icon-forward ui-btn-icon-right">Next</a>
</div> </div>
......
...@@ -103,6 +103,17 @@ ...@@ -103,6 +103,17 @@
return gadget.aq_pleasePublishMyState(forward_kw); return gadget.aq_pleasePublishMyState(forward_kw);
}); });
} }
function getNextStepLink(gadget, portal_type, options) {
if (options.action === "view_machine_shift_spreadsheet") {
var forward_kw = {
action: "view_run_simulation",
id: options.id
};
return gadget.aq_pleasePublishMyState(forward_kw);
} else {
return false;
}
}
function getTitle(gadget, portal_type, options) { function getTitle(gadget, portal_type, options) {
var title; var title;
if (portal_type === "Input Module") { if (portal_type === "Input Module") {
...@@ -330,7 +341,7 @@ ...@@ -330,7 +341,7 @@
return page_gadget.render(options); return page_gadget.render(options);
} }
}).push(function() { }).push(function() {
return RSVP.all([ page_gadget.getElement(), calculateNavigationHTML(gadget, portal_type, options), gadget.aq_pleasePublishMyState(back_kw), getTitle(gadget, portal_type, options), getNextLink(gadget, portal_type, options) ]); return RSVP.all([ page_gadget.getElement(), calculateNavigationHTML(gadget, portal_type, options), gadget.aq_pleasePublishMyState(back_kw), getTitle(gadget, portal_type, options), getNextLink(gadget, portal_type, options), getNextStepLink(gadget, portal_type, options) ]);
}).push(function(result_list) { }).push(function(result_list) {
var nav_html = result_list[1], page_element = result_list[0]; var nav_html = result_list[1], page_element = result_list[0];
// Update title // Update title
...@@ -358,6 +369,25 @@ ...@@ -358,6 +369,25 @@
} }
element.appendChild(page_element); element.appendChild(page_element);
$(element).trigger("create"); $(element).trigger("create");
return result_list[5];
}).push(function(next_step_link) {
var button = gadget.props.element.getElementsByClassName("next_step_link")[0];
$(button).hide();
var idle_timer;
function resetTimer() {
clearTimeout(idle_timer);
idle_timer = setTimeout(function() {
$(button).show();
}, idle_seconds * 1);
}
if (next_step_link !== false) {
button.href = next_step_link;
var idle_seconds = 10;
$(document.body).on("keydown click", resetTimer);
resetTimer();
} else {
$(document.body).off("keydown click", resetTimer);
}
// XXX RenderJS hack to start sub gadget services // XXX RenderJS hack to start sub gadget services
// Only work if this gadget has no parent. // Only work if this gadget has no parent.
if (page_gadget.startService !== undefined) { if (page_gadget.startService !== undefined) {
......
...@@ -117,6 +117,7 @@ ...@@ -117,6 +117,7 @@
edge_data.destination = getNodeId(gadget, connection.targetId); edge_data.destination = getNodeId(gadget, connection.targetId);
gadget.props.data.graph.edge[connection.id] = edge_data; gadget.props.data.graph.edge[connection.id] = edge_data;
} }
checkNodeConstraint(gadget, getNodeId(gadget, connection.targetId));
gadget.notifyDataChanged(); gadget.notifyDataChanged();
} }
function convertToAbsolutePosition(gadget, x, y) { function convertToAbsolutePosition(gadget, x, y) {
...@@ -217,6 +218,20 @@ ...@@ -217,6 +218,20 @@
}); });
gadget.notifyDataChanged(); gadget.notifyDataChanged();
} }
function checkNodeConstraint(gadget, node_id) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], element = $(gadget.props.element).find("#" + element_id), jsplumb_instance = gadget.props.jsplumb_instance, node_class = gadget.props.data.graph.node[node_id]._class;
if (jsplumb_instance.getConnections({
target: element_id
}).length === 0 && node_class.toLowerCase().search("source") === -1) {
var warning = $("<div></div>").attr({
"class": "alert_no_input ui-btn-icon-notext ui-icon-alert",
title: "No input defined!"
});
element.append(warning);
} else {
element.find(".alert_no_input").remove();
}
}
function updateElementData(gadget, node_id, data) { function updateElementData(gadget, node_id, data) {
var element_id = gadget.props.node_id_to_dom_element_id[node_id], new_id = data.id; var element_id = gadget.props.node_id_to_dom_element_id[node_id], new_id = data.id;
$(gadget.props.element).find("#" + element_id).text(data.data.name || new_id).attr("title", data.data.name || new_id).append('<div class="ep"></div></div>'); $(gadget.props.element).find("#" + element_id).text(data.data.name || new_id).attr("title", data.data.name || new_id).append('<div class="ep"></div></div>');
...@@ -638,6 +653,9 @@ ...@@ -638,6 +653,9 @@
$.each(this.props.data.graph.edge, function(key, value) { $.each(this.props.data.graph.edge, function(key, value) {
addEdge(gadget, key, value); addEdge(gadget, key, value);
}); });
$.each(this.props.data.graph.node, function(key, value) {
checkNodeConstraint(gadget, key);
});
return RSVP.all([ waitForDrop(gadget), waitForConnection(gadget), waitForConnectionDetached(gadget), waitForConnectionClick(gadget), gadget.props.nodes_click_monitor ]); return RSVP.all([ waitForDrop(gadget), waitForConnection(gadget), waitForConnectionDetached(gadget), waitForConnectionClick(gadget), gadget.props.nodes_click_monitor ]);
}); });
})(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser); })(RSVP, rJS, $, jsPlumb, Handlebars, loopEventListener, promiseEventListener, DOMParser);
\ No newline at end of file
/*! /*!
* QUnit 1.17.1 * QUnit 1.18.0
* http://qunitjs.com/ * http://qunitjs.com/
* *
* Copyright jQuery Foundation and other contributors * Copyright jQuery Foundation and other contributors
* Released under the MIT license * Released under the MIT license
* http://jquery.org/license * http://jquery.org/license
* *
* Date: 2015-01-20T19:39Z * Date: 2015-04-03T10:23Z
*/ */
/** Font Family and Sizes */ /** Font Family and Sizes */
...@@ -116,7 +116,13 @@ ...@@ -116,7 +116,13 @@
#qunit-tests.hidepass li.running, #qunit-tests.hidepass li.running,
#qunit-tests.hidepass li.pass { #qunit-tests.hidepass li.pass {
display: none; visibility: hidden;
position: absolute;
width: 0px;
height: 0px;
padding: 0;
border: 0;
margin: 0;
} }
#qunit-tests li strong { #qunit-tests li strong {
...@@ -132,6 +138,11 @@ ...@@ -132,6 +138,11 @@
color: #C2CCD1; color: #C2CCD1;
text-decoration: none; text-decoration: none;
} }
#qunit-tests li p a {
padding: 0.25em;
color: #6B6464;
}
#qunit-tests li a:hover, #qunit-tests li a:hover,
#qunit-tests li a:focus { #qunit-tests li a:focus {
color: #000; color: #000;
......
/*! /*!
* QUnit 1.17.1 * QUnit 1.18.0
* http://qunitjs.com/ * http://qunitjs.com/
* *
* Copyright jQuery Foundation and other contributors * Copyright jQuery Foundation and other contributors
* Released under the MIT license * Released under the MIT license
* http://jquery.org/license * http://jquery.org/license
* *
* Date: 2015-01-20T19:39Z * Date: 2015-04-03T10:23Z
*/ */
(function( window ) { (function( window ) {
...@@ -116,6 +116,9 @@ config = { ...@@ -116,6 +116,9 @@ config = {
// when enabled, all tests must call expect() // when enabled, all tests must call expect()
requireExpects: false, requireExpects: false,
// depth up-to which object will be dumped
maxDepth: 5,
// add checkboxes that are persisted in the query-string // add checkboxes that are persisted in the query-string
// when enabled, the id is set to `true` as a `QUnit.config` property // when enabled, the id is set to `true` as a `QUnit.config` property
urlConfig: [ urlConfig: [
...@@ -185,11 +188,17 @@ config.modules.push( config.currentModule ); ...@@ -185,11 +188,17 @@ config.modules.push( config.currentModule );
// String search anywhere in moduleName+testName // String search anywhere in moduleName+testName
config.filter = urlParams.filter; config.filter = urlParams.filter;
if ( urlParams.maxDepth ) {
config.maxDepth = parseInt( urlParams.maxDepth, 10 ) === -1 ?
Number.POSITIVE_INFINITY :
urlParams.maxDepth;
}
config.testId = []; config.testId = [];
if ( urlParams.testId ) { if ( urlParams.testId ) {
// Ensure that urlParams.testId is an array // Ensure that urlParams.testId is an array
urlParams.testId = [].concat( urlParams.testId ); urlParams.testId = decodeURIComponent( urlParams.testId ).split( "," );
for ( i = 0; i < urlParams.testId.length; i++ ) { for ( i = 0; i < urlParams.testId.length; i++ ) {
config.testId.push( urlParams.testId[ i ] ); config.testId.push( urlParams.testId[ i ] );
} }
...@@ -197,6 +206,9 @@ config.modules.push( config.currentModule ); ...@@ -197,6 +206,9 @@ config.modules.push( config.currentModule );
// Figure out if we're running the tests from a server or not // Figure out if we're running the tests from a server or not
QUnit.isLocal = location.protocol === "file:"; QUnit.isLocal = location.protocol === "file:";
// Expose the current QUnit version
QUnit.version = "1.18.0";
}()); }());
// Root QUnit object. // Root QUnit object.
...@@ -484,20 +496,14 @@ function done() { ...@@ -484,20 +496,14 @@ function done() {
}); });
} }
// Doesn't support IE6 to IE9 // Doesn't support IE6 to IE9, it will return undefined on these browsers
// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack // See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
function extractStacktrace( e, offset ) { function extractStacktrace( e, offset ) {
offset = offset === undefined ? 4 : offset; offset = offset === undefined ? 4 : offset;
var stack, include, i; var stack, include, i;
if ( e.stacktrace ) { if ( e.stack ) {
// Opera 12.x
return e.stacktrace.split( "\n" )[ offset + 3 ];
} else if ( e.stack ) {
// Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
stack = e.stack.split( "\n" ); stack = e.stack.split( "\n" );
if ( /^error$/i.test( stack[ 0 ] ) ) { if ( /^error$/i.test( stack[ 0 ] ) ) {
stack.shift(); stack.shift();
...@@ -515,9 +521,10 @@ function extractStacktrace( e, offset ) { ...@@ -515,9 +521,10 @@ function extractStacktrace( e, offset ) {
} }
} }
return stack[ offset ]; return stack[ offset ];
// Support: Safari <=6 only
} else if ( e.sourceURL ) { } else if ( e.sourceURL ) {
// Safari < 6
// exclude useless self-reference for generated Error objects // exclude useless self-reference for generated Error objects
if ( /qunit.js$/.test( e.sourceURL ) ) { if ( /qunit.js$/.test( e.sourceURL ) ) {
return; return;
...@@ -529,16 +536,19 @@ function extractStacktrace( e, offset ) { ...@@ -529,16 +536,19 @@ function extractStacktrace( e, offset ) {
} }
function sourceFromStacktrace( offset ) { function sourceFromStacktrace( offset ) {
var e = new Error(); var error = new Error();
if ( !e.stack ) {
// Support: Safari <=7 only, IE <=10 - 11 only
// Not all browsers generate the `stack` property for `new Error()`, see also #636
if ( !error.stack ) {
try { try {
throw e; throw error;
} catch ( err ) { } catch ( err ) {
// This should already be true in most browsers error = err;
e = err;
} }
} }
return extractStacktrace( e, offset );
return extractStacktrace( error, offset );
} }
function synchronize( callback, last ) { function synchronize( callback, last ) {
...@@ -1123,7 +1133,7 @@ Test.prototype = { ...@@ -1123,7 +1133,7 @@ Test.prototype = {
valid: function() { valid: function() {
var include, var include,
filter = config.filter, filter = config.filter && config.filter.toLowerCase(),
module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(), module = QUnit.urlParams.module && QUnit.urlParams.module.toLowerCase(),
fullName = ( this.module.name + ": " + this.testName ).toLowerCase(); fullName = ( this.module.name + ": " + this.testName ).toLowerCase();
...@@ -1146,7 +1156,7 @@ Test.prototype = { ...@@ -1146,7 +1156,7 @@ Test.prototype = {
include = filter.charAt( 0 ) !== "!"; include = filter.charAt( 0 ) !== "!";
if ( !include ) { if ( !include ) {
filter = filter.toLowerCase().slice( 1 ); filter = filter.slice( 1 );
} }
// If the filter matches, we need to honour include // If the filter matches, we need to honour include
...@@ -1284,87 +1294,52 @@ QUnit.assert = Assert.prototype = { ...@@ -1284,87 +1294,52 @@ QUnit.assert = Assert.prototype = {
return assert.test.push.apply( assert.test, arguments ); return assert.test.push.apply( assert.test, arguments );
}, },
/**
* Asserts rough true-ish result.
* @name ok
* @function
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function( result, message ) { ok: function( result, message ) {
message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " + message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
QUnit.dump.parse( result ) ); QUnit.dump.parse( result ) );
this.push( !!result, result, true, message ); this.push( !!result, result, true, message );
}, },
/** notOk: function( result, message ) {
* Assert that the first two arguments are equal, with an optional message. message = message || ( !result ? "okay" : "failed, expected argument to be falsy, was: " +
* Prints out both actual and expected values. QUnit.dump.parse( result ) );
* @name equal this.push( !result, result, false, message );
* @function },
* @example equal( format( "{0} bytes.", 2), "2 bytes.", "replaces {0} with next argument" );
*/
equal: function( actual, expected, message ) { equal: function( actual, expected, message ) {
/*jshint eqeqeq:false */ /*jshint eqeqeq:false */
this.push( expected == actual, actual, expected, message ); this.push( expected == actual, actual, expected, message );
}, },
/**
* @name notEqual
* @function
*/
notEqual: function( actual, expected, message ) { notEqual: function( actual, expected, message ) {
/*jshint eqeqeq:false */ /*jshint eqeqeq:false */
this.push( expected != actual, actual, expected, message ); this.push( expected != actual, actual, expected, message );
}, },
/**
* @name propEqual
* @function
*/
propEqual: function( actual, expected, message ) { propEqual: function( actual, expected, message ) {
actual = objectValues( actual ); actual = objectValues( actual );
expected = objectValues( expected ); expected = objectValues( expected );
this.push( QUnit.equiv( actual, expected ), actual, expected, message ); this.push( QUnit.equiv( actual, expected ), actual, expected, message );
}, },
/**
* @name notPropEqual
* @function
*/
notPropEqual: function( actual, expected, message ) { notPropEqual: function( actual, expected, message ) {
actual = objectValues( actual ); actual = objectValues( actual );
expected = objectValues( expected ); expected = objectValues( expected );
this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
}, },
/**
* @name deepEqual
* @function
*/
deepEqual: function( actual, expected, message ) { deepEqual: function( actual, expected, message ) {
this.push( QUnit.equiv( actual, expected ), actual, expected, message ); this.push( QUnit.equiv( actual, expected ), actual, expected, message );
}, },
/**
* @name notDeepEqual
* @function
*/
notDeepEqual: function( actual, expected, message ) { notDeepEqual: function( actual, expected, message ) {
this.push( !QUnit.equiv( actual, expected ), actual, expected, message ); this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
}, },
/**
* @name strictEqual
* @function
*/
strictEqual: function( actual, expected, message ) { strictEqual: function( actual, expected, message ) {
this.push( expected === actual, actual, expected, message ); this.push( expected === actual, actual, expected, message );
}, },
/**
* @name notStrictEqual
* @function
*/
notStrictEqual: function( actual, expected, message ) { notStrictEqual: function( actual, expected, message ) {
this.push( expected !== actual, actual, expected, message ); this.push( expected !== actual, actual, expected, message );
}, },
...@@ -1372,7 +1347,8 @@ QUnit.assert = Assert.prototype = { ...@@ -1372,7 +1347,8 @@ QUnit.assert = Assert.prototype = {
"throws": function( block, expected, message ) { "throws": function( block, expected, message ) {
var actual, expectedType, var actual, expectedType,
expectedOutput = expected, expectedOutput = expected,
ok = false; ok = false,
currentTest = ( this instanceof Assert && this.test ) || QUnit.config.current;
// 'expected' is optional unless doing string comparison // 'expected' is optional unless doing string comparison
if ( message == null && typeof expected === "string" ) { if ( message == null && typeof expected === "string" ) {
...@@ -1380,13 +1356,13 @@ QUnit.assert = Assert.prototype = { ...@@ -1380,13 +1356,13 @@ QUnit.assert = Assert.prototype = {
expected = null; expected = null;
} }
this.test.ignoreGlobalErrors = true; currentTest.ignoreGlobalErrors = true;
try { try {
block.call( this.test.testEnvironment ); block.call( currentTest.testEnvironment );
} catch (e) { } catch (e) {
actual = e; actual = e;
} }
this.test.ignoreGlobalErrors = false; currentTest.ignoreGlobalErrors = false;
if ( actual ) { if ( actual ) {
expectedType = QUnit.objectType( expected ); expectedType = QUnit.objectType( expected );
...@@ -1419,11 +1395,9 @@ QUnit.assert = Assert.prototype = { ...@@ -1419,11 +1395,9 @@ QUnit.assert = Assert.prototype = {
expectedOutput = null; expectedOutput = null;
ok = true; ok = true;
} }
this.push( ok, actual, expectedOutput, message );
} else {
this.test.pushFailure( message, null, "No exception was thrown." );
} }
currentTest.assert.push( ok, actual, expectedOutput, message );
} }
}; };
...@@ -1783,7 +1757,7 @@ QUnit.dump = (function() { ...@@ -1783,7 +1757,7 @@ QUnit.dump = (function() {
join: join, join: join,
// //
depth: 1, depth: 1,
maxDepth: 5, maxDepth: QUnit.config.maxDepth,
// This is the list of parsers, to modify them, use dump.setParser // This is the list of parsers, to modify them, use dump.setParser
parsers: { parsers: {
...@@ -1830,7 +1804,7 @@ QUnit.dump = (function() { ...@@ -1830,7 +1804,7 @@ QUnit.dump = (function() {
nonEnumerableProperties = [ "message", "name" ]; nonEnumerableProperties = [ "message", "name" ];
for ( i in nonEnumerableProperties ) { for ( i in nonEnumerableProperties ) {
key = nonEnumerableProperties[ i ]; key = nonEnumerableProperties[ i ];
if ( key in map && !( key in keys ) ) { if ( key in map && inArray( key, keys ) < 0 ) {
keys.push( key ); keys.push( key );
} }
} }
...@@ -1949,6 +1923,7 @@ if ( typeof window !== "undefined" ) { ...@@ -1949,6 +1923,7 @@ if ( typeof window !== "undefined" ) {
"start", "start",
"stop", "stop",
"ok", "ok",
"notOk",
"equal", "equal",
"notEqual", "notEqual",
"propEqual", "propEqual",
...@@ -1981,6 +1956,13 @@ if ( typeof exports !== "undefined" && exports ) { ...@@ -1981,6 +1956,13 @@ if ( typeof exports !== "undefined" && exports ) {
exports.QUnit = QUnit; exports.QUnit = QUnit;
} }
if ( typeof define === "function" && define.amd ) {
define( function() {
return QUnit;
} );
QUnit.config.autostart = false;
}
// Get a reference to the global object, like window in browsers // Get a reference to the global object, like window in browsers
}( (function() { }( (function() {
return this; return this;
...@@ -1989,150 +1971,1088 @@ if ( typeof exports !== "undefined" && exports ) { ...@@ -1989,150 +1971,1088 @@ if ( typeof exports !== "undefined" && exports ) {
/*istanbul ignore next */ /*istanbul ignore next */
// jscs:disable maximumLineLength // jscs:disable maximumLineLength
/* /*
* Javascript Diff Algorithm * This file is a modified version of google-diff-match-patch's JavaScript implementation
* By John Resig (http://ejohn.org/) * (https://code.google.com/p/google-diff-match-patch/source/browse/trunk/javascript/diff_match_patch_uncompressed.js),
* Modified by Chu Alan "sprite" * modifications are licensed as more fully set forth in LICENSE.txt.
*
* The original source of google-diff-match-patch is attributable and licensed as follows:
* *
* Released under the MIT license. * Copyright 2006 Google Inc.
* http://code.google.com/p/google-diff-match-patch/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* *
* More Info: * More Info:
* http://ejohn.org/projects/javascript-diff-algorithm/ * https://code.google.com/p/google-diff-match-patch/
* *
* Usage: QUnit.diff(expected, actual) * Usage: QUnit.diff(expected, actual)
* *
* QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) === "the quick <del>brown </del> fox jump<ins>s</ins><del>ed</del over"
*/ */
QUnit.diff = (function() { QUnit.diff = (function() {
var hasOwn = Object.prototype.hasOwnProperty;
/*jshint eqeqeq:false, eqnull:true */
function diff( o, n ) {
var i,
ns = {},
os = {};
for ( i = 0; i < n.length; i++ ) {
if ( !hasOwn.call( ns, n[ i ] ) ) {
ns[ n[ i ] ] = {
rows: [],
o: null
};
}
ns[ n[ i ] ].rows.push( i );
}
for ( i = 0; i < o.length; i++ ) {
if ( !hasOwn.call( os, o[ i ] ) ) {
os[ o[ i ] ] = {
rows: [],
n: null
};
}
os[ o[ i ] ].rows.push( i );
}
for ( i in ns ) {
if ( hasOwn.call( ns, i ) ) {
if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
n[ ns[ i ].rows[ 0 ] ] = {
text: n[ ns[ i ].rows[ 0 ] ],
row: os[ i ].rows[ 0 ]
};
o[ os[ i ].rows[ 0 ] ] = {
text: o[ os[ i ].rows[ 0 ] ],
row: ns[ i ].rows[ 0 ]
};
}
}
}
for ( i = 0; i < n.length - 1; i++ ) { function DiffMatchPatch() {
if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
n[ i + 1 ] == o[ n[ i ].row + 1 ] ) { // Defaults.
// Redefine these in your program to override the defaults.
n[ i + 1 ] = {
text: n[ i + 1 ], // Number of seconds to map a diff before giving up (0 for infinity).
row: n[ i ].row + 1 this.DiffTimeout = 1.0;
}; // Cost of an empty edit operation in terms of edit characters.
o[ n[ i ].row + 1 ] = { this.DiffEditCost = 4;
text: o[ n[ i ].row + 1 ], }
row: i + 1
}; // DIFF FUNCTIONS
}
} /**
* The data structure representing a diff is an array of tuples:
for ( i = n.length - 1; i > 0; i-- ) { * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null && * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
n[ i - 1 ] == o[ n[ i ].row - 1 ] ) { */
var DIFF_DELETE = -1,
n[ i - 1 ] = { DIFF_INSERT = 1,
text: n[ i - 1 ], DIFF_EQUAL = 0;
row: n[ i ].row - 1
}; /**
o[ n[ i ].row - 1 ] = { * Find the differences between two texts. Simplifies the problem by stripping
text: o[ n[ i ].row - 1 ], * any common prefix or suffix off the texts before diffing.
row: i - 1 * @param {string} text1 Old string to be diffed.
}; * @param {string} text2 New string to be diffed.
} * @param {boolean=} optChecklines Optional speedup flag. If present and false,
} * then don't run a line-level diff first to identify the changed areas.
* Defaults to true, which does a faster, slightly less optimal diff.
return { * @param {number} optDeadline Optional time when the diff should be complete
o: o, * by. Used internally for recursive calls. Users should set DiffTimeout
n: n * instead.
}; * @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
} */
DiffMatchPatch.prototype.DiffMain = function( text1, text2, optChecklines, optDeadline ) {
return function( o, n ) { var deadline, checklines, commonlength,
o = o.replace( /\s+$/, "" ); commonprefix, commonsuffix, diffs;
n = n.replace( /\s+$/, "" ); // Set a deadline by which time the diff must be complete.
if ( typeof optDeadline === "undefined" ) {
var i, pre, if ( this.DiffTimeout <= 0 ) {
str = "", optDeadline = Number.MAX_VALUE;
out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ), } else {
oSpace = o.match( /\s+/g ), optDeadline = ( new Date() ).getTime() + this.DiffTimeout * 1000;
nSpace = n.match( /\s+/g ); }
}
if ( oSpace == null ) { deadline = optDeadline;
oSpace = [ " " ];
} else { // Check for null inputs.
oSpace.push( " " ); if ( text1 === null || text2 === null ) {
} throw new Error( "Null input. (DiffMain)" );
}
if ( nSpace == null ) {
nSpace = [ " " ]; // Check for equality (speedup).
} else { if ( text1 === text2 ) {
nSpace.push( " " ); if ( text1 ) {
} return [
[ DIFF_EQUAL, text1 ]
if ( out.n.length === 0 ) { ];
for ( i = 0; i < out.o.length; i++ ) { }
str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>"; return [];
} }
} else {
if ( out.n[ 0 ].text == null ) { if ( typeof optChecklines === "undefined" ) {
for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) { optChecklines = true;
str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>"; }
}
} checklines = optChecklines;
for ( i = 0; i < out.n.length; i++ ) { // Trim off common prefix (speedup).
if ( out.n[ i ].text == null ) { commonlength = this.diffCommonPrefix( text1, text2 );
str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>"; commonprefix = text1.substring( 0, commonlength );
} else { text1 = text1.substring( commonlength );
text2 = text2.substring( commonlength );
// `pre` initialized at top of scope
pre = ""; // Trim off common suffix (speedup).
/////////
for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) { commonlength = this.diffCommonSuffix( text1, text2 );
pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>"; commonsuffix = text1.substring( text1.length - commonlength );
} text1 = text1.substring( 0, text1.length - commonlength );
str += " " + out.n[ i ].text + nSpace[ i ] + pre; text2 = text2.substring( 0, text2.length - commonlength );
}
} // Compute the diff on the middle block.
} diffs = this.diffCompute( text1, text2, checklines, deadline );
return str; // Restore the prefix and suffix.
}; if ( commonprefix ) {
diffs.unshift( [ DIFF_EQUAL, commonprefix ] );
}
if ( commonsuffix ) {
diffs.push( [ DIFF_EQUAL, commonsuffix ] );
}
this.diffCleanupMerge( diffs );
return diffs;
};
/**
* Reduce the number of edits by eliminating operationally trivial equalities.
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
*/
DiffMatchPatch.prototype.diffCleanupEfficiency = function( diffs ) {
var changes, equalities, equalitiesLength, lastequality,
pointer, preIns, preDel, postIns, postDel;
changes = false;
equalities = []; // Stack of indices where equalities are found.
equalitiesLength = 0; // Keeping our own length var is faster in JS.
/** @type {?string} */
lastequality = null;
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
pointer = 0; // Index of current position.
// Is there an insertion operation before the last equality.
preIns = false;
// Is there a deletion operation before the last equality.
preDel = false;
// Is there an insertion operation after the last equality.
postIns = false;
// Is there a deletion operation after the last equality.
postDel = false;
while ( pointer < diffs.length ) {
if ( diffs[ pointer ][ 0 ] === DIFF_EQUAL ) { // Equality found.
if ( diffs[ pointer ][ 1 ].length < this.DiffEditCost && ( postIns || postDel ) ) {
// Candidate found.
equalities[ equalitiesLength++ ] = pointer;
preIns = postIns;
preDel = postDel;
lastequality = diffs[ pointer ][ 1 ];
} else {
// Not a candidate, and can never become one.
equalitiesLength = 0;
lastequality = null;
}
postIns = postDel = false;
} else { // An insertion or deletion.
if ( diffs[ pointer ][ 0 ] === DIFF_DELETE ) {
postDel = true;
} else {
postIns = true;
}
/*
* Five types to be split:
* <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
* <ins>A</ins>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<ins>C</ins>
* <ins>A</del>X<ins>C</ins><del>D</del>
* <ins>A</ins><del>B</del>X<del>C</del>
*/
if ( lastequality && ( ( preIns && preDel && postIns && postDel ) ||
( ( lastequality.length < this.DiffEditCost / 2 ) &&
( preIns + preDel + postIns + postDel ) === 3 ) ) ) {
// Duplicate record.
diffs.splice( equalities[equalitiesLength - 1], 0, [ DIFF_DELETE, lastequality ] );
// Change second copy to insert.
diffs[ equalities[ equalitiesLength - 1 ] + 1 ][ 0 ] = DIFF_INSERT;
equalitiesLength--; // Throw away the equality we just deleted;
lastequality = null;
if (preIns && preDel) {
// No changes made which could affect previous entry, keep going.
postIns = postDel = true;
equalitiesLength = 0;
} else {
equalitiesLength--; // Throw away the previous equality.
pointer = equalitiesLength > 0 ? equalities[ equalitiesLength - 1 ] : -1;
postIns = postDel = false;
}
changes = true;
}
}
pointer++;
}
if ( changes ) {
this.diffCleanupMerge( diffs );
}
};
/**
* Convert a diff array into a pretty HTML report.
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
* @param {integer} string to be beautified.
* @return {string} HTML representation.
*/
DiffMatchPatch.prototype.diffPrettyHtml = function( diffs ) {
var op, data, x, html = [];
for ( x = 0; x < diffs.length; x++ ) {
op = diffs[x][0]; // Operation (insert, delete, equal)
data = diffs[x][1]; // Text of change.
switch ( op ) {
case DIFF_INSERT:
html[x] = "<ins>" + data + "</ins>";
break;
case DIFF_DELETE:
html[x] = "<del>" + data + "</del>";
break;
case DIFF_EQUAL:
html[x] = "<span>" + data + "</span>";
break;
}
}
return html.join("");
};
/**
* Determine the common prefix of two strings.
* @param {string} text1 First string.
* @param {string} text2 Second string.
* @return {number} The number of characters common to the start of each
* string.
*/
DiffMatchPatch.prototype.diffCommonPrefix = function( text1, text2 ) {
var pointermid, pointermax, pointermin, pointerstart;
// Quick check for common null cases.
if ( !text1 || !text2 || text1.charAt(0) !== text2.charAt(0) ) {
return 0;
}
// Binary search.
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
pointermin = 0;
pointermax = Math.min( text1.length, text2.length );
pointermid = pointermax;
pointerstart = 0;
while ( pointermin < pointermid ) {
if ( text1.substring( pointerstart, pointermid ) === text2.substring( pointerstart, pointermid ) ) {
pointermin = pointermid;
pointerstart = pointermin;
} else {
pointermax = pointermid;
}
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
}
return pointermid;
};
/**
* Determine the common suffix of two strings.
* @param {string} text1 First string.
* @param {string} text2 Second string.
* @return {number} The number of characters common to the end of each string.
*/
DiffMatchPatch.prototype.diffCommonSuffix = function( text1, text2 ) {
var pointermid, pointermax, pointermin, pointerend;
// Quick check for common null cases.
if (!text1 || !text2 || text1.charAt(text1.length - 1) !== text2.charAt(text2.length - 1)) {
return 0;
}
// Binary search.
// Performance analysis: http://neil.fraser.name/news/2007/10/09/
pointermin = 0;
pointermax = Math.min(text1.length, text2.length);
pointermid = pointermax;
pointerend = 0;
while ( pointermin < pointermid ) {
if (text1.substring( text1.length - pointermid, text1.length - pointerend ) ===
text2.substring( text2.length - pointermid, text2.length - pointerend ) ) {
pointermin = pointermid;
pointerend = pointermin;
} else {
pointermax = pointermid;
}
pointermid = Math.floor( ( pointermax - pointermin ) / 2 + pointermin );
}
return pointermid;
};
/**
* Find the differences between two texts. Assumes that the texts do not
* have any common prefix or suffix.
* @param {string} text1 Old string to be diffed.
* @param {string} text2 New string to be diffed.
* @param {boolean} checklines Speedup flag. If false, then don't run a
* line-level diff first to identify the changed areas.
* If true, then run a faster, slightly less optimal diff.
* @param {number} deadline Time when the diff should be complete by.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
* @private
*/
DiffMatchPatch.prototype.diffCompute = function( text1, text2, checklines, deadline ) {
var diffs, longtext, shorttext, i, hm,
text1A, text2A, text1B, text2B,
midCommon, diffsA, diffsB;
if ( !text1 ) {
// Just add some text (speedup).
return [
[ DIFF_INSERT, text2 ]
];
}
if (!text2) {
// Just delete some text (speedup).
return [
[ DIFF_DELETE, text1 ]
];
}
longtext = text1.length > text2.length ? text1 : text2;
shorttext = text1.length > text2.length ? text2 : text1;
i = longtext.indexOf( shorttext );
if ( i !== -1 ) {
// Shorter text is inside the longer text (speedup).
diffs = [
[ DIFF_INSERT, longtext.substring( 0, i ) ],
[ DIFF_EQUAL, shorttext ],
[ DIFF_INSERT, longtext.substring( i + shorttext.length ) ]
];
// Swap insertions for deletions if diff is reversed.
if ( text1.length > text2.length ) {
diffs[0][0] = diffs[2][0] = DIFF_DELETE;
}
return diffs;
}
if ( shorttext.length === 1 ) {
// Single character string.
// After the previous speedup, the character can't be an equality.
return [
[ DIFF_DELETE, text1 ],
[ DIFF_INSERT, text2 ]
];
}
// Check to see if the problem can be split in two.
hm = this.diffHalfMatch(text1, text2);
if (hm) {
// A half-match was found, sort out the return data.
text1A = hm[0];
text1B = hm[1];
text2A = hm[2];
text2B = hm[3];
midCommon = hm[4];
// Send both pairs off for separate processing.
diffsA = this.DiffMain(text1A, text2A, checklines, deadline);
diffsB = this.DiffMain(text1B, text2B, checklines, deadline);
// Merge the results.
return diffsA.concat([
[ DIFF_EQUAL, midCommon ]
], diffsB);
}
if (checklines && text1.length > 100 && text2.length > 100) {
return this.diffLineMode(text1, text2, deadline);
}
return this.diffBisect(text1, text2, deadline);
};
/**
* Do the two texts share a substring which is at least half the length of the
* longer text?
* This speedup can produce non-minimal diffs.
* @param {string} text1 First string.
* @param {string} text2 Second string.
* @return {Array.<string>} Five element Array, containing the prefix of
* text1, the suffix of text1, the prefix of text2, the suffix of
* text2 and the common middle. Or null if there was no match.
* @private
*/
DiffMatchPatch.prototype.diffHalfMatch = function(text1, text2) {
var longtext, shorttext, dmp,
text1A, text2B, text2A, text1B, midCommon,
hm1, hm2, hm;
if (this.DiffTimeout <= 0) {
// Don't risk returning a non-optimal diff if we have unlimited time.
return null;
}
longtext = text1.length > text2.length ? text1 : text2;
shorttext = text1.length > text2.length ? text2 : text1;
if (longtext.length < 4 || shorttext.length * 2 < longtext.length) {
return null; // Pointless.
}
dmp = this; // 'this' becomes 'window' in a closure.
/**
* Does a substring of shorttext exist within longtext such that the substring
* is at least half the length of longtext?
* Closure, but does not reference any external variables.
* @param {string} longtext Longer string.
* @param {string} shorttext Shorter string.
* @param {number} i Start index of quarter length substring within longtext.
* @return {Array.<string>} Five element Array, containing the prefix of
* longtext, the suffix of longtext, the prefix of shorttext, the suffix
* of shorttext and the common middle. Or null if there was no match.
* @private
*/
function diffHalfMatchI(longtext, shorttext, i) {
var seed, j, bestCommon, prefixLength, suffixLength,
bestLongtextA, bestLongtextB, bestShorttextA, bestShorttextB;
// Start with a 1/4 length substring at position i as a seed.
seed = longtext.substring(i, i + Math.floor(longtext.length / 4));
j = -1;
bestCommon = "";
while ((j = shorttext.indexOf(seed, j + 1)) !== -1) {
prefixLength = dmp.diffCommonPrefix(longtext.substring(i),
shorttext.substring(j));
suffixLength = dmp.diffCommonSuffix(longtext.substring(0, i),
shorttext.substring(0, j));
if (bestCommon.length < suffixLength + prefixLength) {
bestCommon = shorttext.substring(j - suffixLength, j) +
shorttext.substring(j, j + prefixLength);
bestLongtextA = longtext.substring(0, i - suffixLength);
bestLongtextB = longtext.substring(i + prefixLength);
bestShorttextA = shorttext.substring(0, j - suffixLength);
bestShorttextB = shorttext.substring(j + prefixLength);
}
}
if (bestCommon.length * 2 >= longtext.length) {
return [ bestLongtextA, bestLongtextB,
bestShorttextA, bestShorttextB, bestCommon
];
} else {
return null;
}
}
// First check if the second quarter is the seed for a half-match.
hm1 = diffHalfMatchI(longtext, shorttext,
Math.ceil(longtext.length / 4));
// Check again based on the third quarter.
hm2 = diffHalfMatchI(longtext, shorttext,
Math.ceil(longtext.length / 2));
if (!hm1 && !hm2) {
return null;
} else if (!hm2) {
hm = hm1;
} else if (!hm1) {
hm = hm2;
} else {
// Both matched. Select the longest.
hm = hm1[4].length > hm2[4].length ? hm1 : hm2;
}
// A half-match was found, sort out the return data.
text1A, text1B, text2A, text2B;
if (text1.length > text2.length) {
text1A = hm[0];
text1B = hm[1];
text2A = hm[2];
text2B = hm[3];
} else {
text2A = hm[0];
text2B = hm[1];
text1A = hm[2];
text1B = hm[3];
}
midCommon = hm[4];
return [ text1A, text1B, text2A, text2B, midCommon ];
};
/**
* Do a quick line-level diff on both strings, then rediff the parts for
* greater accuracy.
* This speedup can produce non-minimal diffs.
* @param {string} text1 Old string to be diffed.
* @param {string} text2 New string to be diffed.
* @param {number} deadline Time when the diff should be complete by.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
* @private
*/
DiffMatchPatch.prototype.diffLineMode = function(text1, text2, deadline) {
var a, diffs, linearray, pointer, countInsert,
countDelete, textInsert, textDelete, j;
// Scan the text on a line-by-line basis first.
a = this.diffLinesToChars(text1, text2);
text1 = a.chars1;
text2 = a.chars2;
linearray = a.lineArray;
diffs = this.DiffMain(text1, text2, false, deadline);
// Convert the diff back to original text.
this.diffCharsToLines(diffs, linearray);
// Eliminate freak matches (e.g. blank lines)
this.diffCleanupSemantic(diffs);
// Rediff any replacement blocks, this time character-by-character.
// Add a dummy entry at the end.
diffs.push( [ DIFF_EQUAL, "" ] );
pointer = 0;
countDelete = 0;
countInsert = 0;
textDelete = "";
textInsert = "";
while (pointer < diffs.length) {
switch ( diffs[pointer][0] ) {
case DIFF_INSERT:
countInsert++;
textInsert += diffs[pointer][1];
break;
case DIFF_DELETE:
countDelete++;
textDelete += diffs[pointer][1];
break;
case DIFF_EQUAL:
// Upon reaching an equality, check for prior redundancies.
if (countDelete >= 1 && countInsert >= 1) {
// Delete the offending records and add the merged ones.
diffs.splice(pointer - countDelete - countInsert,
countDelete + countInsert);
pointer = pointer - countDelete - countInsert;
a = this.DiffMain(textDelete, textInsert, false, deadline);
for (j = a.length - 1; j >= 0; j--) {
diffs.splice( pointer, 0, a[j] );
}
pointer = pointer + a.length;
}
countInsert = 0;
countDelete = 0;
textDelete = "";
textInsert = "";
break;
}
pointer++;
}
diffs.pop(); // Remove the dummy entry at the end.
return diffs;
};
/**
* Find the 'middle snake' of a diff, split the problem in two
* and return the recursively constructed diff.
* See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
* @param {string} text1 Old string to be diffed.
* @param {string} text2 New string to be diffed.
* @param {number} deadline Time at which to bail if not yet complete.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
* @private
*/
DiffMatchPatch.prototype.diffBisect = function(text1, text2, deadline) {
var text1Length, text2Length, maxD, vOffset, vLength,
v1, v2, x, delta, front, k1start, k1end, k2start,
k2end, k2Offset, k1Offset, x1, x2, y1, y2, d, k1, k2;
// Cache the text lengths to prevent multiple calls.
text1Length = text1.length;
text2Length = text2.length;
maxD = Math.ceil((text1Length + text2Length) / 2);
vOffset = maxD;
vLength = 2 * maxD;
v1 = new Array(vLength);
v2 = new Array(vLength);
// Setting all elements to -1 is faster in Chrome & Firefox than mixing
// integers and undefined.
for (x = 0; x < vLength; x++) {
v1[x] = -1;
v2[x] = -1;
}
v1[vOffset + 1] = 0;
v2[vOffset + 1] = 0;
delta = text1Length - text2Length;
// If the total number of characters is odd, then the front path will collide
// with the reverse path.
front = (delta % 2 !== 0);
// Offsets for start and end of k loop.
// Prevents mapping of space beyond the grid.
k1start = 0;
k1end = 0;
k2start = 0;
k2end = 0;
for (d = 0; d < maxD; d++) {
// Bail out if deadline is reached.
if ((new Date()).getTime() > deadline) {
break;
}
// Walk the front path one step.
for (k1 = -d + k1start; k1 <= d - k1end; k1 += 2) {
k1Offset = vOffset + k1;
if ( k1 === -d || ( k1 !== d && v1[ k1Offset - 1 ] < v1[ k1Offset + 1 ] ) ) {
x1 = v1[k1Offset + 1];
} else {
x1 = v1[k1Offset - 1] + 1;
}
y1 = x1 - k1;
while (x1 < text1Length && y1 < text2Length &&
text1.charAt(x1) === text2.charAt(y1)) {
x1++;
y1++;
}
v1[k1Offset] = x1;
if (x1 > text1Length) {
// Ran off the right of the graph.
k1end += 2;
} else if (y1 > text2Length) {
// Ran off the bottom of the graph.
k1start += 2;
} else if (front) {
k2Offset = vOffset + delta - k1;
if (k2Offset >= 0 && k2Offset < vLength && v2[k2Offset] !== -1) {
// Mirror x2 onto top-left coordinate system.
x2 = text1Length - v2[k2Offset];
if (x1 >= x2) {
// Overlap detected.
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
}
}
}
}
// Walk the reverse path one step.
for (k2 = -d + k2start; k2 <= d - k2end; k2 += 2) {
k2Offset = vOffset + k2;
if ( k2 === -d || (k2 !== d && v2[ k2Offset - 1 ] < v2[ k2Offset + 1 ] ) ) {
x2 = v2[k2Offset + 1];
} else {
x2 = v2[k2Offset - 1] + 1;
}
y2 = x2 - k2;
while (x2 < text1Length && y2 < text2Length &&
text1.charAt(text1Length - x2 - 1) ===
text2.charAt(text2Length - y2 - 1)) {
x2++;
y2++;
}
v2[k2Offset] = x2;
if (x2 > text1Length) {
// Ran off the left of the graph.
k2end += 2;
} else if (y2 > text2Length) {
// Ran off the top of the graph.
k2start += 2;
} else if (!front) {
k1Offset = vOffset + delta - k2;
if (k1Offset >= 0 && k1Offset < vLength && v1[k1Offset] !== -1) {
x1 = v1[k1Offset];
y1 = vOffset + x1 - k1Offset;
// Mirror x2 onto top-left coordinate system.
x2 = text1Length - x2;
if (x1 >= x2) {
// Overlap detected.
return this.diffBisectSplit(text1, text2, x1, y1, deadline);
}
}
}
}
}
// Diff took too long and hit the deadline or
// number of diffs equals number of characters, no commonality at all.
return [
[ DIFF_DELETE, text1 ],
[ DIFF_INSERT, text2 ]
];
};
/**
* Given the location of the 'middle snake', split the diff in two parts
* and recurse.
* @param {string} text1 Old string to be diffed.
* @param {string} text2 New string to be diffed.
* @param {number} x Index of split point in text1.
* @param {number} y Index of split point in text2.
* @param {number} deadline Time at which to bail if not yet complete.
* @return {!Array.<!DiffMatchPatch.Diff>} Array of diff tuples.
* @private
*/
DiffMatchPatch.prototype.diffBisectSplit = function( text1, text2, x, y, deadline ) {
var text1a, text1b, text2a, text2b, diffs, diffsb;
text1a = text1.substring(0, x);
text2a = text2.substring(0, y);
text1b = text1.substring(x);
text2b = text2.substring(y);
// Compute both diffs serially.
diffs = this.DiffMain(text1a, text2a, false, deadline);
diffsb = this.DiffMain(text1b, text2b, false, deadline);
return diffs.concat(diffsb);
};
/**
* Reduce the number of edits by eliminating semantically trivial equalities.
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
*/
DiffMatchPatch.prototype.diffCleanupSemantic = function(diffs) {
var changes, equalities, equalitiesLength, lastequality,
pointer, lengthInsertions2, lengthDeletions2, lengthInsertions1,
lengthDeletions1, deletion, insertion, overlapLength1, overlapLength2;
changes = false;
equalities = []; // Stack of indices where equalities are found.
equalitiesLength = 0; // Keeping our own length var is faster in JS.
/** @type {?string} */
lastequality = null;
// Always equal to diffs[equalities[equalitiesLength - 1]][1]
pointer = 0; // Index of current position.
// Number of characters that changed prior to the equality.
lengthInsertions1 = 0;
lengthDeletions1 = 0;
// Number of characters that changed after the equality.
lengthInsertions2 = 0;
lengthDeletions2 = 0;
while (pointer < diffs.length) {
if (diffs[pointer][0] === DIFF_EQUAL) { // Equality found.
equalities[equalitiesLength++] = pointer;
lengthInsertions1 = lengthInsertions2;
lengthDeletions1 = lengthDeletions2;
lengthInsertions2 = 0;
lengthDeletions2 = 0;
lastequality = diffs[pointer][1];
} else { // An insertion or deletion.
if (diffs[pointer][0] === DIFF_INSERT) {
lengthInsertions2 += diffs[pointer][1].length;
} else {
lengthDeletions2 += diffs[pointer][1].length;
}
// Eliminate an equality that is smaller or equal to the edits on both
// sides of it.
if (lastequality && (lastequality.length <=
Math.max(lengthInsertions1, lengthDeletions1)) &&
(lastequality.length <= Math.max(lengthInsertions2,
lengthDeletions2))) {
// Duplicate record.
diffs.splice( equalities[ equalitiesLength - 1 ], 0, [ DIFF_DELETE, lastequality ] );
// Change second copy to insert.
diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT;
// Throw away the equality we just deleted.
equalitiesLength--;
// Throw away the previous equality (it needs to be reevaluated).
equalitiesLength--;
pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
lengthInsertions1 = 0; // Reset the counters.
lengthDeletions1 = 0;
lengthInsertions2 = 0;
lengthDeletions2 = 0;
lastequality = null;
changes = true;
}
}
pointer++;
}
// Normalize the diff.
if (changes) {
this.diffCleanupMerge(diffs);
}
// Find any overlaps between deletions and insertions.
// e.g: <del>abcxxx</del><ins>xxxdef</ins>
// -> <del>abc</del>xxx<ins>def</ins>
// e.g: <del>xxxabc</del><ins>defxxx</ins>
// -> <ins>def</ins>xxx<del>abc</del>
// Only extract an overlap if it is as big as the edit ahead or behind it.
pointer = 1;
while (pointer < diffs.length) {
if (diffs[pointer - 1][0] === DIFF_DELETE &&
diffs[pointer][0] === DIFF_INSERT) {
deletion = diffs[pointer - 1][1];
insertion = diffs[pointer][1];
overlapLength1 = this.diffCommonOverlap(deletion, insertion);
overlapLength2 = this.diffCommonOverlap(insertion, deletion);
if (overlapLength1 >= overlapLength2) {
if (overlapLength1 >= deletion.length / 2 ||
overlapLength1 >= insertion.length / 2) {
// Overlap found. Insert an equality and trim the surrounding edits.
diffs.splice( pointer, 0, [ DIFF_EQUAL, insertion.substring( 0, overlapLength1 ) ] );
diffs[pointer - 1][1] =
deletion.substring(0, deletion.length - overlapLength1);
diffs[pointer + 1][1] = insertion.substring(overlapLength1);
pointer++;
}
} else {
if (overlapLength2 >= deletion.length / 2 ||
overlapLength2 >= insertion.length / 2) {
// Reverse overlap found.
// Insert an equality and swap and trim the surrounding edits.
diffs.splice( pointer, 0, [ DIFF_EQUAL, deletion.substring( 0, overlapLength2 ) ] );
diffs[pointer - 1][0] = DIFF_INSERT;
diffs[pointer - 1][1] =
insertion.substring(0, insertion.length - overlapLength2);
diffs[pointer + 1][0] = DIFF_DELETE;
diffs[pointer + 1][1] =
deletion.substring(overlapLength2);
pointer++;
}
}
pointer++;
}
pointer++;
}
};
/**
* Determine if the suffix of one string is the prefix of another.
* @param {string} text1 First string.
* @param {string} text2 Second string.
* @return {number} The number of characters common to the end of the first
* string and the start of the second string.
* @private
*/
DiffMatchPatch.prototype.diffCommonOverlap = function(text1, text2) {
var text1Length, text2Length, textLength,
best, length, pattern, found;
// Cache the text lengths to prevent multiple calls.
text1Length = text1.length;
text2Length = text2.length;
// Eliminate the null case.
if (text1Length === 0 || text2Length === 0) {
return 0;
}
// Truncate the longer string.
if (text1Length > text2Length) {
text1 = text1.substring(text1Length - text2Length);
} else if (text1Length < text2Length) {
text2 = text2.substring(0, text1Length);
}
textLength = Math.min(text1Length, text2Length);
// Quick check for the worst case.
if (text1 === text2) {
return textLength;
}
// Start by looking for a single character match
// and increase length until no match is found.
// Performance analysis: http://neil.fraser.name/news/2010/11/04/
best = 0;
length = 1;
while (true) {
pattern = text1.substring(textLength - length);
found = text2.indexOf(pattern);
if (found === -1) {
return best;
}
length += found;
if (found === 0 || text1.substring(textLength - length) ===
text2.substring(0, length)) {
best = length;
length++;
}
}
};
/**
* Split two texts into an array of strings. Reduce the texts to a string of
* hashes where each Unicode character represents one line.
* @param {string} text1 First string.
* @param {string} text2 Second string.
* @return {{chars1: string, chars2: string, lineArray: !Array.<string>}}
* An object containing the encoded text1, the encoded text2 and
* the array of unique strings.
* The zeroth element of the array of unique strings is intentionally blank.
* @private
*/
DiffMatchPatch.prototype.diffLinesToChars = function(text1, text2) {
var lineArray, lineHash, chars1, chars2;
lineArray = []; // e.g. lineArray[4] === 'Hello\n'
lineHash = {}; // e.g. lineHash['Hello\n'] === 4
// '\x00' is a valid character, but various debuggers don't like it.
// So we'll insert a junk entry to avoid generating a null character.
lineArray[0] = "";
/**
* Split a text into an array of strings. Reduce the texts to a string of
* hashes where each Unicode character represents one line.
* Modifies linearray and linehash through being a closure.
* @param {string} text String to encode.
* @return {string} Encoded string.
* @private
*/
function diffLinesToCharsMunge(text) {
var chars, lineStart, lineEnd, lineArrayLength, line;
chars = "";
// Walk the text, pulling out a substring for each line.
// text.split('\n') would would temporarily double our memory footprint.
// Modifying text would create many large strings to garbage collect.
lineStart = 0;
lineEnd = -1;
// Keeping our own length variable is faster than looking it up.
lineArrayLength = lineArray.length;
while (lineEnd < text.length - 1) {
lineEnd = text.indexOf("\n", lineStart);
if (lineEnd === -1) {
lineEnd = text.length - 1;
}
line = text.substring(lineStart, lineEnd + 1);
lineStart = lineEnd + 1;
if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) :
(lineHash[line] !== undefined)) {
chars += String.fromCharCode( lineHash[ line ] );
} else {
chars += String.fromCharCode(lineArrayLength);
lineHash[line] = lineArrayLength;
lineArray[lineArrayLength++] = line;
}
}
return chars;
}
chars1 = diffLinesToCharsMunge(text1);
chars2 = diffLinesToCharsMunge(text2);
return {
chars1: chars1,
chars2: chars2,
lineArray: lineArray
};
};
/**
* Rehydrate the text in a diff from a string of line hashes to real lines of
* text.
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
* @param {!Array.<string>} lineArray Array of unique strings.
* @private
*/
DiffMatchPatch.prototype.diffCharsToLines = function( diffs, lineArray ) {
var x, chars, text, y;
for ( x = 0; x < diffs.length; x++ ) {
chars = diffs[x][1];
text = [];
for ( y = 0; y < chars.length; y++ ) {
text[y] = lineArray[chars.charCodeAt(y)];
}
diffs[x][1] = text.join("");
}
};
/**
* Reorder and merge like edit sections. Merge equalities.
* Any edit section can move as long as it doesn't cross an equality.
* @param {!Array.<!DiffMatchPatch.Diff>} diffs Array of diff tuples.
*/
DiffMatchPatch.prototype.diffCleanupMerge = function(diffs) {
var pointer, countDelete, countInsert, textInsert, textDelete,
commonlength, changes;
diffs.push( [ DIFF_EQUAL, "" ] ); // Add a dummy entry at the end.
pointer = 0;
countDelete = 0;
countInsert = 0;
textDelete = "";
textInsert = "";
commonlength;
while (pointer < diffs.length) {
switch ( diffs[ pointer ][ 0 ] ) {
case DIFF_INSERT:
countInsert++;
textInsert += diffs[pointer][1];
pointer++;
break;
case DIFF_DELETE:
countDelete++;
textDelete += diffs[pointer][1];
pointer++;
break;
case DIFF_EQUAL:
// Upon reaching an equality, check for prior redundancies.
if (countDelete + countInsert > 1) {
if (countDelete !== 0 && countInsert !== 0) {
// Factor out any common prefixies.
commonlength = this.diffCommonPrefix(textInsert, textDelete);
if (commonlength !== 0) {
if ((pointer - countDelete - countInsert) > 0 &&
diffs[pointer - countDelete - countInsert - 1][0] ===
DIFF_EQUAL) {
diffs[pointer - countDelete - countInsert - 1][1] +=
textInsert.substring(0, commonlength);
} else {
diffs.splice( 0, 0, [ DIFF_EQUAL,
textInsert.substring( 0, commonlength )
] );
pointer++;
}
textInsert = textInsert.substring(commonlength);
textDelete = textDelete.substring(commonlength);
}
// Factor out any common suffixies.
commonlength = this.diffCommonSuffix(textInsert, textDelete);
if (commonlength !== 0) {
diffs[pointer][1] = textInsert.substring(textInsert.length -
commonlength) + diffs[pointer][1];
textInsert = textInsert.substring(0, textInsert.length -
commonlength);
textDelete = textDelete.substring(0, textDelete.length -
commonlength);
}
}
// Delete the offending records and add the merged ones.
if (countDelete === 0) {
diffs.splice( pointer - countInsert,
countDelete + countInsert, [ DIFF_INSERT, textInsert ] );
} else if (countInsert === 0) {
diffs.splice( pointer - countDelete,
countDelete + countInsert, [ DIFF_DELETE, textDelete ] );
} else {
diffs.splice( pointer - countDelete - countInsert,
countDelete + countInsert, [ DIFF_DELETE, textDelete ], [ DIFF_INSERT, textInsert ] );
}
pointer = pointer - countDelete - countInsert +
(countDelete ? 1 : 0) + (countInsert ? 1 : 0) + 1;
} else if (pointer !== 0 && diffs[pointer - 1][0] === DIFF_EQUAL) {
// Merge this equality with the previous one.
diffs[pointer - 1][1] += diffs[pointer][1];
diffs.splice(pointer, 1);
} else {
pointer++;
}
countInsert = 0;
countDelete = 0;
textDelete = "";
textInsert = "";
break;
}
}
if (diffs[diffs.length - 1][1] === "") {
diffs.pop(); // Remove the dummy entry at the end.
}
// Second pass: look for single edits surrounded on both sides by equalities
// which can be shifted sideways to eliminate an equality.
// e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
changes = false;
pointer = 1;
// Intentionally ignore the first and last element (don't need checking).
while (pointer < diffs.length - 1) {
if (diffs[pointer - 1][0] === DIFF_EQUAL &&
diffs[pointer + 1][0] === DIFF_EQUAL) {
// This is a single edit surrounded by equalities.
if ( diffs[ pointer ][ 1 ].substring( diffs[ pointer ][ 1 ].length -
diffs[ pointer - 1 ][ 1 ].length ) === diffs[ pointer - 1 ][ 1 ] ) {
// Shift the edit over the previous equality.
diffs[pointer][1] = diffs[pointer - 1][1] +
diffs[pointer][1].substring(0, diffs[pointer][1].length -
diffs[pointer - 1][1].length);
diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
diffs.splice(pointer - 1, 1);
changes = true;
} else if ( diffs[ pointer ][ 1 ].substring( 0, diffs[ pointer + 1 ][ 1 ].length ) ===
diffs[ pointer + 1 ][ 1 ] ) {
// Shift the edit over the next equality.
diffs[pointer - 1][1] += diffs[pointer + 1][1];
diffs[pointer][1] =
diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
diffs[pointer + 1][1];
diffs.splice(pointer + 1, 1);
changes = true;
}
}
pointer++;
}
// If shifts were made, the diff needs reordering and another shift sweep.
if (changes) {
this.diffCleanupMerge(diffs);
}
};
return function(o, n) {
var diff, output, text;
diff = new DiffMatchPatch();
output = diff.DiffMain(o, n);
//console.log(output);
diff.diffCleanupEfficiency(output);
text = diff.diffPrettyHtml(output);
return text;
};
}()); }());
// jscs:enable // jscs:enable
...@@ -2256,7 +3176,14 @@ function addEvent( elem, type, fn ) { ...@@ -2256,7 +3176,14 @@ function addEvent( elem, type, fn ) {
} else if ( elem.attachEvent ) { } else if ( elem.attachEvent ) {
// support: IE <9 // support: IE <9
elem.attachEvent( "on" + type, fn ); elem.attachEvent( "on" + type, function() {
var event = window.event;
if ( !event.target ) {
event.target = event.srcElement || document;
}
fn.call( elem, event );
});
} }
} }
...@@ -2427,12 +3354,16 @@ function setUrl( params ) { ...@@ -2427,12 +3354,16 @@ function setUrl( params ) {
} }
function applyUrlParams() { function applyUrlParams() {
var selectBox = id( "qunit-modulefilter" ), var selectedModule,
selection = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value ), modulesList = id( "qunit-modulefilter" ),
filter = id( "qunit-filter-input" ).value; filter = id( "qunit-filter-input" ).value;
selectedModule = modulesList ?
decodeURIComponent( modulesList.options[ modulesList.selectedIndex ].value ) :
undefined;
window.location = setUrl({ window.location = setUrl({
module: ( selection === "" ) ? undefined : selection, module: ( selectedModule === "" ) ? undefined : selectedModule,
filter: ( filter === "" ) ? undefined : filter, filter: ( filter === "" ) ? undefined : filter,
// Remove testId filter // Remove testId filter
...@@ -2588,9 +3519,14 @@ function storeFixture() { ...@@ -2588,9 +3519,14 @@ function storeFixture() {
function appendUserAgent() { function appendUserAgent() {
var userAgent = id( "qunit-userAgent" ); var userAgent = id( "qunit-userAgent" );
if ( userAgent ) { if ( userAgent ) {
userAgent.innerHTML = ""; userAgent.innerHTML = "";
userAgent.appendChild( document.createTextNode( navigator.userAgent ) ); userAgent.appendChild(
document.createTextNode(
"QUnit " + QUnit.version + "; " + navigator.userAgent
)
);
} }
} }
...@@ -2733,7 +3669,7 @@ function getNameHtml( name, module ) { ...@@ -2733,7 +3669,7 @@ function getNameHtml( name, module ) {
} }
QUnit.testStart(function( details ) { QUnit.testStart(function( details ) {
var running, testBlock; var running, testBlock, bad;
testBlock = id( "qunit-test-output-" + details.testId ); testBlock = id( "qunit-test-output-" + details.testId );
if ( testBlock ) { if ( testBlock ) {
...@@ -2746,7 +3682,13 @@ QUnit.testStart(function( details ) { ...@@ -2746,7 +3682,13 @@ QUnit.testStart(function( details ) {
running = id( "qunit-testresult" ); running = id( "qunit-testresult" );
if ( running ) { if ( running ) {
running.innerHTML = "Running: <br />" + getNameHtml( details.name, details.module ); bad = QUnit.config.reorder && defined.sessionStorage &&
+sessionStorage.getItem( "qunit-test-" + details.module + "-" + details.name );
running.innerHTML = ( bad ?
"Rerunning previously failed test: <br />" :
"Running: <br />" ) +
getNameHtml( details.name, details.module );
} }
}); });
...@@ -2779,6 +3721,15 @@ QUnit.log(function( details ) { ...@@ -2779,6 +3721,15 @@ QUnit.log(function( details ) {
actual + "</pre></td></tr>" + actual + "</pre></td></tr>" +
"<tr class='test-diff'><th>Diff: </th><td><pre>" + "<tr class='test-diff'><th>Diff: </th><td><pre>" +
QUnit.diff( expected, actual ) + "</pre></td></tr>"; QUnit.diff( expected, actual ) + "</pre></td></tr>";
} else {
if ( expected.indexOf( "[object Array]" ) !== -1 ||
expected.indexOf( "[object Object]" ) !== -1 ) {
message += "<tr class='test-message'><th>Message: </th><td>" +
"Diff suppressed as the depth of object is more than current max depth (" +
QUnit.config.maxDepth + ").<p>Hint: Use <code>QUnit.dump.maxDepth</code> to " +
" run with a higher max depth or <a href='" + setUrl({ maxDepth: -1 }) + "'>" +
"Rerun</a> without max depth.</p></td></tr>";
}
} }
if ( details.source ) { if ( details.source ) {
...@@ -2863,13 +3814,15 @@ QUnit.testDone(function( details ) { ...@@ -2863,13 +3814,15 @@ QUnit.testDone(function( details ) {
} }
}); });
if ( !defined.document || document.readyState === "complete" ) { if ( defined.document ) {
if ( document.readyState === "complete" ) {
QUnit.load();
} else {
addEvent( window, "load", QUnit.load );
}
} else {
config.pageLoaded = true; config.pageLoaded = true;
config.autorun = true; config.autorun = true;
} }
if ( defined.document ) {
addEvent( window, "load", QUnit.load );
}
})(); })();
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment