diff --git a/editor/editor.css b/editor/editor.css index 0f08598..6b1b85b 100755 --- a/editor/editor.css +++ b/editor/editor.css @@ -27,7 +27,9 @@ body { margin: 0; } .icon-swap { background-image: url(images/swap.png); } .icon-arrow-right { background-image: url(images/arrow-right.png); } .icon-arrow-right-v { background-image: url(images/arrow-right-v.png); } +.icon-arrow-right-vv { background-image: url(images/arrow-right-vv.png); } .icon-arrow-left-v { background-image: url(images/arrow-left-v.png); } +.icon-arrow-left-vv { background-image: url(images/arrow-left-vv.png); } .icon-arrow-up { background-image: url(images/arrow-up-v.png); } .icon-arrow-down { background-image: url(images/arrow-down-v.png); } .icon-x-mark { background-image: url(images/x-mark.png); } diff --git a/editor/editor.js b/editor/editor.js index 453abbf..41cf6c2 100755 --- a/editor/editor.js +++ b/editor/editor.js @@ -249,6 +249,9 @@ $(document).ready(function() { handleFind(ed.find('#mergely-editor-lhs')); } else if (id == 'edit-left-merge-right') { + ed.mergely('mergeCurrentDiff', 'rhs'); + } + else if (id == 'edit-left-merge-right-file') { ed.mergely('merge', 'rhs'); } else if ([ @@ -272,6 +275,9 @@ $(document).ready(function() { handleFind(ed.find('#mergely-editor-rhs')); } else if (id == 'edit-right-merge-left') { + ed.mergely('mergeCurrentDiff', 'lhs'); + } + else if (id == 'edit-right-merge-left-file') { ed.mergely('merge', 'lhs'); } else if (id == 'edit-right-clear') { @@ -286,6 +292,12 @@ $(document).ready(function() { else if (id == 'view-refresh') { ed.mergely('update'); } + else if (id == 'view-change-next') { + ed.mergely('scrollToDiff', 'next'); + } + else if (id == 'view-change-prev') { + ed.mergely('scrollToDiff', 'prev'); + } else if (id == 'view-clear') { ed.mergely('unmarkup'); } diff --git a/editor/editor.php b/editor/editor.php index 9f78257..0786222 100755 --- a/editor/editor.php +++ b/editor/editor.php @@ -1,6 +1,6 @@ Redo
  • Find
  • -
  • Merge right
  • +
  • Merge change right
  • +
  • Merge file right
  • Read only
  • Clear
  • @@ -110,7 +111,8 @@ if (isset($_GET['key'])) {
  • Redo
  • Find
  • -
  • Merge left
  • +
  • Merge change left
  • +
  • Merge file left
  • Read only
  • Clear
  • @@ -123,6 +125,9 @@ if (isset($_GET['key'])) {
  • Render diff view
  • Clear render
  • +
  • +
  • View prev change
  • +
  • View next change
  • @@ -188,8 +193,11 @@ if (isset($_GET['key'])) {
  • Import
  • Save .diff
  • -
  • Merge left
  • -
  • Merge right
  • +
  • Previous change
  • +
  • Next change
  • +
  • +
  • Merge change left
  • +
  • Merge change right
  • Swap sides
  • diff --git a/editor/images/arrow-left-vv.png b/editor/images/arrow-left-vv.png new file mode 100755 index 0000000..0e284bf Binary files /dev/null and b/editor/images/arrow-left-vv.png differ diff --git a/editor/images/arrow-right-vv.png b/editor/images/arrow-right-vv.png new file mode 100755 index 0000000..83fba47 Binary files /dev/null and b/editor/images/arrow-right-vv.png differ diff --git a/editor/lib/wicked-ui.css b/editor/lib/wicked-ui.css index ce61a05..608930d 100755 --- a/editor/lib/wicked-ui.css +++ b/editor/lib/wicked-ui.css @@ -75,7 +75,7 @@ THE SOFTWARE. .wicked-menu ul li:hover > ul { display: block; position: absolute; } /* drop menu */ -.wicked-menu ul { z-index: 7; min-width: 200px; top: 2.1em; left: -1px; } +.wicked-menu ul { z-index: 7; min-width: 210px; top: 2.1em; left: -1px; } .wicked-menu > li.hover { /* for the top-level menu, after hovering do not show background */ @@ -93,7 +93,7 @@ padding-left: 31px; background-repeat: no-repeat; } -.wicked-menu ul li:hover > ul { min-width:200px; top: 0px; left: 100%; } +.wicked-menu ul li:hover > ul { min-width: 210px; top: 0px; left: 100%; } .wicked-menu li.separator { border-top: 1px solid #e5e5e5; height: 3px; display: block; line-height: 3em; margin-top: 3px; } .wicked-menu li.separator:hover { background: transparent; cursor:default; } diff --git a/editor/lib/wicked-ui.js b/editor/lib/wicked-ui.js index c61ea49..8d89375 100755 --- a/editor/lib/wicked-ui.js +++ b/editor/lib/wicked-ui.js @@ -24,6 +24,24 @@ THE SOFTWARE. ;(function( $, window, document, undefined ){ var pluginName = 'wickedtoolbar'; + var SHIFT = new RegExp(/shift/i); + var ALT = new RegExp(/alt/i); + var CTRL = new RegExp(/ctrl/i); + var ARROW_DOWN = new RegExp(/↓/); + var ARROW_UP = new RegExp(/↑/); + var ARROW_LEFT = new RegExp(/←/); + var ARROW_RIGHT = new RegExp(/→/); + + var keys = { + shift: 16, + alt: 17, + ctrl: 18, + meta: 91, + arrow_up: 38, + arrow_down: 40, + arrow_left: 37, + arrow_right: 39 + }; var defaults = { hasIcon: function(id) { }, @@ -34,7 +52,8 @@ THE SOFTWARE. var self = this; this.element = $(element); - this.settings = $.extend({}, defaults, options) ; + this.settings = $.extend({}, defaults, options); + this.bindings = []; // for each menu item, modify and wrap in div this.element.find('li').each(function () { var tthis = $(this); @@ -53,10 +72,10 @@ THE SOFTWARE. // change:
  • Text
  • // to:
  • - // - // Text - // - //
  • + // + // Text + // + // var li = tthis; var div = $(''); div.click(function(ev) { @@ -92,18 +111,50 @@ THE SOFTWARE. if (hotkey) { tthis.removeAttr('data-hotkey'); div.append('' + hotkey + ''); + + if (!accesskey) { + // add our own handler + var parts = hotkey.split('+'); + var bind = {}; + for (var i = 0; i < parts.length; ++i) { + if (SHIFT.test(parts[i])) { + bind.shiftKey = true; + } + else if (ALT.test(parts[i])) { + bind.altKey = true; + } + else if (CTRL.test(parts[i])) { + bind.ctrlKey = true; + } + else if (ARROW_DOWN.test(parts[i])) { + bind.which = keys.arrow_down; + } + else if (ARROW_UP.test(parts[i])) { + bind.which = keys.arrow_up; + } + else if (ARROW_RIGHT.test(parts[i])) { + bind.which = keys.arrow_right; + } + else if (ARROW_LEFT.test(parts[i])) { + bind.which = keys.arrow_left; + } + } + bind.target = div; + self.bindings.push(bind); + } } } // icon - var id = tthis.attr('id'); + var id = tthis.attr('id'), icon; if (self.settings.hasIcon(id)) { span.addClass('icon'); - if (icon = self.settings.getIcon(id)) { + icon = self.settings.getIcon(id); + if (icon) { span.addClass(icon); } } - var icon = tthis.attr('data-icon'); + icon = tthis.attr('data-icon'); if (icon) { tthis.removeAttr('data-icon'); span.addClass('icon ' + icon); @@ -111,22 +162,35 @@ THE SOFTWARE. else if (icons) { span.addClass('icon'); } - li.prepend(div); }); + $(document).on('keydown', function(ev) { + for (var i = 0; i < self.bindings.length; ++i) { + var bind = self.bindings[i]; + // handle custom key events + if ((bind.shiftKey === undefined ? true : (bind.shiftKey === ev.shiftKey)) && + (bind.ctrlKey === undefined ? true : (bind.ctrlKey === ev.ctrlKey)) && + (bind.altKey === undefined ? true : (bind.altKey === ev.altKey)) && + bind.which && ev.which && (bind.which === ev.which)) { + bind.target.trigger('click'); + ev.preventDefault(); + } + } + }); } MenuBase.prototype.update = function (id) { - var li = this.element.find('#' + id); + var li = this.element.find('#' + id), icon; var span = li.find('span:first-child'); if (this.settings.hasIcon(id)) { span.removeClass(); // this could be brutally unfair span.addClass('icon'); - if (icon = this.settings.getIcon(id)) { + icon = this.settings.getIcon(id); + if (icon) { span.addClass(icon); } } - } + }; // ------------ // Menu @@ -139,6 +203,7 @@ THE SOFTWARE. Menu.prototype.constructor = function () { this.element.addClass('wicked-ui wicked-menu'); + var self = this; var dohover = function(ev) { $(this).parent().addClass('hover'); if ($(this).closest('ul').hasClass('wicked-menu')) { @@ -170,7 +235,6 @@ THE SOFTWARE. } ); }); - var self = this; this.element.find('> li > a.menu-item').hover( function() { if (!self.accessing) return; @@ -178,7 +242,7 @@ THE SOFTWARE. $.proxy(dohover, this)(); } ); - } + }; // ------------ // Toolbar @@ -195,12 +259,12 @@ THE SOFTWARE. function(){ $(this).parent().addClass('hover'); }, function(){ $(this).parent().removeClass('hover'); } ); - } + }; var plugins = { wickedmenu: Menu, wickedtoolbar: Toolbar }; for (var key in plugins) { (function(name, Plugin){ - $.fn[name] = function ( options ) { + $.fn[name] = function (options) { var args = arguments; return this.each(function () { if (typeof options === 'object' || !options) { @@ -217,7 +281,7 @@ THE SOFTWARE. return d[options](Array.prototype.slice.call(args, 1)); } }); - } + }; })(key, plugins[key]) } diff --git a/examples/ajax.html b/examples/ajax.html index dbc1c89..24e4489 100644 --- a/examples/ajax.html +++ b/examples/ajax.html @@ -12,42 +12,121 @@ This example demonstrates how to set left and right editors using ajax. - - + + - + - + + - + + + + +
    Drop files here
    ignore witespaces
    +
    -
    + + + +
      save   save
    + +
    diff --git a/lib/mergely.js b/lib/mergely.js index 9c153e1..38b5e74 100644 --- a/lib/mergely.js +++ b/lib/mergely.js @@ -386,11 +386,12 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { viewport: false, ignorews: false, fadein: 'fast', - editor_width: '400px', + editor_width: '650px', editor_height: '400px', resize_timeout: 500, change_timeout: 150, - fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f'}, + fgcolor: {a:'#4ba3fa',c:'#a3a3a3',d:'#ff7f7f', // color for differences (soft color) + ca:'#4b73ff',cc:'#434343',cd:'#ff4f4f'}, // color for currently active difference (bright color) bgcolor: '#eee', vpcolor: 'rgba(0, 0, 200, 0.5)', lhs: function(setValue) { }, @@ -481,6 +482,26 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { unmarkup: function() { this._clear(); }, + scrollToDiff: function(direction) { + if (!this.changes.length) return; + if (direction == 'next') { + this._current_diff = Math.min(++this._current_diff, this.changes.length - 1); + } + else { + this._current_diff = Math.max(--this._current_diff, 0); + } + this._scroll_to_change(this.changes[this._current_diff]); + this._changed(this.id + '-lhs', this.id + '-rhs'); + }, + mergeCurrentDiff: function(side) { + if (!this.changes.length) return; + if (side == 'lhs' && !this.lhs_cmsettings.readOnly) { + this._merge_change(this.changes[this._current_diff], 'rhs', 'lhs'); + } + else if (side == 'rhs' && !this.rhs_cmsettings.readOnly) { + this._merge_change(this.changes[this._current_diff], 'lhs', 'rhs'); + } + }, scrollTo: function(side, num) { var le = this.editor[this.id + '-lhs']; var re = this.editor[this.id + '-rhs']; @@ -605,7 +626,7 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { } else { // homebrew - var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;'; + var style = 'opacity:0.4;width:10px;height:15px;background-color:#888;cursor:pointer;text-align:center;color:#eee;border:1px solid: #222;margin-right:5px;margin-top: -2px;'; merge_lhs_button = '
    <
    '; merge_rhs_button = '
    >
    '; } @@ -688,7 +709,31 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { this.settings.rhs(setv.bind(this.editor[this.id + '-rhs'].getDoc())); } }, - + + _scroll_to_change : function(change) { + if (!change) return; + var self = this; + var led = self.editor[self.id+'-lhs']; + var red = self.editor[self.id+'-rhs']; + + var yref = led.getScrollerElement().offsetHeight * 1/2; // center between >0 and 1/2 + + // set cursors + led.setCursor(Math.max(change["lhs-line-from"],0), 0); // use led.getCursor().ch ? + red.setCursor(Math.max(change["rhs-line-from"],0), 0); + + // using directly CodeMirror breaks canvas alignment + // var ly = led.charCoords({line: Math.max(change["lhs-line-from"],0), ch: 0}, "local").top; + + // calculate scroll offset for current change. Warning: returns relative y position so we scroll to 0 first. + led.scrollTo(null, 0); + red.scrollTo(null, 0); + self._calculate_offsets(self.id+'-lhs', self.id+'-rhs', [change]); + led.scrollTo(null, Math.max(change["lhs-y-start"]-yref, 0)); + red.scrollTo(null, Math.max(change["rhs-y-start"]-yref, 0)); + // right pane should simply follows + }, + _scrolling: function(editor_name) { if (this._skipscroll[editor_name] === true) { // scrolling one side causes the other to event - ignore it @@ -845,6 +890,12 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { this.trace('change', 'diff time', timer.stop()); this.changes = Mgly.DiffParser(d.normal_form()); this.trace('change', 'parse time', timer.stop()); + if (this._current_diff === undefined) { + // go to first difference on start-up + this._current_diff = 0; + this._scroll_to_change(this.changes[0]); + } + this.trace('change', 'scroll_to_change time', timer.stop()); this._calculate_offsets(editor_name1, editor_name2, this.changes); this.trace('change', 'offsets time', timer.stop()); this._markup_changes(editor_name1, editor_name2, this.changes); @@ -1230,8 +1281,17 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { } }); var change = self.changes[cid]; + self._merge_change(change, side, oside); + return false; + }); + this.trace('change', 'markup buttons time', timer.stop()); + }, + _merge_change : function(change, side, oside) { + if (!change) return; + var led = this.editor[this.id+'-lhs']; + var red = this.editor[this.id+'-rhs']; + var ed = {lhs:led, rhs:red}; - var line = {lhs: ed['lhs'].lineInfo(llt), rhs: ed['rhs'].lineInfo(rlt)}; var text = ed[side].getRange( CodeMirror.Pos(change[side + '-line-from'], 0), @@ -1272,9 +1332,8 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { //reset ed['lhs'].setValue(ed['lhs'].getValue()); ed['rhs'].setValue(ed['rhs'].getValue()); - return false; - }); - this.trace('change', 'markup buttons time', timer.stop()); + + this._scroll_to_change(change) }, _draw_info: function(editor_name1, editor_name2) { var visible_page_height = jQuery(this.editor[editor_name1].getScrollerElement()).height(); @@ -1345,14 +1404,14 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { this.trace('draw', 'marker calculated', lhs_y_start, lhs_y_end, rhs_y_start, rhs_y_end); ctx_lhs.beginPath(); - ctx_lhs.fillStyle = this.settings.fgcolor[change['op']]; + ctx_lhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']]; ctx_lhs.strokeStyle = '#000'; ctx_lhs.lineWidth = 0.5; ctx_lhs.fillRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); ctx_lhs.strokeRect(1.5, lhs_y_start, 4.5, Math.max(lhs_y_end - lhs_y_start, 5)); ctx_rhs.beginPath(); - ctx_rhs.fillStyle = this.settings.fgcolor[change['op']]; + ctx_rhs.fillStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']]; ctx_rhs.strokeStyle = '#000'; ctx_rhs.lineWidth = 0.5; ctx_rhs.fillRect(1.5, rhs_y_start, 4.5, Math.max(rhs_y_end - rhs_y_start, 5)); @@ -1371,8 +1430,8 @@ jQuery.extend(Mgly.CodeMirrorDiffView.prototype, { // draw left box ctx.beginPath(); - ctx.strokeStyle = this.settings.fgcolor[change['op']]; - ctx.lineWidth = 1; + ctx.strokeStyle = this.settings.fgcolor[(this._current_diff==i?'c':'')+change['op']]; + ctx.lineWidth = (this._current_diff==i) ? 1.5 : 1; var rectWidth = this.draw_lhs_width; var rectHeight = lhs_y_end - lhs_y_start - 1;