forked from extern/egroupware
Merge pull request #59 from EGroupware/feature/server-side-diff
Move diff to server
This commit is contained in:
commit
a5ca61e7db
@ -13,8 +13,7 @@
|
||||
/*egw:uses
|
||||
/vendor/bower-asset/jquery/dist/jquery.js;
|
||||
/vendor/bower-asset/jquery-ui/jquery-ui.js;
|
||||
lib/jsdifflib/difflib;
|
||||
lib/jsdifflib/diffview;
|
||||
/vendor/bower-asset/diff2html/dist/diff2html.min.js;
|
||||
et2_core_valueWidget;
|
||||
*/
|
||||
|
||||
@ -31,6 +30,11 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe
|
||||
}
|
||||
},
|
||||
|
||||
diff_options: {
|
||||
"inputFormat":"diff",
|
||||
"matching": "words"
|
||||
},
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -41,65 +45,74 @@ var et2_diff = (function(){ "use strict"; return et2_valueWidget.extend([et2_IDe
|
||||
this.mini = true;
|
||||
|
||||
// included via etemplate2.css
|
||||
//this.egw().includeCSS('etemplate/js/lib/jsdifflib/diffview.css');
|
||||
//this.egw().includeCSS('../../../vendor/bower-asset/dist/dist2html.css');
|
||||
this.div = document.createElement("div");
|
||||
jQuery(this.div).addClass('diff');
|
||||
jQuery(this.div).addClass('et2_diff');
|
||||
},
|
||||
|
||||
set_value: function(value) {
|
||||
jQuery(this.div).empty();
|
||||
if(typeof value['old'] == 'string' && typeof value['new'] == 'string') {
|
||||
// Build diff
|
||||
var old_text = difflib.stringAsLines(value['old'].toString());
|
||||
var new_text = difflib.stringAsLines(value['new'].toString());
|
||||
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: 'auto',
|
||||
autoResize: true,
|
||||
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
|
||||
dialog_div.dialog("destroy");
|
||||
self.minify(this);
|
||||
if(typeof value == 'string') {
|
||||
|
||||
// Put it back where it came from, or et2 will error when clear() is called
|
||||
diff.prependTo(div);
|
||||
}
|
||||
});
|
||||
});
|
||||
// Diff2Html likes to have files, we don't have them
|
||||
if(value.indexOf('---') !== 0)
|
||||
{
|
||||
value = "--- diff\n+++ diff\n"+value;
|
||||
}
|
||||
var diff = Diff2Html.getPrettyHtml(value, this.diff_options);
|
||||
// var ui = new Diff2HtmlUI({diff: diff});
|
||||
// ui.draw(jQuery(this.div), this.diff_options);
|
||||
jQuery(this.div).append(diff);
|
||||
}
|
||||
else if(typeof value != 'object')
|
||||
{
|
||||
jQuery(this.div).append(value);
|
||||
}
|
||||
this.check_mini();
|
||||
},
|
||||
|
||||
check_mini: function() {
|
||||
if(!this.mini)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var view = jQuery(this.div).children();
|
||||
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, label: self.options.label}, 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: e.data.label,
|
||||
width: 'auto',
|
||||
autoResize: true,
|
||||
modal: true,
|
||||
buttons: [{text: self.egw().lang('ok'), click: function() {jQuery(this).dialog("close");}}],
|
||||
open: function() {
|
||||
if(jQuery(this).parent().height() > jQuery(window).height())
|
||||
{
|
||||
jQuery(this).height(jQuery(window).height() *0.7);
|
||||
}
|
||||
jQuery(this).addClass('et2_diff').dialog({position: "center"});
|
||||
},
|
||||
close: function(event, ui) {
|
||||
// Need to destroy the dialog, etemplate widget needs divs back where they were
|
||||
dialog_div.dialog("destroy");
|
||||
self.minify(this);
|
||||
|
||||
// Put it back where it came from, or et2 will error when clear() is called
|
||||
diff.prependTo(div);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
set_label: function(_label) {
|
||||
this.options.label = _label;
|
||||
|
||||
|
@ -473,7 +473,9 @@ var et2_historylog = (function(){ "use strict"; return et2_valueWidget.extend([e
|
||||
widget = undefined;
|
||||
value = _data['share_email'];
|
||||
}
|
||||
if(typeof widget == 'undefined' && typeof self.fields[_data.status] != 'undefined')
|
||||
// Get widget from list, unless it needs a diff widget
|
||||
if(typeof widget == 'undefined' && typeof self.fields[_data.status] != 'undefined' && (i < self.NEW_VALUE ||
|
||||
i >= self.NEW_VALUE &&!self._needsDiffWidget(_data['status'], _data[self.columns[self.OLD_VALUE].id])))
|
||||
{
|
||||
widget = self.fields[_data.status].widget;
|
||||
if(!widget._children.length)
|
||||
@ -500,20 +502,12 @@ var et2_historylog = (function(){ "use strict"; return et2_valueWidget.extend([e
|
||||
{
|
||||
// Large text value - span both columns, and show a nice diff
|
||||
var jthis = jQuery(this);
|
||||
if(i == self.NEW_VALUE)
|
||||
if(i === self.NEW_VALUE)
|
||||
{
|
||||
// Diff widget
|
||||
widget = self.diff.widget;
|
||||
nodes = self.diff.nodes.clone();
|
||||
|
||||
if (typeof _data[self.columns[self.NEW_VALUE].id] == "string")
|
||||
{
|
||||
value = _data[self.columns[i].id] = {
|
||||
'old': _data[self.columns[i+1].id],
|
||||
'new': value
|
||||
};
|
||||
}
|
||||
|
||||
// Skip column 4
|
||||
jthis.parents("td").attr("colspan", 2)
|
||||
.css("border-right", "none");
|
||||
@ -571,7 +565,7 @@ var et2_historylog = (function(){ "use strict"; return et2_valueWidget.extend([e
|
||||
this.egw().debug("warn", "Crazy diff value", value);
|
||||
return false;
|
||||
}
|
||||
return columnName == 'note' || columnName == 'description' || (value && (value.length > 50 || value.match(/\n/g)));
|
||||
return value=== '***diff***';
|
||||
},
|
||||
|
||||
resize: function (_height)
|
||||
@ -594,6 +588,12 @@ var et2_historylog = (function(){ "use strict"; return et2_valueWidget.extend([e
|
||||
this.div.trigger('resize.' +this.options.value.app + this.options.value.id);
|
||||
}
|
||||
}
|
||||
// Resize diff widgets to match new space
|
||||
if(this.dataview)
|
||||
{
|
||||
var columns = this.dataview.getColumnMgr().columnWidths;
|
||||
jQuery('.et2_diff', this.div).parent().width(columns[this.NEW_VALUE] + columns[this.OLD_VALUE]);
|
||||
}
|
||||
}
|
||||
});}).call(this);
|
||||
et2_register_widget(et2_historylog, ['historylog']);
|
||||
|
@ -11,7 +11,7 @@
|
||||
/* Basic information about this app */
|
||||
$setup_info['api']['name'] = 'api';
|
||||
$setup_info['api']['title'] = 'EGroupware API';
|
||||
$setup_info['api']['version'] = '17.1.005';
|
||||
$setup_info['api']['version'] = '17.1.006';
|
||||
$setup_info['api']['versions']['current_header'] = '1.29';
|
||||
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
|
||||
$setup_info['api']['versions']['maintenance_release'] = '17.1.20190222';
|
||||
|
@ -516,3 +516,57 @@ function api_upgrade17_1_004()
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '17.1.005';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Store multiline history content as diff
|
||||
*
|
||||
* Benchmark on Ralf's Laptop (OS X)
|
||||
* - auto/Native: 66k rows in 15mins
|
||||
*
|
||||
* @return string new version
|
||||
*/
|
||||
function api_upgrade17_1_005()
|
||||
{
|
||||
$renderer = new Horde_Text_Diff_Renderer_Unified();
|
||||
$start = microtime(true);
|
||||
$junk_size = 200; // 2*200*160KB = 64MB
|
||||
$total = $saved = 0;
|
||||
do {
|
||||
$n = 0;
|
||||
foreach($GLOBALS['egw_setup']->db->select('egw_history_log', 'history_id,history_new_value,history_old_value', array(
|
||||
'history_old_value != '.$GLOBALS['egw_setup']->db->quote(Api\Storage\Tracking::DIFF_MARKER),
|
||||
// if one is empty, no need to store diff
|
||||
"(LENGTH(history_new_value) > 0 AND LENGTH(history_old_value) > 0)",
|
||||
"(history_status LIKE '%description' OR history_status='De' OR history_status='note'".
|
||||
" OR LENGTH(history_new_value) > 200 OR LENGTH(history_old_value) > 200)",
|
||||
), __LINE__, __FILE__, 0, 'ORDER BY history_id', false, $junk_size) as $row)
|
||||
{
|
||||
// use OS diff command for big texts, if available
|
||||
$diff = new Horde_Text_Diff('auto', array(
|
||||
explode("\n", $row['history_old_value']),
|
||||
explode("\n", $row['history_new_value']),
|
||||
));
|
||||
$diff_str = $renderer->render($diff);
|
||||
|
||||
$saved += strlen($row['history_old_value'])+strlen($row['history_new_value'])
|
||||
-strlen($diff_str)-strlen(Api\Storage\Tracking::DIFF_MARKER);
|
||||
|
||||
$GLOBALS['egw_setup']->db->update('egw_history_log', array(
|
||||
'history_new_value' => $diff_str,
|
||||
'history_old_value' => Api\Storage\Tracking::DIFF_MARKER,
|
||||
), array(
|
||||
'history_id' => $row['history_id'],
|
||||
), __LINE__, __FILE__);
|
||||
|
||||
$n++;
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
while($n == $junk_size);
|
||||
$saved = number_format($saved/(1024.0*1024.0), 1);
|
||||
$time = number_format((microtime(true)-$start)/60, 1);
|
||||
echo "$total history-records converted in $time minutes to diff with a total of $saved MB saved\n";
|
||||
|
||||
return $GLOBALS['setup_info']['api']['currentver'] = '17.1.006';
|
||||
}
|
||||
|
||||
|
@ -260,6 +260,17 @@ class History
|
||||
$row[$field] = explode(Tracking::ONE2N_SEPERATOR,$row[$field]);
|
||||
}
|
||||
}
|
||||
if ($row['history_old_value'] !== Tracking::DIFF_MARKER && (
|
||||
static::needs_diff($row['history_status'], $row['history_old_value']) ||
|
||||
static::needs_diff($row['history_status'], $row['history_old_value'])
|
||||
))
|
||||
{
|
||||
// Larger text stored with full old / new value - calculate diff and just send that
|
||||
$diff = new \Horde_Text_Diff('auto', array(explode("\n",$row['history_old_value']), explode("\n",$row['history_new_value'])));
|
||||
$renderer = new \Horde_Text_Diff_Renderer_Unified();
|
||||
$row['history_new_value'] = $renderer->render($diff);
|
||||
$row['history_old_value'] = Tracking::DIFF_MARKER;
|
||||
}
|
||||
// Get information needed for proper display
|
||||
if($row['history_appname'] == 'filemanager')
|
||||
{
|
||||
@ -297,4 +308,12 @@ class History
|
||||
|
||||
return $total;
|
||||
}
|
||||
|
||||
protected static function needs_diff($name, $value)
|
||||
{
|
||||
return $name == 'note' || // Addressbook
|
||||
strpos($name, 'description') !== false || // Calendar, Records, Timesheet, ProjectManager, Resources
|
||||
$name == 'De' || // Tracker, InfoLog
|
||||
($value && (strlen($value) > 200 || strstr($value, "\n") !== FALSE));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ use EGroupware\Api;
|
||||
// explicitly reference classes still in phpgwapi or otherwise outside api
|
||||
use notifications;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for trackering:
|
||||
* - logging all modifications of an entry
|
||||
@ -189,6 +190,12 @@ abstract class Tracking
|
||||
*/
|
||||
const ONE2N_SEPERATOR = '~|~';
|
||||
|
||||
/**
|
||||
* Marker for change stored as unified diff, not old/new value
|
||||
* Diff is in the new value, marker in old value
|
||||
*/
|
||||
const DIFF_MARKER = '***diff***';
|
||||
|
||||
/**
|
||||
* Config name for custom notification message
|
||||
*/
|
||||
@ -451,6 +458,19 @@ abstract class Tracking
|
||||
$this->historylog->add($name,$data[$this->id_field],$added[$i],$removed[$i]);
|
||||
}
|
||||
}
|
||||
else if (is_string($data[$name]) && is_string($old[$name]) && (
|
||||
strpos($data[$name], PHP_EOL) !== FALSE || strpos($old[$name], PHP_EOL) !== FALSE))
|
||||
{
|
||||
// Multiline string, just store diff
|
||||
$diff = new \Horde_Text_Diff('auto', array(explode("\n",$old[$name]), explode("\n",$data[$name])));
|
||||
$renderer = new \Horde_Text_Diff_Renderer_Unified();
|
||||
$this->historylog->add(
|
||||
$status,
|
||||
$data[$this->id_field],
|
||||
$renderer->render($diff),
|
||||
self::DIFF_MARKER
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
//error_log(__METHOD__.__LINE__.' IDField:'.array2string($this->id_field).' ->'.$data[$this->id_field].' New:'.$data[$name].' Old:'.$old[$name]);
|
||||
|
@ -13,7 +13,7 @@
|
||||
/*@import url("../../js/jquery/blueimp/css/blueimp-gallery.min.css");*/
|
||||
/*@import url("../../js/dhtmlxtree/codebase/dhtmlxtree.css");*/
|
||||
/*@import url("../../js/egw_action/test/skins/dhtmlxmenu_egw.css");*/
|
||||
/*@import url("../../js/etemplate/lib/jsdifflib/diffview.css");*/
|
||||
/*@import url("../../../vendor/bower-asset/diff2html/dist/diff2html.css");*/
|
||||
/*@import url("../../../vendor/bower-asset/cropper/dist/cropper.min.css");*/
|
||||
/*@import url("css/flags.css");*/
|
||||
/*@import url("css/htmlarea.css");*/
|
||||
@ -567,14 +567,48 @@ for printing
|
||||
/**
|
||||
* Diff widget
|
||||
*/
|
||||
.diff thead,
|
||||
.author {
|
||||
div.et2_diff {
|
||||
width: 100%;
|
||||
}
|
||||
.et2_diff thead,
|
||||
.author,
|
||||
.d2h-file-header,
|
||||
.d2h-file-info,
|
||||
.d2h-info,
|
||||
.et2_diff .d2h-cntx
|
||||
{
|
||||
display: none;
|
||||
}
|
||||
.diff .ui-icon {
|
||||
.d2h-file-diff {
|
||||
white-space:normal;
|
||||
}
|
||||
.et2_diff .d2h-file-diff {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.ui-widget-content .d2h-code-line-ctn {
|
||||
white-space:normal;
|
||||
}
|
||||
.ui-widget-content .d2h-file-diff {
|
||||
overflow-x: visible;
|
||||
overflow-y: visible;
|
||||
}
|
||||
.et2_diff .ui-icon {
|
||||
margin-top: -16px;
|
||||
float: right;
|
||||
}
|
||||
.et2_diff .d2h-del, .et2_diff.d2h-del.d2h-change, .et2_diff .d2h-file-diff .d2h-del.d2h-change {
|
||||
background-color: #ffeef0;
|
||||
}
|
||||
.et2_diff .d2h-code-line del, .et2_diff .d2h-code-side-line del {
|
||||
background-color: #fdb8c0;
|
||||
}
|
||||
.et2_diff .d2h-ins, .et2_diff.d2h-ins.d2h-change, .et2_diff .d2h-file-diff .d2h-ins.d2h-change {
|
||||
background-color: #e6ffed;
|
||||
}
|
||||
.et2_diff .d2h-code-line ins, .et2_diff .d2h-code-side-line ins {
|
||||
background-color: #acf2bd;
|
||||
}
|
||||
|
||||
/** Display a loading icon **/
|
||||
.loading {
|
||||
background-position: center;
|
||||
|
@ -86,7 +86,9 @@
|
||||
"egroupware/tracker": "self.version",
|
||||
"egroupware/z-push-dev": "self.version",
|
||||
"egroupware/activesync": "self.version",
|
||||
"egroupware/adodb-php": "self.version"
|
||||
"egroupware/adodb-php": "self.version",
|
||||
"pear-pear.horde.org/Horde_Text_Diff": "^2.2",
|
||||
"bower-asset/diff2html": "^2.7"
|
||||
},
|
||||
"require-dev": {
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user