##// END OF EJS Templates
diffs: added diff navigation to improve UX when browisng the full context diffs.
marcink -
r4441:114e65cb default
parent child
Show More
@@ -0,0 +1,91
1 // jQuery Scrollstop Plugin v1.2.0
2 // https://github.com/ssorallen/jquery-scrollstop
3
4 (function (factory) {
5 // UMD[2] wrapper for jQuery plugins to work in AMD or in CommonJS.
6 //
7 // [2] https://github.com/umdjs/umd
8
9 if (typeof define === 'function' && define.amd) {
10 // AMD. Register as an anonymous module.
11 define(['jquery'], factory);
12 } else if (typeof exports === 'object') {
13 // Node/CommonJS
14 module.exports = factory(require('jquery'));
15 } else {
16 // Browser globals
17 factory(jQuery);
18 }
19 }(function ($) {
20 // $.event.dispatch was undocumented and was deprecated in jQuery 1.7[1]. It
21 // was replaced by $.event.handle in jQuery 1.9.
22 //
23 // Use the first of the available functions to support jQuery <1.8.
24 //
25 // [1] https://github.com/jquery/jquery-migrate/blob/master/src/event.js#L25
26 var dispatch = $.event.dispatch || $.event.handle;
27
28 var special = $.event.special,
29 uid1 = 'D' + (+new Date()),
30 uid2 = 'D' + (+new Date() + 1);
31
32 special.scrollstart = {
33 setup: function(data) {
34 var _data = $.extend({
35 latency: special.scrollstop.latency
36 }, data);
37
38 var timer,
39 handler = function(evt) {
40 var _self = this,
41 _args = arguments;
42
43 if (timer) {
44 clearTimeout(timer);
45 } else {
46 evt.type = 'scrollstart';
47 dispatch.apply(_self, _args);
48 }
49
50 timer = setTimeout(function() {
51 timer = null;
52 }, _data.latency);
53 };
54
55 $(this).bind('scroll', handler).data(uid1, handler);
56 },
57 teardown: function() {
58 $(this).unbind('scroll', $(this).data(uid1));
59 }
60 };
61
62 special.scrollstop = {
63 latency: 250,
64 setup: function(data) {
65 var _data = $.extend({
66 latency: special.scrollstop.latency
67 }, data);
68
69 var timer,
70 handler = function(evt) {
71 var _self = this,
72 _args = arguments;
73
74 if (timer) {
75 clearTimeout(timer);
76 }
77
78 timer = setTimeout(function() {
79 timer = null;
80 evt.type = 'scrollstop';
81 dispatch.apply(_self, _args);
82 }, _data.latency);
83 };
84
85 $(this).bind('scroll', handler).data(uid2, handler);
86 },
87 teardown: function() {
88 $(this).unbind('scroll', $(this).data(uid2));
89 }
90 };
91 }));
@@ -0,0 +1,171
1 /**
2 * Within Viewport jQuery Plugin
3 *
4 * @description Companion plugin for withinviewport.js - determines whether an element is completely within the browser viewport
5 * @author Craig Patik, http://patik.com/
6 * @version 2.1.2
7 * @date 2019-08-16
8 */
9 (function ($) {
10 /**
11 * $.withinviewport()
12 * @description jQuery method
13 * @param {Object} [settings] optional settings
14 * @return {Collection} Contains all elements that were within the viewport
15 */
16 $.fn.withinviewport = function (settings) {
17 var opts;
18 var elems;
19
20 if (typeof settings === 'string') {
21 settings = {
22 sides: settings
23 };
24 }
25
26 opts = $.extend({}, settings, {
27 sides: 'all'
28 });
29 elems = [];
30
31 this.each(function () {
32 if (withinviewport(this, opts)) {
33 elems.push(this);
34 }
35 });
36
37 return $(elems);
38 };
39
40 // Main custom selector
41 $.extend($.expr[':'], {
42 'within-viewport': function (element) {
43 return withinviewport(element, 'all');
44 }
45 });
46
47 /**
48 * Optional enhancements and shortcuts
49 *
50 * @description Uncomment or comment these pieces as they apply to your project and coding preferences
51 */
52
53 // Shorthand jQuery methods
54
55 $.fn.withinviewporttop = function (settings) {
56 var opts;
57 var elems;
58
59 if (typeof settings === 'string') {
60 settings = {
61 sides: settings
62 };
63 }
64
65 opts = $.extend({}, settings, {
66 sides: 'top'
67 });
68 elems = [];
69
70 this.each(function () {
71 if (withinviewport(this, opts)) {
72 elems.push(this);
73 }
74 });
75
76 return $(elems);
77 };
78
79 $.fn.withinviewportright = function (settings) {
80 var opts;
81 var elems;
82
83 if (typeof settings === 'string') {
84 settings = {
85 sides: settings
86 };
87 }
88
89 opts = $.extend({}, settings, {
90 sides: 'right'
91 });
92 elems = [];
93
94 this.each(function () {
95 if (withinviewport(this, opts)) {
96 elems.push(this);
97 }
98 });
99
100 return $(elems);
101 };
102
103 $.fn.withinviewportbottom = function (settings) {
104 var opts;
105 var elems;
106
107 if (typeof settings === 'string') {
108 settings = {
109 sides: settings
110 };
111 }
112
113 opts = $.extend({}, settings, {
114 sides: 'bottom'
115 });
116 elems = [];
117
118 this.each(function () {
119 if (withinviewport(this, opts)) {
120 elems.push(this);
121 }
122 });
123
124 return $(elems);
125 };
126
127 $.fn.withinviewportleft = function (settings) {
128 var opts;
129 var elems;
130
131 if (typeof settings === 'string') {
132 settings = {
133 sides: settings
134 };
135 }
136
137 opts = $.extend({}, settings, {
138 sides: 'left'
139 });
140 elems = [];
141
142 this.each(function () {
143 if (withinviewport(this, opts)) {
144 elems.push(this);
145 }
146 });
147
148 return $(elems);
149 };
150
151 // Custom jQuery selectors
152 $.extend($.expr[':'], {
153 'within-viewport-top': function (element) {
154 return withinviewport(element, 'top');
155 },
156 'within-viewport-right': function (element) {
157 return withinviewport(element, 'right');
158 },
159 'within-viewport-bottom': function (element) {
160 return withinviewport(element, 'bottom');
161 },
162 'within-viewport-left': function (element) {
163 return withinviewport(element, 'left');
164 }
165 // Example custom selector:
166 //,
167 // 'within-viewport-top-left-45': function (element) {
168 // return withinviewport(element, {sides:'top left', top: 45, left: 45});
169 // }
170 });
171 }(jQuery)); No newline at end of file
@@ -0,0 +1,235
1 /**
2 * Within Viewport
3 *
4 * @description Determines whether an element is completely within the browser viewport
5 * @author Craig Patik, http://patik.com/
6 * @version 2.1.2
7 * @date 2019-08-16
8 */
9 (function (root, name, factory) {
10 // AMD
11 if (typeof define === 'function' && define.amd) {
12 define([], factory);
13 }
14 // Node and CommonJS-like environments
15 else if (typeof module !== 'undefined' && typeof exports === 'object') {
16 module.exports = factory();
17 }
18 // Browser global
19 else {
20 root[name] = factory();
21 }
22 }(this, 'withinviewport', function () {
23 var canUseWindowDimensions = typeof window !== 'undefined' && window.innerHeight !== undefined; // IE 8 and lower fail this
24
25 /**
26 * Determines whether an element is within the viewport
27 * @param {Object} elem DOM Element (required)
28 * @param {Object} options Optional settings
29 * @return {Boolean} Whether the element was completely within the viewport
30 */
31 var withinviewport = function withinviewport(elem, options) {
32 var result = false;
33 var metadata = {};
34 var config = {};
35 var settings;
36 var isWithin;
37 var isContainerTheWindow;
38 var elemBoundingRect;
39 var containerBoundingRect;
40 var containerScrollTop;
41 var containerScrollLeft;
42 var scrollBarWidths = [0, 0];
43 var sideNamesPattern;
44 var sides;
45 var side;
46 var i;
47
48 // If invoked by the jQuery plugin, get the actual DOM element
49 if (typeof jQuery !== 'undefined' && elem instanceof jQuery) {
50 elem = elem.get(0);
51 }
52
53 if (typeof elem !== 'object' || elem.nodeType !== 1) {
54 throw new Error('First argument must be an element');
55 }
56
57 // Look for inline settings on the element
58 if (elem.getAttribute('data-withinviewport-settings') && window.JSON) {
59 metadata = JSON.parse(elem.getAttribute('data-withinviewport-settings'));
60 }
61
62 // Settings argument may be a simple string (`top`, `right`, etc)
63 if (typeof options === 'string') {
64 settings = {
65 sides: options
66 };
67 } else {
68 settings = options || {};
69 }
70
71 // Build configuration from defaults and user-provided settings and metadata
72 config.container = settings.container || metadata.container || withinviewport.defaults.container || window;
73 config.sides = settings.sides || metadata.sides || withinviewport.defaults.sides || 'all';
74 config.top = settings.top || metadata.top || withinviewport.defaults.top || 0;
75 config.right = settings.right || metadata.right || withinviewport.defaults.right || 0;
76 config.bottom = settings.bottom || metadata.bottom || withinviewport.defaults.bottom || 0;
77 config.left = settings.left || metadata.left || withinviewport.defaults.left || 0;
78
79 // Extract the DOM node from a jQuery collection
80 if (typeof jQuery !== 'undefined' && config.container instanceof jQuery) {
81 config.container = config.container.get(0);
82 }
83
84 // Use the window as the container if the user specified the body or a non-element
85 if (config.container === document.body || config.container.nodeType !== 1) {
86 config.container = window;
87 }
88
89 isContainerTheWindow = (config.container === window);
90
91 // Element testing methods
92 isWithin = {
93 // Element is below the top edge of the viewport
94 top: function _isWithin_top() {
95 if (isContainerTheWindow) {
96 return (elemBoundingRect.top >= config.top);
97 } else {
98 return (elemBoundingRect.top >= containerScrollTop - (containerScrollTop - containerBoundingRect.top) + config.top);
99 }
100 },
101
102 // Element is to the left of the right edge of the viewport
103 right: function _isWithin_right() {
104 // Note that `elemBoundingRect.right` is the distance from the *left* of the viewport to the element's far right edge
105
106 if (isContainerTheWindow) {
107 return (elemBoundingRect.right <= (containerBoundingRect.right + containerScrollLeft) - config.right);
108 } else {
109 return (elemBoundingRect.right <= containerBoundingRect.right - scrollBarWidths[0] - config.right);
110 }
111 },
112
113 // Element is above the bottom edge of the viewport
114 bottom: function _isWithin_bottom() {
115 var containerHeight = 0;
116
117 if (isContainerTheWindow) {
118 if (canUseWindowDimensions) {
119 containerHeight = config.container.innerHeight;
120 } else if (document && document.documentElement) {
121 containerHeight = document.documentElement.clientHeight;
122 }
123 } else {
124 containerHeight = containerBoundingRect.bottom;
125 }
126
127 // Note that `elemBoundingRect.bottom` is the distance from the *top* of the viewport to the element's bottom edge
128 return (elemBoundingRect.bottom <= containerHeight - scrollBarWidths[1] - config.bottom);
129 },
130
131 // Element is to the right of the left edge of the viewport
132 left: function _isWithin_left() {
133 if (isContainerTheWindow) {
134 return (elemBoundingRect.left >= config.left);
135 } else {
136 return (elemBoundingRect.left >= containerScrollLeft - (containerScrollLeft - containerBoundingRect.left) + config.left);
137 }
138 },
139
140 // Element is within all four boundaries
141 all: function _isWithin_all() {
142 // Test each boundary in order of efficiency and likeliness to be false. This way we can avoid running all four functions on most elements.
143 // 1. Top: Quickest to calculate + most likely to be false
144 // 2. Bottom: Note quite as quick to calculate, but also very likely to be false
145 // 3-4. Left and right are both equally unlikely to be false since most sites only scroll vertically, but left is faster to calculate
146 return (isWithin.top() && isWithin.bottom() && isWithin.left() && isWithin.right());
147 }
148 };
149
150 // Get the element's bounding rectangle with respect to the viewport
151 elemBoundingRect = elem.getBoundingClientRect();
152
153 // Get viewport dimensions and offsets
154 if (isContainerTheWindow) {
155 containerBoundingRect = document.documentElement.getBoundingClientRect();
156 containerScrollTop = document.body.scrollTop;
157 containerScrollLeft = window.scrollX || document.body.scrollLeft;
158 } else {
159 containerBoundingRect = config.container.getBoundingClientRect();
160 containerScrollTop = config.container.scrollTop;
161 containerScrollLeft = config.container.scrollLeft;
162 }
163
164 // Don't count the space consumed by scrollbars
165 if (containerScrollLeft) {
166 scrollBarWidths[0] = 18;
167 }
168
169 if (containerScrollTop) {
170 scrollBarWidths[1] = 16;
171 }
172
173 // Test the element against each side of the viewport that was requested
174 sideNamesPattern = /^top$|^right$|^bottom$|^left$|^all$/;
175
176 // Loop through all of the sides
177 sides = config.sides.split(' ');
178 i = sides.length;
179
180 while (i--) {
181 side = sides[i].toLowerCase();
182
183 if (sideNamesPattern.test(side)) {
184 if (isWithin[side]()) {
185 result = true;
186 } else {
187 result = false;
188
189 // Quit as soon as the first failure is found
190 break;
191 }
192 }
193 }
194
195 return result;
196 };
197
198 // Default settings
199 withinviewport.prototype.defaults = {
200 container: typeof document !== 'undefined' ? document.body : {},
201 sides: 'all',
202 top: 0,
203 right: 0,
204 bottom: 0,
205 left: 0
206 };
207
208 withinviewport.defaults = withinviewport.prototype.defaults;
209
210 /**
211 * Optional enhancements and shortcuts
212 *
213 * @description Uncomment or comment these pieces as they apply to your project and coding preferences
214 */
215
216 // Shortcut methods for each side of the viewport
217 // Example: `withinviewport.top(elem)` is the same as `withinviewport(elem, 'top')`
218 withinviewport.prototype.top = function _withinviewport_top(element) {
219 return withinviewport(element, 'top');
220 };
221
222 withinviewport.prototype.right = function _withinviewport_right(element) {
223 return withinviewport(element, 'right');
224 };
225
226 withinviewport.prototype.bottom = function _withinviewport_bottom(element) {
227 return withinviewport(element, 'bottom');
228 };
229
230 withinviewport.prototype.left = function _withinviewport_left(element) {
231 return withinviewport(element, 'left');
232 };
233
234 return withinviewport;
235 })); No newline at end of file
@@ -51,9 +51,12
51 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
51 "<%= dirs.js.src %>/plugins/jquery.pjax.js",
52 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
52 "<%= dirs.js.src %>/plugins/jquery.dataTables.js",
53 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
53 "<%= dirs.js.src %>/plugins/flavoured_checkbox.js",
54 "<%= dirs.js.src %>/plugins/within_viewport.js",
54 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
55 "<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js",
55 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
56 "<%= dirs.js.src %>/plugins/jquery.autocomplete.js",
56 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
57 "<%= dirs.js.src %>/plugins/jquery.debounce.js",
58 "<%= dirs.js.src %>/plugins/jquery.scrollstop.js",
59 "<%= dirs.js.src %>/plugins/jquery.within-viewport.js",
57 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
60 "<%= dirs.js.node_modules %>/mark.js/dist/jquery.mark.min.js",
58 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
61 "<%= dirs.js.src %>/plugins/jquery.timeago.js",
59 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
62 "<%= dirs.js.src %>/plugins/jquery.timeago-extension.js",
@@ -540,10 +540,11 class DiffSet(object):
540 })
540 })
541
541
542 file_chunks = patch['chunks'][1:]
542 file_chunks = patch['chunks'][1:]
543 for hunk in file_chunks:
543 for i, hunk in enumerate(file_chunks, 1):
544 hunkbit = self.parse_hunk(hunk, source_file, target_file)
544 hunkbit = self.parse_hunk(hunk, source_file, target_file)
545 hunkbit.source_file_path = source_file_path
545 hunkbit.source_file_path = source_file_path
546 hunkbit.target_file_path = target_file_path
546 hunkbit.target_file_path = target_file_path
547 hunkbit.index = i
547 filediff.hunks.append(hunkbit)
548 filediff.hunks.append(hunkbit)
548
549
549 # Simulate hunk on OPS type line which doesn't really contain any diff
550 # Simulate hunk on OPS type line which doesn't really contain any diff
@@ -53,7 +53,7 from pygments.lexers import (
53 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
54
54
55 from pyramid.threadlocal import get_current_request
55 from pyramid.threadlocal import get_current_request
56
56 from tempita import looper
57 from webhelpers2.html import literal, HTML, escape
57 from webhelpers2.html import literal, HTML, escape
58 from webhelpers2.html._autolink import _auto_link_urls
58 from webhelpers2.html._autolink import _auto_link_urls
59 from webhelpers2.html.tools import (
59 from webhelpers2.html.tools import (
@@ -998,6 +998,21 input.filediff-collapse-state {
998
998
999 /**** END COMMENTS ****/
999 /**** END COMMENTS ****/
1000
1000
1001
1002 .nav-chunk {
1003 position: absolute;
1004 right: 20px;
1005 margin-top: -17px;
1006 }
1007
1008 .nav-chunk.selected {
1009 visibility: visible !important;
1010 }
1011
1012 #diff_nav {
1013 color: @grey3;
1014 }
1015
1001 }
1016 }
1002
1017
1003
1018
@@ -314,6 +314,7 return '%s_%s_%i' % (h.md5_safe(commit+f
314 ${hunk.section_header}
314 ${hunk.section_header}
315 </td>
315 </td>
316 </tr>
316 </tr>
317
317 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
318 ${render_hunk_lines(filediff, c.user_session_attrs["diffmode"], hunk, use_comments=use_comments, inline_comments=inline_comments, active_pattern_entries=active_pattern_entries)}
318 % endfor
319 % endfor
319
320
@@ -657,21 +658,28 def get_comments_for(diff_type, comments
657 %>
658 %>
658
659
659 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
660 <%def name="render_hunk_lines_sideside(filediff, hunk, use_comments=False, inline_comments=None, active_pattern_entries=None)">
660 %for i, line in enumerate(hunk.sideside):
661
662 <% chunk_count = 1 %>
663 %for loop_obj, item in h.looper(hunk.sideside):
661 <%
664 <%
665 line = item
666 i = loop_obj.index
667 prev_line = loop_obj.previous
662 old_line_anchor, new_line_anchor = None, None
668 old_line_anchor, new_line_anchor = None, None
663
669
664 if line.original.lineno:
670 if line.original.lineno:
665 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
671 old_line_anchor = diff_line_anchor(filediff.raw_id, hunk.source_file_path, line.original.lineno, 'o')
666 if line.modified.lineno:
672 if line.modified.lineno:
667 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
673 new_line_anchor = diff_line_anchor(filediff.raw_id, hunk.target_file_path, line.modified.lineno, 'n')
674
675 line_action = line.modified.action or line.original.action
676 prev_line_action = prev_line and (prev_line.modified.action or prev_line.original.action)
668 %>
677 %>
669
678
670 <tr class="cb-line">
679 <tr class="cb-line">
671 <td class="cb-data ${action_class(line.original.action)}"
680 <td class="cb-data ${action_class(line.original.action)}"
672 data-line-no="${line.original.lineno}"
681 data-line-no="${line.original.lineno}"
673 >
682 >
674 <div>
675
683
676 <% line_old_comments = None %>
684 <% line_old_comments = None %>
677 %if line.original.get_comment_args:
685 %if line.original.get_comment_args:
@@ -685,7 +693,6 def get_comments_for(diff_type, comments
685 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
693 <i class="tooltip icon-comment" title="${_('comments: {}. Click to toggle them.').format(len(line_old_comments))}" onclick="return Rhodecode.comments.toggleLineComments(this)"></i>
686 % endif
694 % endif
687 %endif
695 %endif
688 </div>
689 </td>
696 </td>
690 <td class="cb-lineno ${action_class(line.original.action)}"
697 <td class="cb-lineno ${action_class(line.original.action)}"
691 data-line-no="${line.original.lineno}"
698 data-line-no="${line.original.lineno}"
@@ -751,6 +758,12 def get_comments_for(diff_type, comments
751 %if use_comments and line.modified.lineno and line_new_comments:
758 %if use_comments and line.modified.lineno and line_new_comments:
752 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
759 ${inline_comments_container(line_new_comments, active_pattern_entries=active_pattern_entries)}
753 %endif
760 %endif
761 % if line_action in ['+', '-'] and prev_line_action not in ['+', '-']:
762 <div class="nav-chunk" style="visibility: hidden">
763 <i class="icon-eye" title="viewing diff hunk-${hunk.index}-${chunk_count}"></i>
764 </div>
765 <% chunk_count +=1 %>
766 % endif
754 </td>
767 </td>
755 </tr>
768 </tr>
756 %endfor
769 %endfor
@@ -903,12 +916,21 def get_comments_for(diff_type, comments
903 </div>
916 </div>
904 </div>
917 </div>
905 </div>
918 </div>
906 <div class="fpath-placeholder">
919 <div class="fpath-placeholder pull-left">
907 <i class="icon-file-text"></i>
920 <i class="icon-file-text"></i>
908 <strong class="fpath-placeholder-text">
921 <strong class="fpath-placeholder-text">
909 Context file:
922 Context file:
910 </strong>
923 </strong>
911 </div>
924 </div>
925 <div class="pull-right noselect">
926 <span id="diff_nav">Loading diff...:</span>
927 <span class="cursor-pointer" onclick="scrollToPrevChunk(); return false">
928 <i class="icon-angle-up"></i>
929 </span>
930 <span class="cursor-pointer" onclick="scrollToNextChunk(); return false">
931 <i class="icon-angle-down"></i>
932 </span>
933 </div>
912 <div class="sidebar_inner_shadow"></div>
934 <div class="sidebar_inner_shadow"></div>
913 </div>
935 </div>
914 </div>
936 </div>
@@ -1031,10 +1053,84 def get_comments_for(diff_type, comments
1031 e.preventDefault();
1053 e.preventDefault();
1032 });
1054 });
1033
1055
1056 getCurrentChunk = function () {
1057
1058 var chunksAll = $('.nav-chunk').filter(function () {
1059 return $(this).parents('.filediff').prev().get(0).checked !== true
1060 })
1061 var chunkSelected = $('.nav-chunk.selected');
1062 var initial = false;
1063
1064 if (chunkSelected.length === 0) {
1065 // no initial chunk selected, we pick first
1066 chunkSelected = $(chunksAll.get(0));
1067 var initial = true;
1068 }
1069
1070 return {
1071 'all': chunksAll,
1072 'selected': chunkSelected,
1073 'initial': initial,
1074 }
1075 }
1076
1077 animateDiffNavText = function () {
1078 var $diffNav = $('#diff_nav')
1079
1080 var callback = function () {
1081 $diffNav.animate({'opacity': 1.00}, 200)
1082 };
1083 $diffNav.animate({'opacity': 0.15}, 200, callback);
1084 }
1085
1086 scrollToChunk = function (moveBy) {
1087 var chunk = getCurrentChunk();
1088 var all = chunk.all
1089 var selected = chunk.selected
1090
1091 var curPos = all.index(selected);
1092 var newPos = curPos;
1093 if (!chunk.initial) {
1094 var newPos = curPos + moveBy;
1095 }
1096
1097 var curElem = all.get(newPos);
1098
1099 if (curElem === undefined) {
1100 // end or back
1101 $('#diff_nav').html('No next diff element.')
1102 animateDiffNavText()
1103 return
1104 } else if (newPos < 0) {
1105 $('#diff_nav').html('No previous diff element.')
1106 animateDiffNavText()
1107 return
1108 } else {
1109 $('#diff_nav').html('Diff navigation:')
1110 }
1111
1112 curElem = $(curElem)
1113 var offset = 100;
1114 $(window).scrollTop(curElem.position().top - offset);
1115
1116 //clear selection
1117 all.removeClass('selected')
1118 curElem.addClass('selected')
1119 }
1120
1121 scrollToPrevChunk = function () {
1122 scrollToChunk(-1)
1123 }
1124 scrollToNextChunk = function () {
1125 scrollToChunk(1)
1126 }
1127
1034 </script>
1128 </script>
1035 % endif
1129 % endif
1036
1130
1037 <script type="text/javascript">
1131 <script type="text/javascript">
1132 $('#diff_nav').html('loading diff...') // wait until whole page is loaded
1133
1038 $(document).ready(function () {
1134 $(document).ready(function () {
1039
1135
1040 var contextPrefix = _gettext('Context file: ');
1136 var contextPrefix = _gettext('Context file: ');
@@ -1213,6 +1309,46 def get_comments_for(diff_type, comments
1213 $('.toggle-wide-diff').addClass('btn-active');
1309 $('.toggle-wide-diff').addClass('btn-active');
1214 updateSticky();
1310 updateSticky();
1215 }
1311 }
1312
1313 // DIFF NAV //
1314
1315 // element to detect scroll direction of
1316 var $window = $(window);
1317
1318 // initialize last scroll position
1319 var lastScrollY = $window.scrollTop();
1320
1321 $window.on('resize scrollstop', {latency: 350}, function () {
1322 var visibleChunks = $('.nav-chunk').withinviewport({top: 75});