mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-22 16:03:47 +01:00
Historylog widget for et2
This commit is contained in:
parent
88df7e232c
commit
5647df9636
55
etemplate/inc/class.etemplate_widget_historylog.inc.php
Normal file
55
etemplate/inc/class.etemplate_widget_historylog.inc.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/**
|
||||
* eGroupWare eTemplate2 - History log server-side
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright 2012 Nathan Gray
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
/**
|
||||
* eTemplate history log widget displays a list of changes to the current record.
|
||||
* The widget is encapsulated, and only needs the record's ID, and a map of
|
||||
* fields:widgets for display
|
||||
*/
|
||||
|
||||
class etemplate_widget_historylog extends etemplate_widget
|
||||
{
|
||||
|
||||
/**
|
||||
* Fill type options in self::$request->sel_options to be used on the client
|
||||
*
|
||||
* @param string $cname
|
||||
*/
|
||||
public function beforeSendToClient($cname)
|
||||
{
|
||||
$form_name = self::form_name($cname, $this->id);
|
||||
|
||||
foreach(self::$request->content[$form_name]['status-widgets'] as $key => $type)
|
||||
{
|
||||
if(!is_array($type))
|
||||
{
|
||||
list($basetype) = explode('-',$type);
|
||||
$widget = @self::factory($basetype, '<?xml version="1.0"?><'.$basetype.' type="'.$type.'"/>', $key);
|
||||
$widget->id = $key;
|
||||
$widget->attrs['type'] = $type;
|
||||
$widget->type = $type;
|
||||
if(method_exists($widget, 'beforeSendToClient'))
|
||||
{
|
||||
$widget->beforeSendToClient($cname);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!is_array(self::$request->sel_options[$key])) self::$request->sel_options[$key] = array();
|
||||
self::$request->sel_options[$key] += $type;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
165
etemplate/js/et2_widget_diff.js
Normal file
165
etemplate/js/et2_widget_diff.js
Normal file
@ -0,0 +1,165 @@
|
||||
/**
|
||||
* eGroupWare eTemplate2 - JS Diff object
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright Nathan Gray 2012
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*egw:uses
|
||||
jquery.jquery;
|
||||
jquery.jquery-ui;
|
||||
lib/jsdifflib/difflib;
|
||||
lib/jsdifflib/diffview;
|
||||
et2_core_valueWidget;
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class that displays the diff between two [text] values
|
||||
*/
|
||||
var et2_diff = et2_valueWidget.extend([et2_IDetachedDOM], {
|
||||
|
||||
attributes: {
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.mini = true;
|
||||
|
||||
this.egw().includeCSS('etemplate/js/lib/jsdifflib/diffview.css');
|
||||
this.div = document.createElement("div");
|
||||
jQuery(this.div).addClass('diff');
|
||||
},
|
||||
|
||||
set_value: function(value) {
|
||||
jQuery(this.div).empty();
|
||||
if(value['old'] && value['new']) {
|
||||
// Build diff
|
||||
var old_text = difflib.stringAsLines(value['old']);
|
||||
var new_text = difflib.stringAsLines(value['new']);
|
||||
var sm = new difflib.SequenceMatcher(old_text, new_text);
|
||||
var opcodes = sm.get_opcodes();
|
||||
var view = diffview.buildView({
|
||||
baseTextLines: old_text,
|
||||
newTextLines: new_text,
|
||||
opcodes: opcodes,
|
||||
baseTextName: '',//this.egw().lang('Old value'),
|
||||
newTextName: '',//this.egw().lang('New value'),
|
||||
viewType: 1
|
||||
});
|
||||
jQuery(this.div).append(view);
|
||||
if(this.mini) {
|
||||
view = jQuery(view);
|
||||
this.minify(view);
|
||||
var self = this;
|
||||
jQuery('<span class="ui-icon ui-icon-circle-plus"> </span>')
|
||||
.appendTo(self.div)
|
||||
.css("cursor", "pointer")
|
||||
.click({diff: view, div: self.div}, function(e) {
|
||||
var diff = e.data.diff;
|
||||
var div = e.data.div;
|
||||
self.un_minify(diff);
|
||||
var dialog_div = jQuery('<div>')
|
||||
.append(diff);
|
||||
dialog_div.dialog({
|
||||
title: self.options.label,
|
||||
width: 400,
|
||||
height: 300,
|
||||
modal: true,
|
||||
buttons: [{text: self.egw().lang('ok'), click: function() {jQuery(this).dialog("close");}}],
|
||||
close: function(event, ui) {
|
||||
// Need to destroy the dialog, etemplate widget needs divs back where they were
|
||||
diff.dialog("destroy");
|
||||
self.minify(this);
|
||||
|
||||
// Put it back where it came from, or et2 will error when clear() is called
|
||||
diff.prependTo(div);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else if(typeof value != 'object')
|
||||
{
|
||||
jQuery(this.div).append(value);
|
||||
}
|
||||
},
|
||||
|
||||
set_label: function(_label) {
|
||||
this.options.label = _label;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Make the diff into a mini-diff
|
||||
*/
|
||||
minify: function(view) {
|
||||
view = jQuery(view)
|
||||
.addClass('mini')
|
||||
// Dialog changes these, if resized
|
||||
.width('100%').css('height', 'inherit')
|
||||
.show();
|
||||
jQuery('th', view).hide();
|
||||
jQuery('td.equal',view).hide()
|
||||
.prevAll().hide();
|
||||
},
|
||||
|
||||
un_minify: function(view) {
|
||||
jQuery(view).removeClass('mini').show();
|
||||
jQuery('th',view).show();
|
||||
jQuery('td.equal',view).show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Code for implementing et2_IDetachedDOM
|
||||
* Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
|
||||
*/
|
||||
|
||||
/**
|
||||
* Build a list of attributes which can be set when working in the
|
||||
* "detached" mode in the _attrs array which is provided
|
||||
* by the calling code.
|
||||
*/
|
||||
getDetachedAttributes: function(_attrs) {
|
||||
_attrs.push("value", "label");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
|
||||
* passed to the "setDetachedAttributes" function in the same order.
|
||||
*/
|
||||
getDetachedNodes: function() {
|
||||
return [this.div];
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the given associative attribute->value array and applies the
|
||||
* attributes to the given DOM-Node.
|
||||
*
|
||||
* @param _nodes is an array of nodes which has to be in the same order as
|
||||
* the nodes returned by "getDetachedNodes"
|
||||
* @param _values is an associative array which contains a subset of attributes
|
||||
* returned by the "getDetachedAttributes" function and sets them to the
|
||||
* given values.
|
||||
*/
|
||||
setDetachedAttributes: function(_nodes, _values) {
|
||||
this.div = _nodes[0];
|
||||
if(typeof _values['label'] != 'undefined')
|
||||
{
|
||||
this.set_label(_values['label']);
|
||||
}
|
||||
if(typeof _values['value'] != 'undefined')
|
||||
{
|
||||
this.set_value(_values['value']);
|
||||
}
|
||||
}
|
||||
});
|
||||
et2_register_widget(et2_diff, ["diff"]);
|
305
etemplate/js/et2_widget_historylog.js
Normal file
305
etemplate/js/et2_widget_historylog.js
Normal file
@ -0,0 +1,305 @@
|
||||
/**
|
||||
* eGroupWare eTemplate2 - JS History log
|
||||
*
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @package etemplate
|
||||
* @subpackage api
|
||||
* @link http://www.egroupware.org
|
||||
* @author Nathan Gray
|
||||
* @copyright 2012 Nathan Gray
|
||||
* @version $Id$
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*egw:uses
|
||||
jquery.jquery;
|
||||
jquery.jquery-ui;
|
||||
et2_core_valueWidget;
|
||||
|
||||
// Include the grid classes
|
||||
et2_dataview;
|
||||
*/
|
||||
|
||||
/**
|
||||
* eTemplate history log widget displays a list of changes to the current record.
|
||||
* The widget is encapsulated, and only needs the record's ID, and a map of
|
||||
* fields:widgets for display
|
||||
*/
|
||||
|
||||
var et2_historylog = et2_valueWidget.extend([et2_IDataProvider],{
|
||||
columns: [
|
||||
{'id': 'timestamp', caption: 'Date', 'width': '120px', widget_type: 'date-time'},
|
||||
{'id': 'owner', caption: 'User', 'width': '150px', widget_type: 'select-account'},
|
||||
{'id': 'status', caption: 'Changed', 'width': '120px', widget_type: 'select'},
|
||||
{'id': 'new_value', caption: 'New Value'},
|
||||
{'id': 'old_value', caption: 'Old Value'}
|
||||
],
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.div = $j(document.createElement("div"))
|
||||
.addClass("et2_historylog");
|
||||
|
||||
this.innerDiv = $j(document.createElement("div"))
|
||||
.appendTo(this.div);
|
||||
|
||||
this._filters = {
|
||||
record_id: this.options.value.id,
|
||||
appname: this.options.value.app,
|
||||
get_rows: 'historylog::get_rows'
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
doLoadingFinished: function() {
|
||||
this._super.apply(this, arguments);
|
||||
// Find the tab widget, if there is one
|
||||
var tabs = this;
|
||||
var count = 0;
|
||||
do {
|
||||
tabs = tabs._parent;
|
||||
} while (tabs != this.getRoot() && tabs._type != 'tabbox');
|
||||
if(tabs != this.getRoot())
|
||||
{
|
||||
// Find the tab index
|
||||
for(var i = 0; i < tabs.tabData.length; i++)
|
||||
{
|
||||
// Find the tab
|
||||
if(tabs.tabData[i].contentDiv.has(this.div).length)
|
||||
{
|
||||
// Bind the action to when the tab is selected
|
||||
var handler = function(e) {
|
||||
e.data.div.unbind("click.history");
|
||||
e.data.history.finishInit();
|
||||
e.data.history.dynheight.update(function(_w, _h) {
|
||||
e.data.history.dataview.resize(_w, _h);
|
||||
});
|
||||
};
|
||||
tabs.tabData[i].flagDiv.bind("click.history",{"history": this, div: tabs.tabData[i].flagDiv}, handler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.finishInit();
|
||||
}
|
||||
},
|
||||
|
||||
finishInit: function() {
|
||||
|
||||
// Create the dynheight component which dynamically scales the inner
|
||||
// container.
|
||||
this.dynheight = new et2_dynheight(this.egw().window,
|
||||
this.innerDiv, 250
|
||||
);
|
||||
|
||||
// Create the outer grid container
|
||||
this.dataview = new et2_dataview(this.innerDiv, this.egw());
|
||||
this.dataview.setColumns(jQuery.extend(true, [],this.columns));
|
||||
|
||||
// Create widgets for columns that stay the same, and set up varying widgets
|
||||
this.createWidgets();
|
||||
|
||||
// Create the gridview controller
|
||||
var linkCallback = function() {};
|
||||
this.controller = new et2_dataview_controller(null, this.dataview.grid,
|
||||
this, this.rowCallback, linkCallback, this,
|
||||
null
|
||||
);
|
||||
|
||||
// Trigger the initial update
|
||||
this.controller.update();
|
||||
|
||||
// Write something inside the column headers
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
$j(this.dataview.getHeaderContainerNode(i)).text(this.columns[i].caption);
|
||||
}
|
||||
|
||||
// Register a resize callback
|
||||
var self = this;
|
||||
$j(window).resize(function() {
|
||||
self.dynheight.update(function(_w, _h) {
|
||||
self.dataview.resize(_w, _h);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroys all
|
||||
*/
|
||||
destroy: function() {
|
||||
// Free the widgets
|
||||
for(var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
if(this.columns[i].widget) this.columns[i].widget.destroy();
|
||||
}
|
||||
for(var key in this.fields)
|
||||
{
|
||||
this.fields[key].widget.destroy();
|
||||
}
|
||||
this.diff.widget.destroy();
|
||||
|
||||
// Free the grid components
|
||||
this.dataview.free();
|
||||
this.rowProvider.free();
|
||||
this.controller.free();
|
||||
this.dynheight.free();
|
||||
|
||||
this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
createWidgets: function() {
|
||||
|
||||
// Constant widgets - first 3 columns
|
||||
for(var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
if(this.columns[i].widget_type)
|
||||
{
|
||||
var attrs = {'readonly': true, 'id': this.columns[i].id};
|
||||
this.columns[i].widget = et2_createWidget(this.columns[i].widget_type, attrs, this);
|
||||
this.columns[i].widget.transformAttributes(attrs);
|
||||
this.columns[i].nodes = $j(this.columns[i].widget.getDetachedNodes());
|
||||
}
|
||||
}
|
||||
|
||||
// Per-field widgets - new value & old value
|
||||
this.fields = {};
|
||||
for(var key in this.options.value['status-widgets'])
|
||||
{
|
||||
var field = this.options.value['status-widgets'][key];
|
||||
var attrs = {'readonly': true, 'id': key};
|
||||
if(typeof field == 'object') attrs['select-options'] = field;
|
||||
|
||||
var widget = et2_createWidget(typeof field == 'string' ? field : 'select', attrs, this);
|
||||
widget.transformAttributes(attrs);
|
||||
|
||||
this.fields[key] = {
|
||||
attrs: attrs,
|
||||
widget: widget,
|
||||
nodes: jQuery(widget.getDetachedNodes())
|
||||
};
|
||||
}
|
||||
|
||||
// Widget for text diffs
|
||||
var diff = et2_createWidget('diff', {}, this);
|
||||
this.diff = {
|
||||
widget: diff,
|
||||
nodes: jQuery(diff.getDetachedNodes())
|
||||
};
|
||||
},
|
||||
|
||||
getDOMNode: function(_sender) {
|
||||
if (_sender == this)
|
||||
{
|
||||
return this.div[0];
|
||||
}
|
||||
|
||||
for (var i = 0; i < this.columns.length; i++)
|
||||
{
|
||||
if (_sender == this.columns[i].widget)
|
||||
{
|
||||
return this.dataview.getHeaderContainerNode(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
|
||||
dataFetch: function (_queriedRange, _callback, _context) {
|
||||
// Pass the fetch call to the API
|
||||
this.egw().dataFetch(
|
||||
this.getInstanceManager().etemplate_exec_id,
|
||||
_queriedRange,
|
||||
this._filters,
|
||||
this.id,
|
||||
_callback,
|
||||
_context
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
// Needed by interface
|
||||
dataRegisterUID: function (_uid, _callback, _context) {
|
||||
this.egw().dataRegisterUID(_uid, _callback, _context, this.getInstanceManager().etemplate_exec_id,
|
||||
this.id);
|
||||
},
|
||||
|
||||
dataUnregisterUID: function (_uid, _callback, _context) {
|
||||
// Needed by interface
|
||||
},
|
||||
|
||||
/**
|
||||
* The row callback gets called by the gridview controller whenever
|
||||
* the actual DOM-Nodes for a node with the given data have to be
|
||||
* created.
|
||||
*/
|
||||
rowCallback: function(_data, _row, _idx, _entry) {
|
||||
var tr = _row.getDOMNode();
|
||||
jQuery(tr).attr("valign","top");
|
||||
|
||||
var row = this.dataview.rowProvider.getPrototype("default");
|
||||
var self = this;
|
||||
$j("div", row).each(function (i) {
|
||||
var nodes = [];
|
||||
var widget = self.columns[i].widget;
|
||||
if(typeof widget == 'undefined' && typeof self.fields[_data.status] != 'undefined')
|
||||
{
|
||||
nodes = self.fields[_data.status].nodes.clone();
|
||||
widget = self.fields[_data.status].widget;
|
||||
}
|
||||
else if (widget)
|
||||
{
|
||||
nodes = self.columns[i].nodes.clone();
|
||||
}
|
||||
else if (self._needsDiffWidget(_data['status'], _data[self.columns[i].id]))
|
||||
{
|
||||
var jthis = jQuery(this);
|
||||
if(i == 3)
|
||||
{
|
||||
// DIff widget
|
||||
widget = self.diff.widget;
|
||||
nodes = self.diff.nodes.clone();
|
||||
|
||||
_data[self.columns[i].id] = {
|
||||
'old': _data[self.columns[i+1].id],
|
||||
'new': _data[self.columns[i].id]
|
||||
};
|
||||
|
||||
// Skip column 4
|
||||
jthis.parents("td").attr("colspan", 2)
|
||||
.css("border-right", "none");
|
||||
jthis.css("width", "100%");
|
||||
|
||||
if(widget) widget.setDetachedAttributes(nodes, {
|
||||
value:_data[self.columns[i].id],
|
||||
label: jthis.parents("td").prev().text()
|
||||
});
|
||||
}
|
||||
else if (i == 4)
|
||||
{
|
||||
// Skip column 4
|
||||
jthis.parents("td").remove();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes = '<span>'+_data[self.columns[i].id] + '</span>';
|
||||
}
|
||||
if(widget) widget.setDetachedAttributes(nodes, {value:_data[self.columns[i].id]});
|
||||
$j(this).append(nodes);
|
||||
});
|
||||
$j(tr).append(row.children());
|
||||
|
||||
return tr;
|
||||
},
|
||||
|
||||
/**
|
||||
* How to tell if the row needs a diff widget or not
|
||||
*/
|
||||
_needsDiffWidget: function(columnName, value) {
|
||||
return columnName == 'note' || columnName == 'description' || value && value.length > 100
|
||||
},
|
||||
});
|
||||
et2_register_widget(et2_historylog, ['historylog']);
|
@ -27,10 +27,12 @@
|
||||
et2_widget_checkbox;
|
||||
et2_widget_radiobox;
|
||||
et2_widget_date;
|
||||
et2_widget_diff;
|
||||
et2_widget_styles;
|
||||
et2_widget_html;
|
||||
et2_widget_tabs;
|
||||
et2_widget_tree;
|
||||
et2_widget_historylog;
|
||||
et2_widget_hrule;
|
||||
et2_widget_image;
|
||||
et2_widget_file;
|
||||
|
407
etemplate/js/lib/jsdifflib/difflib.js
Executable file
407
etemplate/js/lib/jsdifflib/difflib.js
Executable file
@ -0,0 +1,407 @@
|
||||
/***
|
||||
This is part of jsdifflib v1.0. <http://snowtide.com/jsdifflib>
|
||||
|
||||
Copyright (c) 2007, Snowtide Informatics Systems, Inc.
|
||||
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 the Snowtide Informatics Systems 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
|
||||
***/
|
||||
/* Author: Chas Emerick <cemerick@snowtide.com> */
|
||||
__whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true};
|
||||
|
||||
difflib = {
|
||||
defaultJunkFunction: function (c) {
|
||||
return __whitespace.hasOwnProperty(c);
|
||||
},
|
||||
|
||||
stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); },
|
||||
|
||||
stringAsLines: function (str) {
|
||||
var lfpos = str.indexOf("\n");
|
||||
var crpos = str.indexOf("\r");
|
||||
var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r";
|
||||
|
||||
var lines = str.split(linebreak);
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = difflib.stripLinebreaks(lines[i]);
|
||||
}
|
||||
|
||||
return lines;
|
||||
},
|
||||
|
||||
// iteration-based reduce implementation
|
||||
__reduce: function (func, list, initial) {
|
||||
if (initial != null) {
|
||||
var value = initial;
|
||||
var idx = 0;
|
||||
} else if (list) {
|
||||
var value = list[0];
|
||||
var idx = 1;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (; idx < list.length; idx++) {
|
||||
value = func(value, list[idx]);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
// comparison function for sorting lists of numeric tuples
|
||||
__ntuplecomp: function (a, b) {
|
||||
var mlen = Math.max(a.length, b.length);
|
||||
for (var i = 0; i < mlen; i++) {
|
||||
if (a[i] < b[i]) return -1;
|
||||
if (a[i] > b[i]) return 1;
|
||||
}
|
||||
|
||||
return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1);
|
||||
},
|
||||
|
||||
__calculate_ratio: function (matches, length) {
|
||||
return length ? 2.0 * matches / length : 1.0;
|
||||
},
|
||||
|
||||
// returns a function that returns true if a key passed to the returned function
|
||||
// is in the dict (js object) provided to this function; replaces being able to
|
||||
// carry around dict.has_key in python...
|
||||
__isindict: function (dict) {
|
||||
return function (key) { return dict.hasOwnProperty(key); };
|
||||
},
|
||||
|
||||
// replacement for python's dict.get function -- need easy default values
|
||||
__dictget: function (dict, key, defaultValue) {
|
||||
return dict.hasOwnProperty(key) ? dict[key] : defaultValue;
|
||||
},
|
||||
|
||||
SequenceMatcher: function (a, b, isjunk) {
|
||||
this.set_seqs = function (a, b) {
|
||||
this.set_seq1(a);
|
||||
this.set_seq2(b);
|
||||
}
|
||||
|
||||
this.set_seq1 = function (a) {
|
||||
if (a == this.a) return;
|
||||
this.a = a;
|
||||
this.matching_blocks = this.opcodes = null;
|
||||
}
|
||||
|
||||
this.set_seq2 = function (b) {
|
||||
if (b == this.b) return;
|
||||
this.b = b;
|
||||
this.matching_blocks = this.opcodes = this.fullbcount = null;
|
||||
this.__chain_b();
|
||||
}
|
||||
|
||||
this.__chain_b = function () {
|
||||
var b = this.b;
|
||||
var n = b.length;
|
||||
var b2j = this.b2j = {};
|
||||
var populardict = {};
|
||||
for (var i = 0; i < b.length; i++) {
|
||||
var elt = b[i];
|
||||
if (b2j.hasOwnProperty(elt)) {
|
||||
var indices = b2j[elt];
|
||||
if (n >= 200 && indices.length * 100 > n) {
|
||||
populardict[elt] = 1;
|
||||
delete b2j[elt];
|
||||
} else {
|
||||
indices.push(i);
|
||||
}
|
||||
} else {
|
||||
b2j[elt] = [i];
|
||||
}
|
||||
}
|
||||
|
||||
for (var elt in populardict) {
|
||||
if (populardict.hasOwnProperty(elt)) {
|
||||
delete b2j[elt];
|
||||
}
|
||||
}
|
||||
|
||||
var isjunk = this.isjunk;
|
||||
var junkdict = {};
|
||||
if (isjunk) {
|
||||
for (var elt in populardict) {
|
||||
if (populardict.hasOwnProperty(elt) && isjunk(elt)) {
|
||||
junkdict[elt] = 1;
|
||||
delete populardict[elt];
|
||||
}
|
||||
}
|
||||
for (var elt in b2j) {
|
||||
if (b2j.hasOwnProperty(elt) && isjunk(elt)) {
|
||||
junkdict[elt] = 1;
|
||||
delete b2j[elt];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.isbjunk = difflib.__isindict(junkdict);
|
||||
this.isbpopular = difflib.__isindict(populardict);
|
||||
}
|
||||
|
||||
this.find_longest_match = function (alo, ahi, blo, bhi) {
|
||||
var a = this.a;
|
||||
var b = this.b;
|
||||
var b2j = this.b2j;
|
||||
var isbjunk = this.isbjunk;
|
||||
var besti = alo;
|
||||
var bestj = blo;
|
||||
var bestsize = 0;
|
||||
var j = null;
|
||||
|
||||
var j2len = {};
|
||||
var nothing = [];
|
||||
for (var i = alo; i < ahi; i++) {
|
||||
var newj2len = {};
|
||||
var jdict = difflib.__dictget(b2j, a[i], nothing);
|
||||
for (var jkey in jdict) {
|
||||
if (jdict.hasOwnProperty(jkey)) {
|
||||
j = jdict[jkey];
|
||||
if (j < blo) continue;
|
||||
if (j >= bhi) break;
|
||||
newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1;
|
||||
if (k > bestsize) {
|
||||
besti = i - k + 1;
|
||||
bestj = j - k + 1;
|
||||
bestsize = k;
|
||||
}
|
||||
}
|
||||
}
|
||||
j2len = newj2len;
|
||||
}
|
||||
|
||||
while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
|
||||
besti--;
|
||||
bestj--;
|
||||
bestsize++;
|
||||
}
|
||||
|
||||
while (besti + bestsize < ahi && bestj + bestsize < bhi &&
|
||||
!isbjunk(b[bestj + bestsize]) &&
|
||||
a[besti + bestsize] == b[bestj + bestsize]) {
|
||||
bestsize++;
|
||||
}
|
||||
|
||||
while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
|
||||
besti--;
|
||||
bestj--;
|
||||
bestsize++;
|
||||
}
|
||||
|
||||
while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) &&
|
||||
a[besti + bestsize] == b[bestj + bestsize]) {
|
||||
bestsize++;
|
||||
}
|
||||
|
||||
return [besti, bestj, bestsize];
|
||||
}
|
||||
|
||||
this.get_matching_blocks = function () {
|
||||
if (this.matching_blocks != null) return this.matching_blocks;
|
||||
var la = this.a.length;
|
||||
var lb = this.b.length;
|
||||
|
||||
var queue = [[0, la, 0, lb]];
|
||||
var matching_blocks = [];
|
||||
var alo, ahi, blo, bhi, qi, i, j, k, x;
|
||||
while (queue.length) {
|
||||
qi = queue.pop();
|
||||
alo = qi[0];
|
||||
ahi = qi[1];
|
||||
blo = qi[2];
|
||||
bhi = qi[3];
|
||||
x = this.find_longest_match(alo, ahi, blo, bhi);
|
||||
i = x[0];
|
||||
j = x[1];
|
||||
k = x[2];
|
||||
|
||||
if (k) {
|
||||
matching_blocks.push(x);
|
||||
if (alo < i && blo < j)
|
||||
queue.push([alo, i, blo, j]);
|
||||
if (i+k < ahi && j+k < bhi)
|
||||
queue.push([i + k, ahi, j + k, bhi]);
|
||||
}
|
||||
}
|
||||
|
||||
matching_blocks.sort(difflib.__ntuplecomp);
|
||||
|
||||
var i1 = j1 = k1 = block = 0;
|
||||
var non_adjacent = [];
|
||||
for (var idx in matching_blocks) {
|
||||
if (matching_blocks.hasOwnProperty(idx)) {
|
||||
block = matching_blocks[idx];
|
||||
i2 = block[0];
|
||||
j2 = block[1];
|
||||
k2 = block[2];
|
||||
if (i1 + k1 == i2 && j1 + k1 == j2) {
|
||||
k1 += k2;
|
||||
} else {
|
||||
if (k1) non_adjacent.push([i1, j1, k1]);
|
||||
i1 = i2;
|
||||
j1 = j2;
|
||||
k1 = k2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (k1) non_adjacent.push([i1, j1, k1]);
|
||||
|
||||
non_adjacent.push([la, lb, 0]);
|
||||
this.matching_blocks = non_adjacent;
|
||||
return this.matching_blocks;
|
||||
}
|
||||
|
||||
this.get_opcodes = function () {
|
||||
if (this.opcodes != null) return this.opcodes;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var answer = [];
|
||||
this.opcodes = answer;
|
||||
var block, ai, bj, size, tag;
|
||||
var blocks = this.get_matching_blocks();
|
||||
for (var idx in blocks) {
|
||||
if (blocks.hasOwnProperty(idx)) {
|
||||
block = blocks[idx];
|
||||
ai = block[0];
|
||||
bj = block[1];
|
||||
size = block[2];
|
||||
tag = '';
|
||||
if (i < ai && j < bj) {
|
||||
tag = 'replace';
|
||||
} else if (i < ai) {
|
||||
tag = 'delete';
|
||||
} else if (j < bj) {
|
||||
tag = 'insert';
|
||||
}
|
||||
if (tag) answer.push([tag, i, ai, j, bj]);
|
||||
i = ai + size;
|
||||
j = bj + size;
|
||||
|
||||
if (size) answer.push(['equal', ai, i, bj, j]);
|
||||
}
|
||||
}
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
// this is a generator function in the python lib, which of course is not supported in javascript
|
||||
// the reimplementation builds up the grouped opcodes into a list in their entirety and returns that.
|
||||
this.get_grouped_opcodes = function (n) {
|
||||
if (!n) n = 3;
|
||||
var codes = this.get_opcodes();
|
||||
if (!codes) codes = [["equal", 0, 1, 0, 1]];
|
||||
var code, tag, i1, i2, j1, j2;
|
||||
if (codes[0][0] == 'equal') {
|
||||
code = codes[0];
|
||||
tag = code[0];
|
||||
i1 = code[1];
|
||||
i2 = code[2];
|
||||
j1 = code[3];
|
||||
j2 = code[4];
|
||||
codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2];
|
||||
}
|
||||
if (codes[codes.length - 1][0] == 'equal') {
|
||||
code = codes[codes.length - 1];
|
||||
tag = code[0];
|
||||
i1 = code[1];
|
||||
i2 = code[2];
|
||||
j1 = code[3];
|
||||
j2 = code[4];
|
||||
codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)];
|
||||
}
|
||||
|
||||
var nn = n + n;
|
||||
var groups = [];
|
||||
for (var idx in codes) {
|
||||
if (codes.hasOwnProperty(idx)) {
|
||||
code = codes[idx];
|
||||
tag = code[0];
|
||||
i1 = code[1];
|
||||
i2 = code[2];
|
||||
j1 = code[3];
|
||||
j2 = code[4];
|
||||
if (tag == 'equal' && i2 - i1 > nn) {
|
||||
groups.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]);
|
||||
i1 = Math.max(i1, i2-n);
|
||||
j1 = Math.max(j1, j2-n);
|
||||
}
|
||||
|
||||
groups.push([tag, i1, i2, j1, j2]);
|
||||
}
|
||||
}
|
||||
|
||||
if (groups && groups[groups.length - 1][0] == 'equal') groups.pop();
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
this.ratio = function () {
|
||||
matches = difflib.__reduce(
|
||||
function (sum, triple) { return sum + triple[triple.length - 1]; },
|
||||
this.get_matching_blocks(), 0);
|
||||
return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
|
||||
}
|
||||
|
||||
this.quick_ratio = function () {
|
||||
var fullbcount, elt;
|
||||
if (this.fullbcount == null) {
|
||||
this.fullbcount = fullbcount = {};
|
||||
for (var i = 0; i < this.b.length; i++) {
|
||||
elt = this.b[i];
|
||||
fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1;
|
||||
}
|
||||
}
|
||||
fullbcount = this.fullbcount;
|
||||
|
||||
var avail = {};
|
||||
var availhas = difflib.__isindict(avail);
|
||||
var matches = numb = 0;
|
||||
for (var i = 0; i < this.a.length; i++) {
|
||||
elt = this.a[i];
|
||||
if (availhas(elt)) {
|
||||
numb = avail[elt];
|
||||
} else {
|
||||
numb = difflib.__dictget(fullbcount, elt, 0);
|
||||
}
|
||||
avail[elt] = numb - 1;
|
||||
if (numb > 0) matches++;
|
||||
}
|
||||
|
||||
return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
|
||||
}
|
||||
|
||||
this.real_quick_ratio = function () {
|
||||
var la = this.a.length;
|
||||
var lb = this.b.length;
|
||||
return _calculate_ratio(Math.min(la, lb), la + lb);
|
||||
}
|
||||
|
||||
this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction;
|
||||
this.a = this.b = null;
|
||||
this.set_seqs(a, b);
|
||||
}
|
||||
}
|
83
etemplate/js/lib/jsdifflib/diffview.css
Normal file
83
etemplate/js/lib/jsdifflib/diffview.css
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
This is part of jsdifflib v1.0. <http://github.com/cemerick/jsdifflib>
|
||||
|
||||
Copyright 2007 - 2011 Chas Emerick <cemerick@snowtide.com>. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Chas Emerick ``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 Chas Emerick OR
|
||||
CONTRIBUTORS 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.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of Chas Emerick.
|
||||
*/
|
||||
table.diff {
|
||||
border-collapse:collapse;
|
||||
border:1px solid darkgray;
|
||||
white-space:pre-wrap
|
||||
}
|
||||
table.diff tbody {
|
||||
font-family:Courier, monospace
|
||||
}
|
||||
table.diff tbody th {
|
||||
font-family:verdana,arial,'Bitstream Vera Sans',helvetica,sans-serif;
|
||||
background:#EED;
|
||||
font-size:11px;
|
||||
font-weight:normal;
|
||||
border:1px solid #BBC;
|
||||
color:#886;
|
||||
padding:.3em .5em .1em 2em;
|
||||
text-align:right;
|
||||
vertical-align:top
|
||||
}
|
||||
table.diff thead {
|
||||
border-bottom:1px solid #BBC;
|
||||
background:#EFEFEF;
|
||||
font-family:Verdana
|
||||
}
|
||||
table.diff thead th.texttitle {
|
||||
text-align:left
|
||||
}
|
||||
table.diff tbody td {
|
||||
padding:0px .4em;
|
||||
padding-top:.4em;
|
||||
vertical-align:top;
|
||||
}
|
||||
table.diff .empty {
|
||||
background-color:#DDD;
|
||||
}
|
||||
table.diff .replace {
|
||||
background-color:#FD8
|
||||
}
|
||||
table.diff .delete {
|
||||
background-color:#E99;
|
||||
}
|
||||
table.diff .skip {
|
||||
background-color:#EFEFEF;
|
||||
border:1px solid #AAA;
|
||||
border-right:1px solid #BBC;
|
||||
}
|
||||
table.diff .insert {
|
||||
background-color:#9E9
|
||||
}
|
||||
table.diff th.author {
|
||||
text-align:right;
|
||||
border-top:1px solid #BBC;
|
||||
background:#EFEFEF
|
||||
}
|
197
etemplate/js/lib/jsdifflib/diffview.js
Normal file
197
etemplate/js/lib/jsdifflib/diffview.js
Normal file
@ -0,0 +1,197 @@
|
||||
/*
|
||||
This is part of jsdifflib v1.0. <http://github.com/cemerick/jsdifflib>
|
||||
|
||||
Copyright 2007 - 2011 Chas Emerick <cemerick@snowtide.com>. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. 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.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY Chas Emerick ``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 Chas Emerick OR
|
||||
CONTRIBUTORS 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.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of Chas Emerick.
|
||||
*/
|
||||
diffview = {
|
||||
/**
|
||||
* Builds and returns a visual diff view. The single parameter, `params', should contain
|
||||
* the following values:
|
||||
*
|
||||
* - baseTextLines: the array of strings that was used as the base text input to SequenceMatcher
|
||||
* - newTextLines: the array of strings that was used as the new text input to SequenceMatcher
|
||||
* - opcodes: the array of arrays returned by SequenceMatcher.get_opcodes()
|
||||
* - baseTextName: the title to be displayed above the base text listing in the diff view; defaults
|
||||
* to "Base Text"
|
||||
* - newTextName: the title to be displayed above the new text listing in the diff view; defaults
|
||||
* to "New Text"
|
||||
* - contextSize: the number of lines of context to show around differences; by default, all lines
|
||||
* are shown
|
||||
* - viewType: if 0, a side-by-side diff view is generated (default); if 1, an inline diff view is
|
||||
* generated
|
||||
*/
|
||||
buildView: function (params) {
|
||||
var baseTextLines = params.baseTextLines;
|
||||
var newTextLines = params.newTextLines;
|
||||
var opcodes = params.opcodes;
|
||||
var baseTextName = params.baseTextName ? params.baseTextName : "Base Text";
|
||||
var newTextName = params.newTextName ? params.newTextName : "New Text";
|
||||
var contextSize = params.contextSize;
|
||||
var inline = (params.viewType == 0 || params.viewType == 1) ? params.viewType : 0;
|
||||
|
||||
if (baseTextLines == null)
|
||||
throw "Cannot build diff view; baseTextLines is not defined.";
|
||||
if (newTextLines == null)
|
||||
throw "Cannot build diff view; newTextLines is not defined.";
|
||||
if (!opcodes)
|
||||
throw "Canno build diff view; opcodes is not defined.";
|
||||
|
||||
function celt (name, clazz) {
|
||||
var e = document.createElement(name);
|
||||
e.className = clazz;
|
||||
return e;
|
||||
}
|
||||
|
||||
function telt (name, text) {
|
||||
var e = document.createElement(name);
|
||||
e.appendChild(document.createTextNode(text));
|
||||
return e;
|
||||
}
|
||||
|
||||
function ctelt (name, clazz, text) {
|
||||
var e = document.createElement(name);
|
||||
e.className = clazz;
|
||||
e.appendChild(document.createTextNode(text));
|
||||
return e;
|
||||
}
|
||||
|
||||
var tdata = document.createElement("thead");
|
||||
var node = document.createElement("tr");
|
||||
tdata.appendChild(node);
|
||||
if (inline) {
|
||||
node.appendChild(document.createElement("th"));
|
||||
node.appendChild(document.createElement("th"));
|
||||
node.appendChild(ctelt("th", "texttitle", baseTextName + " vs. " + newTextName));
|
||||
} else {
|
||||
node.appendChild(document.createElement("th"));
|
||||
node.appendChild(ctelt("th", "texttitle", baseTextName));
|
||||
node.appendChild(document.createElement("th"));
|
||||
node.appendChild(ctelt("th", "texttitle", newTextName));
|
||||
}
|
||||
tdata = [tdata];
|
||||
|
||||
var rows = [];
|
||||
var node2;
|
||||
|
||||
/**
|
||||
* Adds two cells to the given row; if the given row corresponds to a real
|
||||
* line number (based on the line index tidx and the endpoint of the
|
||||
* range in question tend), then the cells will contain the line number
|
||||
* and the line of text from textLines at position tidx (with the class of
|
||||
* the second cell set to the name of the change represented), and tidx + 1 will
|
||||
* be returned. Otherwise, tidx is returned, and two empty cells are added
|
||||
* to the given row.
|
||||
*/
|
||||
function addCells (row, tidx, tend, textLines, change) {
|
||||
if (tidx < tend) {
|
||||
row.appendChild(telt("th", (tidx + 1).toString()));
|
||||
row.appendChild(ctelt("td", change, textLines[tidx].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
|
||||
return tidx + 1;
|
||||
} else {
|
||||
row.appendChild(document.createElement("th"));
|
||||
row.appendChild(celt("td", "empty"));
|
||||
return tidx;
|
||||
}
|
||||
}
|
||||
|
||||
function addCellsInline (row, tidx, tidx2, textLines, change) {
|
||||
row.appendChild(telt("th", tidx == null ? "" : (tidx + 1).toString()));
|
||||
row.appendChild(telt("th", tidx2 == null ? "" : (tidx2 + 1).toString()));
|
||||
row.appendChild(ctelt("td", change, textLines[tidx != null ? tidx : tidx2].replace(/\t/g, "\u00a0\u00a0\u00a0\u00a0")));
|
||||
}
|
||||
|
||||
for (var idx = 0; idx < opcodes.length; idx++) {
|
||||
code = opcodes[idx];
|
||||
change = code[0];
|
||||
var b = code[1];
|
||||
var be = code[2];
|
||||
var n = code[3];
|
||||
var ne = code[4];
|
||||
var rowcnt = Math.max(be - b, ne - n);
|
||||
var toprows = [];
|
||||
var botrows = [];
|
||||
for (var i = 0; i < rowcnt; i++) {
|
||||
// jump ahead if we've alredy provided leading context or if this is the first range
|
||||
if (contextSize && opcodes.length > 1 && ((idx > 0 && i == contextSize) || (idx == 0 && i == 0)) && change=="equal") {
|
||||
var jump = rowcnt - ((idx == 0 ? 1 : 2) * contextSize);
|
||||
if (jump > 1) {
|
||||
toprows.push(node = document.createElement("tr"));
|
||||
|
||||
b += jump;
|
||||
n += jump;
|
||||
i += jump - 1;
|
||||
node.appendChild(telt("th", "..."));
|
||||
if (!inline) node.appendChild(ctelt("td", "skip", ""));
|
||||
node.appendChild(telt("th", "..."));
|
||||
node.appendChild(ctelt("td", "skip", ""));
|
||||
|
||||
// skip last lines if they're all equal
|
||||
if (idx + 1 == opcodes.length) {
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toprows.push(node = document.createElement("tr"));
|
||||
if (inline) {
|
||||
if (change == "insert") {
|
||||
addCellsInline(node, null, n++, newTextLines, change);
|
||||
} else if (change == "replace") {
|
||||
botrows.push(node2 = document.createElement("tr"));
|
||||
if (b < be) addCellsInline(node, b++, null, baseTextLines, "delete");
|
||||
if (n < ne) addCellsInline(node2, null, n++, newTextLines, "insert");
|
||||
} else if (change == "delete") {
|
||||
addCellsInline(node, b++, null, baseTextLines, change);
|
||||
} else {
|
||||
// equal
|
||||
addCellsInline(node, b++, n++, baseTextLines, change);
|
||||
}
|
||||
} else {
|
||||
b = addCells(node, b, be, baseTextLines, change);
|
||||
n = addCells(node, n, ne, newTextLines, change);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < toprows.length; i++) rows.push(toprows[i]);
|
||||
for (var i = 0; i < botrows.length; i++) rows.push(botrows[i]);
|
||||
}
|
||||
|
||||
rows.push(node = ctelt("th", "author", "diff view generated by "));
|
||||
node.setAttribute("colspan", inline ? 3 : 4);
|
||||
node.appendChild(node2 = telt("a", "jsdifflib"));
|
||||
node2.setAttribute("href", "http://github.com/cemerick/jsdifflib");
|
||||
|
||||
tdata.push(node = document.createElement("tbody"));
|
||||
for (var idx in rows) node.appendChild(rows[idx]);
|
||||
|
||||
node = celt("table", "diff" + (inline ? " inlinediff" : ""));
|
||||
for (var idx in tdata) node.appendChild(tdata[idx]);
|
||||
return node;
|
||||
}
|
||||
}
|
@ -262,6 +262,17 @@ span.et2_date span {
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff widget
|
||||
*/
|
||||
.diff thead,.author {
|
||||
display: none;
|
||||
}
|
||||
.diff .ui-icon {
|
||||
margin-top: -16px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
/** Display a loading icon **/
|
||||
.loading {
|
||||
background-position: center;
|
||||
|
Loading…
Reference in New Issue
Block a user