diffs: added diff navigation to improve UX when browisng the full context diffs.
This commit is contained in:
parent
9cadf302eb
commit
012f801f16
8 changed files with 658 additions and 6 deletions
|
|
@ -51,9 +51,12 @@
|
|||
"<%= dirs.js.src %>/plugins/jquery.pjax.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.dataTables.js",
|
||||
"<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
|
||||
"<%= dirs.js.src %>/plugins/within_viewport.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.debounce.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.scrollstop.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.within-viewport.js",
|
||||
"<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.timeago.js",
|
||||
"<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
|
||||
|
|
|
|||
|
|
@ -540,10 +540,11 @@ class DiffSet(object):
|
|||
})
|
||||
|
||||
file_chunks = patch['chunks'][1:]
|
||||
for hunk in file_chunks:
|
||||
for i, hunk in enumerate(file_chunks, 1):
|
||||
hunkbit = self.parse_hunk(hunk, source_file, target_file)
|
||||
hunkbit.source_file_path = source_file_path
|
||||
hunkbit.target_file_path = target_file_path
|
||||
hunkbit.index = i
|
||||
filediff.hunks.append(hunkbit)
|
||||
|
||||
# Simulate hunk on OPS type line which doesn't really contain any diff
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ from pygments.lexers import (
|
|||
get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
|
||||
|
||||
from pyramid.threadlocal import get_current_request
|
||||
|
||||
from tempita import looper
|
||||
from webhelpers2.html import literal, HTML, escape
|
||||
from webhelpers2.html._autolink import _auto_link_urls
|
||||
from webhelpers2.html.tools import (
|
||||
|
|
|
|||
|
|
@ -998,6 +998,21 @@ input.filediff-collapse-state {
|
|||
|
||||
/**** END COMMENTS ****/
|
||||
|
||||
|
||||
.nav-chunk {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
margin-top: -17px;
|
||||
}
|
||||
|
||||
.nav-chunk.selected {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
#diff_nav {
|
||||
color: @grey3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
91
rhodecode/public/js/src/plugins/jquery.scrollstop.js
Normal file
91
rhodecode/public/js/src/plugins/jquery.scrollstop.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// jQuery Scrollstop Plugin v1.2.0
|
||||
// https://github.com/ssorallen/jquery-scrollstop
|
||||
|
||||
(function (factory) {
|
||||
// UMD[2] wrapper for jQuery plugins to work in AMD or in CommonJS.
|
||||
//
|
||||
// [2] https://github.com/umdjs/umd
|
||||
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(['jquery'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
// Node/CommonJS
|
||||
module.exports = factory(require('jquery'));
|
||||
} else {
|
||||
// Browser globals
|
||||
factory(jQuery);
|
||||
}
|
||||
}(function ($) {
|
||||
// $.event.dispatch was undocumented and was deprecated in jQuery 1.7[1]. It
|
||||
// was replaced by $.event.handle in jQuery 1.9.
|
||||
//
|
||||
// Use the first of the available functions to support jQuery <1.8.
|
||||
//
|
||||
// [1] https://github.com/jquery/jquery-migrate/blob/master/src/event.js#L25
|
||||
var dispatch = $.event.dispatch || $.event.handle;
|
||||
|
||||
var special = $.event.special,
|
||||
uid1 = 'D' + (+new Date()),
|
||||
uid2 = 'D' + (+new Date() + 1);
|
||||
|
||||
special.scrollstart = {
|
||||
setup: function(data) {
|
||||
var _data = $.extend({
|
||||
latency: special.scrollstop.latency
|
||||
}, data);
|
||||
|
||||
var timer,
|
||||
handler = function(evt) {
|
||||
var _self = this,
|
||||
_args = arguments;
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
} else {
|
||||
evt.type = 'scrollstart';
|
||||
dispatch.apply(_self, _args);
|
||||
}
|
||||
|
||||
timer = setTimeout(function() {
|
||||
timer = null;
|
||||
}, _data.latency);
|
||||
};
|
||||
|
||||
$(this).bind('scroll', handler).data(uid1, handler);
|
||||
},
|
||||
teardown: function() {
|
||||
$(this).unbind('scroll', $(this).data(uid1));
|
||||
}
|
||||
};
|
||||
|
||||
special.scrollstop = {
|
||||
latency: 250,
|
||||
setup: function(data) {
|
||||
var _data = $.extend({
|
||||
latency: special.scrollstop.latency
|
||||
}, data);
|
||||
|
||||
var timer,
|
||||
handler = function(evt) {
|
||||
var _self = this,
|
||||
_args = arguments;
|
||||
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
|
||||
timer = setTimeout(function() {
|
||||
timer = null;
|
||||
evt.type = 'scrollstop';
|
||||
dispatch.apply(_self, _args);
|
||||
}, _data.latency);
|
||||
};
|
||||
|
||||
$(this).bind('scroll', handler).data(uid2, handler);
|
||||
},
|
||||
teardown: function() {
|
||||
$(this).unbind('scroll', $(this).data(uid2));
|
||||
}
|
||||
};
|
||||
}));
|
||||
171
rhodecode/public/js/src/plugins/jquery.within-viewport.js
Normal file
171
rhodecode/public/js/src/plugins/jquery.within-viewport.js
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Within Viewport jQuery Plugin
|
||||
*
|
||||
* @description Companion plugin for withinviewport.js - determines whether an element is completely within the browser viewport
|
||||
* @author Craig Patik, http://patik.com/
|
||||
* @version 2.1.2
|
||||
* @date 2019-08-16
|
||||
*/
|
||||
(function ($) {
|
||||
/**
|
||||
* $.withinviewport()
|
||||
* @description jQuery method
|
||||
* @param {Object} [settings] optional settings
|
||||
* @return {Collection} Contains all elements that were within the viewport
|
||||
*/
|
||||
$.fn.withinviewport = function (settings) {
|
||||
var opts;
|
||||
var elems;
|
||||
|
||||
if (typeof settings === 'string') {
|
||||
settings = {
|
||||
sides: settings
|
||||
};
|
||||
}
|
||||
|
||||
opts = $.extend({}, settings, {
|
||||
sides: 'all'
|
||||
});
|
||||
elems = [];
|
||||
|
||||
this.each(function () {
|
||||
if (withinviewport(this, opts)) {
|
||||
elems.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
return $(elems);
|
||||
};
|
||||
|
||||
// Main custom selector
|
||||
$.extend($.expr[':'], {
|
||||
'within-viewport': function (element) {
|
||||
return withinviewport(element, 'all');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Optional enhancements and shortcuts
|
||||
*
|
||||
* @description Uncomment or comment these pieces as they apply to your project and coding preferences
|
||||
*/
|
||||
|
||||
// Shorthand jQuery methods
|
||||
|
||||
$.fn.withinviewporttop = function (settings) {
|
||||
var opts;
|
||||
var elems;
|
||||
|
||||
if (typeof settings === 'string') {
|
||||
settings = {
|
||||
sides: settings
|
||||
};
|
||||
}
|
||||
|
||||
opts = $.extend({}, settings, {
|
||||
sides: 'top'
|
||||
});
|
||||
elems = [];
|
||||
|
||||
this.each(function () {
|
||||
if (withinviewport(this, opts)) {
|
||||
elems.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
return $(elems);
|
||||
};
|
||||
|
||||
$.fn.withinviewportright = function (settings) {
|
||||
var opts;
|
||||
var elems;
|
||||
|
||||
if (typeof settings === 'string') {
|
||||
settings = {
|
||||
sides: settings
|
||||
};
|
||||
}
|
||||
|
||||
opts = $.extend({}, settings, {
|
||||
sides: 'right'
|
||||
});
|
||||
elems = [];
|
||||
|
||||
this.each(function () {
|
||||
if (withinviewport(this, opts)) {
|
||||
elems.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
return $(elems);
|
||||
};
|
||||
|
||||
$.fn.withinviewportbottom = function (settings) {
|
||||
var opts;
|
||||
var elems;
|
||||
|
||||
if (typeof settings === 'string') {
|
||||
settings = {
|
||||
sides: settings
|
||||
};
|
||||
}
|
||||
|
||||
opts = $.extend({}, settings, {
|
||||
sides: 'bottom'
|
||||
});
|
||||
elems = [];
|
||||
|
||||
this.each(function () {
|
||||
if (withinviewport(this, opts)) {
|
||||
elems.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
return $(elems);
|
||||
};
|
||||
|
||||
$.fn.withinviewportleft = function (settings) {
|
||||
var opts;
|
||||
var elems;
|
||||
|
||||
if (typeof settings === 'string') {
|
||||
settings = {
|
||||
sides: settings
|
||||
};
|
||||
}
|
||||
|
||||
opts = $.extend({}, settings, {
|
||||
sides: 'left'
|
||||
});
|
||||
elems = [];
|
||||
|
||||
this.each(function () {
|
||||
if (withinviewport(this, opts)) {
|
||||
elems.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
return $(elems);
|
||||
};
|
||||
|
||||
// Custom jQuery selectors
|
||||
$.extend($.expr[':'], {
|
||||
'within-viewport-top': function (element) {
|
||||
return withinviewport(element, 'top');
|
||||
},
|
||||
'within-viewport-right': function (element) {
|
||||
return withinviewport(element, 'right');
|
||||
},
|
||||
'within-viewport-bottom': function (element) {
|
||||
return withinviewport(element, 'bottom');
|
||||
},
|
||||
'within-viewport-left': function (element) {
|
||||
return withinviewport(element, 'left');
|
||||
}
|
||||
// Example custom selector:
|
||||
//,
|
||||
// 'within-viewport-top-left-45': function (element) {
|
||||
// return withinviewport(element, {sides:'top left', top: 45, left: 45});
|
||||
// }
|
||||
});
|
||||
}(jQuery));
|
||||
235
rhodecode/public/js/src/plugins/within_viewport.js
Normal file
235
rhodecode/public/js/src/plugins/within_viewport.js
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
* Within Viewport
|
||||
*
|
||||
* @description Determines whether an element is completely within the browser viewport
|
||||
* @author Craig Patik, http://patik.com/
|
||||
* @version 2.1.2
|
||||
* @date 2019-08-16
|
||||
*/
|
||||
(function (root, name, factory) {
|
||||
// AMD
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define([], factory);
|
||||
}
|
||||
// Node and CommonJS-like environments
|
||||
else if (typeof module !== 'undefined' && typeof exports === 'object') {
|
||||
module.exports = factory();
|
||||
}
|
||||
// Browser global
|
||||
else {
|
||||
root[name] = factory();
|
||||
}
|
||||
}(this, 'withinviewport', function () {
|
||||
var canUseWindowDimensions = typeof window !== 'undefined' && window.innerHeight !== undefined; // IE 8 and lower fail this
|
||||
|
||||
/**
|
||||
* Determines whether an element is within the viewport
|
||||
* @param {Object} elem DOM Element (required)
|
||||
* @param {Object} options Optional settings
|
||||
* @return {Boolean} Whether the element was completely within the viewport
|
||||
*/
|
||||
var withinviewport = function withinviewport(elem, options) {
|
||||
var result = false;
|
||||
var metadata = {};
|
||||
var config = {};
|
||||
var settings;
|
||||
var isWithin;
|
||||
var isContainerTheWindow;
|
||||
var elemBoundingRect;
|
||||
var containerBoundingRect;
|
||||
var containerScrollTop;
|
||||
var containerScrollLeft;
|
||||
var scrollBarWidths = [0, 0];
|
||||
var sideNamesPattern;
|
||||
var sides;
|
||||
var side;
|
||||
var i;
|
||||
|
||||
// If invoked by the jQuery plugin, get the actual DOM element
|
||||
if (typeof jQuery !== 'undefined' && elem instanceof jQuery) {
|
||||
elem = elem.get(0);
|
||||
}
|
||||
|
||||
if (typeof elem !== 'object' || elem.nodeType !== 1) {
|
||||
throw new Error('First argument must be an element');
|
||||
}
|
||||
|
||||
// Look for inline settings on the element
|
||||
if (elem.getAttribute('data-withinviewport-settings') && window.JSON) {
|
||||
metadata = JSON.parse(elem.getAttribute('data-withinviewport-settings'));
|
||||
}
|
||||
|
||||
// Settings argument may be a simple string (`top`, `right`, etc)
|
||||
if (typeof options === 'string') {
|
||||
settings = {
|
||||
sides: options
|
||||
};
|
||||
} else {
|
||||
settings = options || {};
|
||||
}
|
||||
|
||||
// Build configuration from defaults and user-provided settings and metadata
|
||||
config.container = settings.container || metadata.container || withinviewport.defaults.container || window;
|
||||
config.sides = settings.sides || metadata.sides || withinviewport.defaults.sides || 'all';
|
||||
config.top = settings.top || metadata.top || withinviewport.defaults.top || 0;
|
||||
config.right = settings.right || metadata.right || withinviewport.defaults.right || 0;
|
||||
config.bottom = settings.bottom || metadata.bottom || withinviewport.defaults.bottom || 0;
|
||||
config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;
|
||||
|
||||
// Extract the DOM node from a jQuery collection
|
||||
if (typeof jQuery !== 'undefined' && config.container instanceof jQuery) {
|
||||
config.container = config.container.get(0);
|
||||
}
|
||||
|
||||
// Use the window as the container if the user specified the body or a non-element
|
||||
if (config.container === document.body || config.container.nodeType !== 1) {
|
||||
config.container = window;
|
||||
}
|
||||
|
||||
isContainerTheWindow = (config.container === window);
|
||||
|
||||
// Element testing methods
|
||||
isWithin = {
|
||||
// Element is below the top edge of the viewport
|
||||
top: function _isWithin_top() {
|
||||
if (isContainerTheWindow) {
|
||||
return (elemBoundingRect.top >= config.top);
|
||||
} else {
|
||||
return (elemBoundingRect.top >= containerScrollTop - (containerScrollTop - containerBoundingRect.top) + config.top);
|
||||
}
|
||||
},
|
||||
|
||||
// Element is to the left of the right edge of the viewport
|
||||
right: function _isWithin_right() {
|
||||
// Note that `elemBoundingRect.right` is the distance from the *left* of the viewport to the element's far right edge
|
||||
|
||||
if (isContainerTheWindow) {
|
||||
return (elemBoundingRect.right <= (containerBoundingRect.right + containerScrollLeft) - config.right);
|
||||
} else {
|
||||
return (elemBoundingRect.right <= containerBoundingRect.right - scrollBarWidths[0] - config.right);
|
||||
}
|
||||
},
|
||||
|
||||
// Element is above the bottom edge of the viewport
|
||||
bottom: function _isWithin_bottom() {
|
||||
var containerHeight = 0;
|
||||
|
||||
if (isContainerTheWindow) {
|
||||
if (canUseWindowDimensions) {
|
||||
containerHeight = config.container.innerHeight;
|
||||
} else if (document && document.documentElement) {
|
||||
containerHeight = document.documentElement.clientHeight;
|
||||
}
|
||||
} else {
|
||||
containerHeight = containerBoundingRect.bottom;
|
||||
}
|
||||
|
||||
// Note that `elemBoundingRect.bottom` is the distance from the *top* of the viewport to the element's bottom edge
|
||||
return (elemBoundingRect.bottom <= containerHeight - scrollBarWidths[1] - config.bottom);
|
||||
},
|
||||
|
||||
// Element is to the right of the left edge of the viewport
|
||||
left: function _isWithin_left() {
|
||||
if (isContainerTheWindow) {
|
||||
return (elemBoundingRect.left >= config.left);
|
||||
} else {
|
||||
return (elemBoundingRect.left >= containerScrollLeft - (containerScrollLeft - containerBoundingRect.left) + config.left);
|
||||
}
|
||||
},
|
||||
|
||||
// Element is within all four boundaries
|
||||
all: function _isWithin_all() {
|
||||
// Test each boundary in order of efficiency and likeliness to be false. This way we can avoid running all four functions on most elements.
|
||||
// 1. Top: Quickest to calculate + most likely to be false
|
||||
// 2. Bottom: Note quite as quick to calculate, but also very likely to be false
|
||||
// 3-4. Left and right are both equally unlikely to be false since most sites only scroll vertically, but left is faster to calculate
|
||||
return (isWithin.top() && isWithin.bottom() && isWithin.left() && isWithin.right());
|
||||
}
|
||||
};
|
||||
|
||||
// Get the element's bounding rectangle with respect to the viewport
|
||||
elemBoundingRect = elem.getBoundingClientRect();
|
||||
|
||||
// Get viewport dimensions and offsets
|
||||
if (isContainerTheWindow) {
|
||||
containerBoundingRect = document.documentElement.getBoundingClientRect();
|
||||
containerScrollTop = document.body.scrollTop;
|
||||
containerScrollLeft = window.scrollX || document.body.scrollLeft;
|
||||
} else {
|
||||
containerBoundingRect = config.container.getBoundingClientRect();
|
||||
containerScrollTop = config.container.scrollTop;
|
||||
containerScrollLeft = config.container.scrollLeft;
|
||||
}
|
||||
|
||||
// Don't count the space consumed by scrollbars
|
||||
if (containerScrollLeft) {
|
||||
scrollBarWidths[0] = 18;
|
||||
}
|
||||
|
||||
if (containerScrollTop) {
|
||||
scrollBarWidths[1] = 16;
|
||||
}
|
||||
|
||||
// Test the element against each side of the viewport that was requested
|
||||
sideNamesPattern = /^top$|^right$|^bottom$|^left$|^all$/;
|
||||
|
||||
// Loop through all of the sides
|
||||
sides = config.sides.split(' ');
|
||||
i = sides.length;
|
||||
|
||||
while (i--) {
|
||||
side = sides[i].toLowerCase();
|
||||
|
||||
if (sideNamesPattern.test(side)) {
|
||||
if (isWithin[side]()) {
|
||||
result = true;
|
||||
} else {
|
||||
result = false;
|
||||
|
||||
// Quit as soon as the first failure is found
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// Default settings
|
||||
withinviewport.prototype.defaults = {
|
||||
container: typeof document !== 'undefined' ? document.body : {},
|
||||
sides: 'all',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
};
|
||||
|
||||
withinviewport.defaults = withinviewport.prototype.defaults;
|
||||
|
||||
/**
|
||||
* Optional enhancements and shortcuts
|
||||
*
|
||||
* @description Uncomment or comment these pieces as they apply to your project and coding preferences
|
||||
*/
|
||||
|
||||
// Shortcut methods for each side of the viewport
|
||||
// Example: `withinviewport.top(elem)` is the same as `withinviewport(elem, 'top')`
|
||||
withinviewport.prototype.top = function _withinviewport_top(element) {
|
||||
return withinviewport(element, 'top');
|
||||
};
|
||||
|
||||
withinviewport.prototype.right = function _withinviewport_right(element) {
|
||||
return withinviewport(element, 'right');
|
||||
};
|
||||
|
||||
withinviewport.prototype.bottom = function _withinviewport_bottom(element) {
|
||||
return withinviewport(element, 'bottom');
|
||||
};
|
||||
|
||||
withinviewport.prototype.left = function _withinviewport_left(element) {
|
||||
return withinviewport(element, 'left');
|
||||
};
|
||||
|
||||
return withinviewport;
|
||||
}));
|
||||
|
|
@ -314,6 +314,7 @@ return '%s_%s_%i' % (h.md5_safe(commit+filename), type, line)
|
|||
${hunk.section_header}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
|
||||
% endfor
|
||||
|
||||
|
|
@ -657,21 +658,28 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
%>
|
||||
|
||||
<%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
|
||||
%for i, line in enumerate(hunk.sideside):
|
||||
|
||||
<% chunk_count = 1 %>
|
||||
%for loop_obj, item in h.looper(hunk.sideside):
|
||||
<%
|
||||
line = item
|
||||
i = loop_obj.index
|
||||
prev_line = loop_obj.previous
|
||||
old_line_anchor, new_line_anchor = None, None
|
||||
|
||||
if line.original.lineno:
|
||||
old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
|
||||
if line.modified.lineno:
|
||||
new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
|
||||
|
||||
line_action = line.modified.action or line.original.action
|
||||
prev_line_action = prev_line and (prev_line.modified.action or prev_line.original.action)
|
||||
%>
|
||||
|
||||
<tr class="cb-line">
|
||||
<td class="cb-data ${action_class(line.original.action)}"
|
||||
data-line-no="${line.original.lineno}"
|
||||
>
|
||||
<div>
|
||||
|
||||
<% line_old_comments = None %>
|
||||
%if line.original.get_comment_args:
|
||||
|
|
@ -685,7 +693,6 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
<i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
|
||||
% endif
|
||||
%endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="cb-lineno ${action_class(line.original.action)}"
|
||||
data-line-no="${line.original.lineno}"
|
||||
|
|
@ -751,6 +758,12 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
%if use_comments and line.modified.lineno and line_new_comments:
|
||||
${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
|
||||
%endif
|
||||
% if line_action in ['+', '-'] and prev_line_action not in ['+', '-']:
|
||||
<div class="nav-chunk" style="visibility: hidden">
|
||||
<i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i>
|
||||
</div>
|
||||
<% chunk_count +=1 %>
|
||||
% endif
|
||||
</td>
|
||||
</tr>
|
||||
%endfor
|
||||
|
|
@ -903,12 +916,21 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fpath-placeholder">
|
||||
<div class="fpath-placeholder pull-left">
|
||||
<i class="icon-file-text"></i>
|
||||
<strong class="fpath-placeholder-text">
|
||||
Context file:
|
||||
</strong>
|
||||
</div>
|
||||
<div class="pull-right noselect">
|
||||
<span id="diff_nav">Loading diff...:</span>
|
||||
<span class="cursor-pointer" onclick="scrollToPrevChunk(); return false">
|
||||
<i class="icon-angle-up"></i>
|
||||
</span>
|
||||
<span class="cursor-pointer" onclick="scrollToNextChunk(); return false">
|
||||
<i class="icon-angle-down"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="sidebar_inner_shadow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1031,10 +1053,84 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
getCurrentChunk = function () {
|
||||
|
||||
var chunksAll = $('.nav-chunk').filter(function () {
|
||||
return $(this).parents('.filediff').prev().get(0).checked !== true
|
||||
})
|
||||
var chunkSelected = $('.nav-chunk.selected');
|
||||
var initial = false;
|
||||
|
||||
if (chunkSelected.length === 0) {
|
||||
// no initial chunk selected, we pick first
|
||||
chunkSelected = $(chunksAll.get(0));
|
||||
var initial = true;
|
||||
}
|
||||
|
||||
return {
|
||||
'all': chunksAll,
|
||||
'selected': chunkSelected,
|
||||
'initial': initial,
|
||||
}
|
||||
}
|
||||
|
||||
animateDiffNavText = function () {
|
||||
var $diffNav = $('#diff_nav')
|
||||
|
||||
var callback = function () {
|
||||
$diffNav.animate({'opacity': 1.00}, 200)
|
||||
};
|
||||
$diffNav.animate({'opacity': 0.15}, 200, callback);
|
||||
}
|
||||
|
||||
scrollToChunk = function (moveBy) {
|
||||
var chunk = getCurrentChunk();
|
||||
var all = chunk.all
|
||||
var selected = chunk.selected
|
||||
|
||||
var curPos = all.index(selected);
|
||||
var newPos = curPos;
|
||||
if (!chunk.initial) {
|
||||
var newPos = curPos + moveBy;
|
||||
}
|
||||
|
||||
var curElem = all.get(newPos);
|
||||
|
||||
if (curElem === undefined) {
|
||||
// end or back
|
||||
$('#diff_nav').html('No next diff element.')
|
||||
animateDiffNavText()
|
||||
return
|
||||
} else if (newPos < 0) {
|
||||
$('#diff_nav').html('No previous diff element.')
|
||||
animateDiffNavText()
|
||||
return
|
||||
} else {
|
||||
$('#diff_nav').html('Diff navigation:')
|
||||
}
|
||||
|
||||
curElem = $(curElem)
|
||||
var offset = 100;
|
||||
$(window).scrollTop(curElem.position().top - offset);
|
||||
|
||||
//clear selection
|
||||
all.removeClass('selected')
|
||||
curElem.addClass('selected')
|
||||
}
|
||||
|
||||
scrollToPrevChunk = function () {
|
||||
scrollToChunk(-1)
|
||||
}
|
||||
scrollToNextChunk = function () {
|
||||
scrollToChunk(1)
|
||||
}
|
||||
|
||||
</script>
|
||||
% endif
|
||||
|
||||
<script type="text/javascript">
|
||||
$('#diff_nav').html('loading diff...') // wait until whole page is loaded
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
var contextPrefix = _gettext('Context file: ');
|
||||
|
|
@ -1213,6 +1309,46 @@ def get_comments_for(diff_type, comments, filename, line_version, line_number):
|
|||
$('.toggle-wide-diff').addClass('btn-active');
|
||||
updateSticky();
|
||||
}
|
||||
|
||||
// DIFF NAV //
|
||||
|
||||
// element to detect scroll direction of
|
||||
var $window = $(window);
|
||||
|
||||
// initialize last scroll position
|
||||
var lastScrollY = $window.scrollTop();
|
||||
|
||||
$window.on('resize scrollstop', {latency: 350}, function () {
|
||||
var visibleChunks = $('.nav-chunk').withinviewport({top: 75});
|
||||
|
||||
// get current scroll position
|
||||
var currentScrollY = $window.scrollTop();
|
||||
|
||||
// determine current scroll direction
|
||||
if (currentScrollY > lastScrollY) {
|
||||
var y = 'down'
|
||||
} else if (currentScrollY !== lastScrollY) {
|
||||
var y = 'up';
|
||||
}
|
||||
|
||||
var pos = -1; // by default we use last element in viewport
|
||||
if (y === 'down') {
|
||||
pos = -1;
|
||||
} else if (y === 'up') {
|
||||
pos = 0;
|
||||
}
|
||||
|
||||
if (visibleChunks.length > 0) {
|
||||
$('.nav-chunk').removeClass('selected');
|
||||
$(visibleChunks.get(pos)).addClass('selected');
|
||||
}
|
||||
|
||||
// update last scroll position to current position
|
||||
lastScrollY = currentScrollY;
|
||||
|
||||
});
|
||||
$('#diff_nav').html('Diff navigation:')
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue