##// END OF EJS Templates
release: merge back stable branch into default
marcink -
r334:8c44af47 merge default
parent child Browse files
Show More
@@ -0,0 +1,14 b''
1 |RCE| 4.2.1 |RNS|
2 -----------------
3
4 Release Date
5 ^^^^^^^^^^^^
6
7 - 2016-07-04
8
9 Fixes
10 ^^^^^
11
12 - ui: fixed empty labels caused by missing translation of JS components
13 - login: fixed bad routing URL in comments when user is not logged in.
14 - celery: make sure to run tasks in sync mode if connection to celery is lost.
@@ -0,0 +1,36 b''
1 // # Copyright (C) 2016-2016 RhodeCode GmbH
2 // #
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
6 // #
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 i18nLog = Logger.get('i18n');
20
21 var _gettext = function (s) {
22 if (_TM.hasOwnProperty(s)) {
23 return _TM[s];
24 }
25 i18nLog.error(
26 'String `' + s + '` was requested but cannot be ' +
27 'found in translation table');
28 return s
29 };
30
31 var _ngettext = function (singular, plural, n) {
32 if (n === 1) {
33 return _gettext(singular)
34 }
35 return _gettext(plural)
36 };
@@ -1,6 +1,7 b''
1 1 1bd3e92b7e2e2d2024152b34bb88dff1db544a71 v4.0.0
2 2 170c5398320ea6cddd50955e88d408794c21d43a v4.0.1
3 3 c3fe200198f5aa34cf2e4066df2881a9cefe3704 v4.1.0
4 4 7fd5c850745e2ea821fb4406af5f4bff9b0a7526 v4.1.1
5 5 41c87da28a179953df86061d817bc35533c66dd2 v4.1.2
6 6 baaf9f5bcea3bae0ef12ae20c8b270482e62abb6 v4.2.0
7 32a70c7e56844a825f61df496ee5eaf8c3c4e189 v4.2.1
@@ -1,138 +1,139 b''
1 1 module.exports = function(grunt) {
2 2 grunt.initConfig({
3 3
4 4 dirs: {
5 5 css: "rhodecode/public/css",
6 6 js: {
7 7 "src": "rhodecode/public/js/src",
8 8 "dest": "rhodecode/public/js"
9 9 }
10 10 },
11 11
12 12 concat: {
13 13 dist: {
14 14 src: [
15 15 // Base libraries
16 16 '<%= dirs.js.src %>/jquery-1.11.1.min.js',
17 17 '<%= dirs.js.src %>/logging.js',
18 18 '<%= dirs.js.src %>/bootstrap.js',
19 19 '<%= dirs.js.src %>/mousetrap.js',
20 20 '<%= dirs.js.src %>/moment.js',
21 21 '<%= dirs.js.src %>/appenlight-client-0.4.1.min.js',
22 '<%= dirs.js.src %>/i18n_utils.js',
22 23
23 24 // Plugins
24 25 '<%= dirs.js.src %>/plugins/jquery.pjax.js',
25 26 '<%= dirs.js.src %>/plugins/jquery.dataTables.js',
26 27 '<%= dirs.js.src %>/plugins/flavoured_checkbox.js',
27 28 '<%= dirs.js.src %>/plugins/jquery.auto-grow-input.js',
28 29 '<%= dirs.js.src %>/plugins/jquery.autocomplete.js',
29 30 '<%= dirs.js.src %>/plugins/jquery.debounce.js',
30 31 '<%= dirs.js.src %>/plugins/jquery.mark.js',
31 32 '<%= dirs.js.src %>/plugins/jquery.timeago.js',
32 33 '<%= dirs.js.src %>/plugins/jquery.timeago-extension.js',
33 34
34 35 // Select2
35 36 '<%= dirs.js.src %>/select2/select2.js',
36 37
37 38 // Code-mirror
38 39 '<%= dirs.js.src %>/codemirror/codemirror.js',
39 40 '<%= dirs.js.src %>/codemirror/codemirror_loadmode.js',
40 41 '<%= dirs.js.src %>/codemirror/codemirror_hint.js',
41 42 '<%= dirs.js.src %>/codemirror/codemirror_overlay.js',
42 43 '<%= dirs.js.src %>/codemirror/codemirror_placeholder.js',
43 44 // TODO: mikhail: this is an exception. Since the code mirror modes
44 45 // are loaded "on the fly", we need to keep them in a public folder
45 46 '<%= dirs.js.dest %>/mode/meta.js',
46 47 '<%= dirs.js.dest %>/mode/meta_ext.js',
47 48 '<%= dirs.js.dest %>/rhodecode/i18n/select2/translations.js',
48 49
49 50 // Rhodecode utilities
50 51 '<%= dirs.js.src %>/rhodecode/utils/array.js',
51 52 '<%= dirs.js.src %>/rhodecode/utils/string.js',
52 53 '<%= dirs.js.src %>/rhodecode/utils/pyroutes.js',
53 54 '<%= dirs.js.src %>/rhodecode/utils/ajax.js',
54 55 '<%= dirs.js.src %>/rhodecode/utils/autocomplete.js',
55 56 '<%= dirs.js.src %>/rhodecode/utils/colorgenerator.js',
56 57 '<%= dirs.js.src %>/rhodecode/utils/ie.js',
57 58 '<%= dirs.js.src %>/rhodecode/utils/os.js',
58 59
59 60 // Rhodecode widgets
60 61 '<%= dirs.js.src %>/rhodecode/widgets/multiselect.js',
61 62
62 63 // Rhodecode components
63 64 '<%= dirs.js.src %>/rhodecode/init.js',
64 65 '<%= dirs.js.src %>/rhodecode/codemirror.js',
65 66 '<%= dirs.js.src %>/rhodecode/comments.js',
66 67 '<%= dirs.js.src %>/rhodecode/constants.js',
67 68 '<%= dirs.js.src %>/rhodecode/files.js',
68 69 '<%= dirs.js.src %>/rhodecode/followers.js',
69 70 '<%= dirs.js.src %>/rhodecode/menus.js',
70 71 '<%= dirs.js.src %>/rhodecode/notifications.js',
71 72 '<%= dirs.js.src %>/rhodecode/permissions.js',
72 73 '<%= dirs.js.src %>/rhodecode/pjax.js',
73 74 '<%= dirs.js.src %>/rhodecode/pullrequests.js',
74 75 '<%= dirs.js.src %>/rhodecode/settings.js',
75 76 '<%= dirs.js.src %>/rhodecode/select2_widgets.js',
76 77 '<%= dirs.js.src %>/rhodecode/tooltips.js',
77 78 '<%= dirs.js.src %>/rhodecode/users.js',
78 79 '<%= dirs.js.src %>/rhodecode/appenlight.js',
79 80
80 81 // Rhodecode main module
81 82 '<%= dirs.js.src %>/rhodecode.js'
82 83 ],
83 84 dest: '<%= dirs.js.dest %>/scripts.js',
84 85 nonull: true
85 86 }
86 87 },
87 88
88 89 less: {
89 90 development: {
90 91 options: {
91 92 compress: false,
92 93 yuicompress: false,
93 94 optimization: 0
94 95 },
95 96 files: {
96 97 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
97 98 }
98 99 },
99 100 production: {
100 101 options: {
101 102 compress: true,
102 103 yuicompress: true,
103 104 optimization: 2
104 105 },
105 106 files: {
106 107 "<%= dirs.css %>/style.css": "<%= dirs.css %>/main.less"
107 108 }
108 109 }
109 110 },
110 111
111 112 watch: {
112 113 less: {
113 114 files: ["<%= dirs.css %>/*.less"],
114 115 tasks: ["less:production"]
115 116 },
116 117 js: {
117 118 files: ["<%= dirs.js.src %>/**/*.js"],
118 119 tasks: ["concat:dist"]
119 120 }
120 121 },
121 122
122 123 jshint: {
123 124 rhodecode: {
124 125 src: '<%= dirs.js.src %>/rhodecode/**/*.js',
125 126 options: {
126 127 jshintrc: '.jshintrc'
127 128 }
128 129 }
129 130 }
130 131 });
131 132
132 133 grunt.loadNpmTasks('grunt-contrib-less');
133 134 grunt.loadNpmTasks('grunt-contrib-concat');
134 135 grunt.loadNpmTasks('grunt-contrib-watch');
135 136 grunt.loadNpmTasks('grunt-contrib-jshint');
136 137
137 138 grunt.registerTask('default', ['less:production', 'concat:dist']);
138 139 };
@@ -1,83 +1,84 b''
1 1 .. _rhodecode-release-notes-ref:
2 2
3 3 Release Notes
4 4 =============
5 5
6 6 |RCE| 4.x Versions
7 7 ------------------
8 8
9 9 .. toctree::
10 10 :maxdepth: 1
11 11
12 release-notes-4.2.1.rst
12 13 release-notes-4.2.0.rst
13 14 release-notes-4.1.2.rst
14 15 release-notes-4.1.1.rst
15 16 release-notes-4.1.0.rst
16 17 release-notes-4.0.1.rst
17 18 release-notes-4.0.0.rst
18 19
19 20 |RCE| 3.x Versions
20 21 ------------------
21 22
22 23 .. toctree::
23 24 :maxdepth: 1
24 25
25 26 release-notes-3.8.4.rst
26 27 release-notes-3.8.3.rst
27 28 release-notes-3.8.2.rst
28 29 release-notes-3.8.1.rst
29 30 release-notes-3.8.0.rst
30 31 release-notes-3.7.1.rst
31 32 release-notes-3.7.0.rst
32 33 release-notes-3.6.1.rst
33 34 release-notes-3.6.0.rst
34 35 release-notes-3.5.2.rst
35 36 release-notes-3.5.1.rst
36 37 release-notes-3.5.0.rst
37 38 release-notes-3.4.1.rst
38 39 release-notes-3.4.0.rst
39 40 release-notes-3.3.4.rst
40 41 release-notes-3.3.3.rst
41 42 release-notes-3.3.2.rst
42 43 release-notes-3.3.1.rst
43 44 release-notes-3.3.0.rst
44 45 release-notes-3.2.3.rst
45 46 release-notes-3.2.2.rst
46 47 release-notes-3.2.1.rst
47 48 release-notes-3.2.0.rst
48 49 release-notes-3.1.1.rst
49 50 release-notes-3.1.0.rst
50 51 release-notes-3.0.2.rst
51 52 release-notes-3.0.1.rst
52 53 release-notes-3.0.0.rst
53 54
54 55 |RCE| 2.x Versions
55 56 ------------------
56 57
57 58 .. toctree::
58 59 :maxdepth: 1
59 60
60 61 release-notes-2.2.8.rst
61 62 release-notes-2.2.7.rst
62 63 release-notes-2.2.6.rst
63 64 release-notes-2.2.5.rst
64 65 release-notes-2.2.4.rst
65 66 release-notes-2.2.3.rst
66 67 release-notes-2.2.2.rst
67 68 release-notes-2.2.1.rst
68 69 release-notes-2.2.0.rst
69 70 release-notes-2.1.0.rst
70 71 release-notes-2.0.2.rst
71 72 release-notes-2.0.1.rst
72 73 release-notes-2.0.0.rst
73 74
74 75 |RCE| 1.x Versions
75 76 ------------------
76 77
77 78 .. toctree::
78 79 :maxdepth: 1
79 80
80 81 release-notes-1.7.2.rst
81 82 release-notes-1.7.1.rst
82 83 release-notes-1.7.0.rst
83 84 release-notes-1.6.0.rst
@@ -1,34 +1,43 b''
1 1 // translate select2 components
2 2 select2Locales = {
3 3 formatLoadMore: function(pageNumber) {
4 return _TM["Loading more results..."];
4 return _gettext("Loading more results...");
5 5 },
6 6 formatSearching: function() {
7 return _TM["Searching..."];
7 return _gettext("Searching...");
8 8 },
9 9 formatNoMatches: function() {
10 return _TM["No matches found"];
10 return _gettext("No matches found");
11 11 },
12 12 formatAjaxError: function(jqXHR, textStatus, errorThrown) {
13 return _TM["Loading failed"];
13 return _gettext("Loading failed");
14 14 },
15 15 formatMatches: function(matches) {
16 16 if (matches === 1) {
17 return _TM["One result is available, press enter to select it."];
17 return _gettext("One result is available, press enter to select it.");
18 18 }
19 return _TM["{0} results are available, use up and down arrow keys to navigate."].format(matches);
19 return _gettext("{0} results are available, use up and down arrow keys to navigate.").format(matches);
20 20 },
21 21 formatInputTooShort: function(input, min) {
22 22 var n = min - input.length;
23 return "Please enter {0} or more character".format(n) + (n === 1 ? "" : "s");
23 if (n === 1) {
24 return _gettext("Please enter {0} or more character").format(n);
25 }
26 return _gettext("Please enter {0} or more characters").format(n);
24 27 },
25 28 formatInputTooLong: function(input, max) {
26 29 var n = input.length - max;
27 return "Please delete {0} character".format(n) + (n === 1 ? "" : "s");
30 if (n === 1) {
31 return _gettext("Please delete {0} character").format(n);
32 }
33 return _gettext("Please delete {0} characters").format(n);
28 34 },
29 35 formatSelectionTooBig: function(limit) {
30 return "You can only select {0} item".format(limit) + (limit === 1 ? "" : "s");
36 if (limit === 1) {
37 return _gettext("You can only select {0} item").format(limit);
38 }
39 return _gettext("You can only select {0} items").format(limit);
31 40 }
32 41 };
33 42
34 43 $.extend($.fn.select2.defaults, select2Locales);
@@ -1,936 +1,936 b''
1 1 /**
2 2 * Ajax Autocomplete for jQuery, version dev
3 3 * RhodeCode additions
4 4 * (c) 2014 Tomas Kirda
5 5 * (c) 2014 Marcin Kuzminski
6 6 *
7 7 * Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license.
8 8 * For details, see the web site: https://github.com/devbridge/jQuery-Autocomplete
9 9 */
10 10 // Expose plugin as an AMD module if AMD loader is present:
11 11 (function (factory) {
12 12 'use strict';
13 13 if (typeof define === 'function' && define.amd) {
14 14 // AMD. Register as an anonymous module.
15 15 define(['jquery'], factory);
16 16 } else if (typeof exports === 'object' && typeof require === 'function') {
17 17 // Browserify
18 18 factory(require('jquery'));
19 19 } else {
20 20 // Browser globals
21 21 factory(jQuery);
22 22 }
23 23 }(function ($) {
24 24 'use strict';
25 25
26 26 var
27 27 utils = (function () {
28 28 return {
29 29 escapeRegExChars: function (value) {
30 30 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
31 31 },
32 32 createNode: function (containerClass) {
33 33 var div = document.createElement('div');
34 34 div.className = containerClass;
35 35 div.style.position = 'absolute';
36 36 div.style.display = 'none';
37 37 return div;
38 38 }
39 39 };
40 40 }()),
41 41
42 42 keys = {
43 43 ESC: 27,
44 44 TAB: 9,
45 45 RETURN: 13,
46 46 LEFT: 37,
47 47 UP: 38,
48 48 RIGHT: 39,
49 49 DOWN: 40
50 50 };
51 51
52 52 function Autocomplete(el, options) {
53 53 var noop = function () { },
54 54 that = this,
55 55 defaults = {
56 56 ajaxSettings: {},
57 57 autoSelectFirst: false,
58 58 appendTo: document.body,
59 59 serviceUrl: null,
60 60 lookup: null,
61 61 width: 'auto',
62 62 minChars: 1,
63 63 maxHeight: 300,
64 64 deferRequestBy: 0,
65 65 params: {},
66 66 formatResult: Autocomplete.formatResult,
67 67 lookupFilter: Autocomplete.lookupFilter,
68 68 delimiter: null,
69 69 zIndex: 9999,
70 70 type: 'GET',
71 71 noCache: false,
72 72 onSelect: noop,
73 73 onSearchStart: noop,
74 74 onSearchComplete: noop,
75 75 onSearchError: noop,
76 76 containerClass: 'autocomplete-suggestions',
77 77 tabDisabled: false,
78 78 dataType: 'text',
79 79 currentRequest: null,
80 80 triggerSelectOnValidInput: false,
81 81 preventBadQueries: true,
82 82 paramName: 'query',
83 83 transformResult: function (response) {
84 84 return typeof response === 'string' ? $.parseJSON(response) : response;
85 85 },
86 86 showNoSuggestionNotice: false,
87 noSuggestionNotice: _TM['No results'],
87 noSuggestionNotice: _gettext('No results'),
88 88 orientation: 'bottom',
89 89 forceFixPosition: false,
90 90 replaceOnArrowKey: true
91 91 };
92 92
93 93 // Shared variables:
94 94 that.element = el;
95 95 that.el = $(el);
96 96 that.suggestions = [];
97 97 that.badQueries = [];
98 98 that.selectedIndex = -1;
99 99 that.currentValue = that.element.value;
100 100 that.intervalId = 0;
101 101 that.cachedResponse = {};
102 102 that.onChangeInterval = null;
103 103 that.onChange = null;
104 104 that.isLocal = false;
105 105 that.suggestionsContainer = null;
106 106 that.noSuggestionsContainer = null;
107 107 that.options = $.extend({}, defaults, options);
108 108 that.classes = {
109 109 selected: 'autocomplete-selected',
110 110 suggestion: 'autocomplete-suggestion'
111 111 };
112 112 that.hint = null;
113 113 that.hintValue = '';
114 114 that.selection = null;
115 115
116 116 // Initialize and set options:
117 117 that.initialize();
118 118 that.setOptions(options);
119 119 }
120 120
121 121 Autocomplete.utils = utils;
122 122
123 123 $.Autocomplete = Autocomplete;
124 124
125 125 Autocomplete.formatResult = function (suggestion, currentValue) {
126 126 var pattern = '(' + utils.escapeRegExChars(currentValue) + ')';
127 127 return suggestion.value.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
128 128 };
129 129 Autocomplete.lookupFilter = function (suggestion, originalQuery, queryLowerCase) {
130 130 return suggestion.value.toLowerCase().indexOf(queryLowerCase) !== -1;
131 131 };
132 132
133 133 Autocomplete.prototype = {
134 134
135 135 killerFn: null,
136 136
137 137 initialize: function () {
138 138 var that = this,
139 139 suggestionSelector = '.' + that.classes.suggestion,
140 140 selected = that.classes.selected,
141 141 options = that.options,
142 142 container;
143 143
144 144 // Remove autocomplete attribute to prevent native suggestions:
145 145 that.element.setAttribute('autocomplete', 'off');
146 146
147 147 that.killerFn = function (e) {
148 148 if ($(e.target).closest('.' + that.options.containerClass).length === 0) {
149 149 that.killSuggestions();
150 150 that.disableKillerFn();
151 151 }
152 152 };
153 153
154 154 // html() deals with many types: htmlString or Element or Array or jQuery
155 155 that.noSuggestionsContainer = $('<div class="autocomplete-no-suggestion"></div>')
156 156 .html(this.options.noSuggestionNotice).get(0);
157 157
158 158 that.suggestionsContainer = Autocomplete.utils.createNode(options.containerClass);
159 159
160 160 container = $(that.suggestionsContainer);
161 161
162 162 container.appendTo(options.appendTo);
163 163
164 164 // Only set width if it was provided:
165 165 if (options.width !== 'auto') {
166 166 container.width(options.width);
167 167 }
168 168
169 169 // Listen for mouse over event on suggestions list:
170 170 container.on('mouseover.autocomplete', suggestionSelector, function () {
171 171 that.activate($(this).data('index'));
172 172 });
173 173
174 174 // Deselect active element when mouse leaves suggestions container:
175 175 container.on('mouseout.autocomplete', function () {
176 176 that.selectedIndex = -1;
177 177 container.children('.' + selected).removeClass(selected);
178 178 });
179 179
180 180 // Listen for click event on suggestions list:
181 181 container.on('click.autocomplete', suggestionSelector, function () {
182 182 that.select($(this).data('index'));
183 183 });
184 184
185 185 that.fixPositionCapture = function () {
186 186 if (that.visible) {
187 187 that.fixPosition();
188 188 }
189 189 };
190 190
191 191 $(window).on('resize.autocomplete', that.fixPositionCapture);
192 192
193 193 that.el.on('keydown.autocomplete', function (e) { that.onKeyPress(e); });
194 194 that.el.on('keyup.autocomplete', function (e) { that.onKeyUp(e); });
195 195 that.el.on('blur.autocomplete', function () { that.onBlur(); });
196 196 that.el.on('focus.autocomplete', function () { that.onFocus(); });
197 197 that.el.on('change.autocomplete', function (e) { that.onKeyUp(e); });
198 198 },
199 199
200 200 onFocus: function () {
201 201 var that = this;
202 202 that.fixPosition();
203 203 if (that.options.minChars <= that.el.val().length) {
204 204 that.onValueChange();
205 205 }
206 206 },
207 207
208 208 onBlur: function () {
209 209 this.enableKillerFn();
210 210 },
211 211
212 212 setOptions: function (suppliedOptions) {
213 213 var that = this,
214 214 options = that.options;
215 215
216 216 $.extend(options, suppliedOptions);
217 217
218 218 that.isLocal = $.isArray(options.lookup);
219 219
220 220 if (that.isLocal) {
221 221 options.lookup = that.verifySuggestionsFormat(options.lookup);
222 222 }
223 223
224 224 options.orientation = that.validateOrientation(options.orientation, 'bottom');
225 225
226 226 // Adjust height, width and z-index:
227 227 $(that.suggestionsContainer).css({
228 228 'max-height': options.maxHeight + 'px',
229 229 'width': options.width + 'px',
230 230 'z-index': options.zIndex
231 231 });
232 232 },
233 233
234 234 clearCache: function () {
235 235 this.cachedResponse = {};
236 236 this.badQueries = [];
237 237 },
238 238
239 239 clear: function () {
240 240 this.clearCache();
241 241 this.currentValue = '';
242 242 this.suggestions = [];
243 243 },
244 244
245 245 disable: function () {
246 246 var that = this;
247 247 that.disabled = true;
248 248 if (that.currentRequest) {
249 249 that.currentRequest.abort();
250 250 }
251 251 },
252 252
253 253 enable: function () {
254 254 this.disabled = false;
255 255 },
256 256
257 257 fixPosition: function () {
258 258 // Use only when container has already its content
259 259
260 260 var that = this,
261 261 $container = $(that.suggestionsContainer),
262 262 containerParent = $container.parent().get(0);
263 263 // Fix position automatically when appended to body.
264 264 // In other cases force parameter must be given.
265 265 if (containerParent !== document.body && !that.options.forceFixPosition)
266 266 return;
267 267
268 268 // Choose orientation
269 269 var orientation = that.options.orientation,
270 270 containerHeight = $container.outerHeight(),
271 271 height = that.el.outerHeight(),
272 272 offset = that.el.offset(),
273 273 styles = { 'top': offset.top, 'left': offset.left };
274 274
275 275 if (orientation == 'auto') {
276 276 var viewPortHeight = $(window).height(),
277 277 scrollTop = $(window).scrollTop(),
278 278 topOverflow = -scrollTop + offset.top - containerHeight,
279 279 bottomOverflow = scrollTop + viewPortHeight - (offset.top + height + containerHeight);
280 280
281 281 orientation = (Math.max(topOverflow, bottomOverflow) === topOverflow)
282 282 ? 'top'
283 283 : 'bottom';
284 284 }
285 285
286 286 if (orientation === 'top') {
287 287 styles.top += -containerHeight;
288 288 } else {
289 289 styles.top += height;
290 290 }
291 291
292 292 // If container is not positioned to body,
293 293 // correct its position using offset parent offset
294 294 if(containerParent !== document.body) {
295 295 var opacity = $container.css('opacity'),
296 296 parentOffsetDiff;
297 297
298 298 if (!that.visible){
299 299 $container.css('opacity', 0).show();
300 300 }
301 301
302 302 parentOffsetDiff = $container.offsetParent().offset();
303 303 styles.top -= parentOffsetDiff.top;
304 304 styles.left -= parentOffsetDiff.left;
305 305
306 306 if (!that.visible){
307 307 $container.css('opacity', opacity).hide();
308 308 }
309 309 }
310 310
311 311 // -2px to account for suggestions border.
312 312 if (that.options.width === 'auto') {
313 313 styles.width = (that.el.outerWidth() - 2) + 'px';
314 314 }
315 315
316 316 $container.css(styles);
317 317 },
318 318
319 319 enableKillerFn: function () {
320 320 var that = this;
321 321 $(document).on('click.autocomplete', that.killerFn);
322 322 },
323 323
324 324 disableKillerFn: function () {
325 325 var that = this;
326 326 $(document).off('click.autocomplete', that.killerFn);
327 327 },
328 328
329 329 killSuggestions: function () {
330 330 var that = this;
331 331 that.stopKillSuggestions();
332 332 that.intervalId = window.setInterval(function () {
333 333 that.hide();
334 334 that.stopKillSuggestions();
335 335 }, 50);
336 336 },
337 337
338 338 stopKillSuggestions: function () {
339 339 window.clearInterval(this.intervalId);
340 340 },
341 341
342 342 isCursorAtEnd: function () {
343 343 var that = this,
344 344 valLength = that.el.val().length,
345 345 selectionStart = that.element.selectionStart,
346 346 range;
347 347
348 348 if (typeof selectionStart === 'number') {
349 349 return selectionStart === valLength;
350 350 }
351 351 if (document.selection) {
352 352 range = document.selection.createRange();
353 353 range.moveStart('character', -valLength);
354 354 return valLength === range.text.length;
355 355 }
356 356 return true;
357 357 },
358 358
359 359 onKeyPress: function (e) {
360 360 var that = this;
361 361
362 362 // If suggestions are hidden and user presses arrow down, display suggestions:
363 363 if (!that.disabled && !that.visible && e.which === keys.DOWN && that.currentValue) {
364 364 that.suggest();
365 365 return;
366 366 }
367 367
368 368 if (that.disabled || !that.visible) {
369 369 return;
370 370 }
371 371
372 372 switch (e.which) {
373 373 case keys.ESC:
374 374 that.el.val(that.currentValue);
375 375 that.hide();
376 376 break;
377 377 case keys.RIGHT:
378 378 if (that.hint && that.options.onHint && that.isCursorAtEnd()) {
379 379 that.selectHint();
380 380 break;
381 381 }
382 382 return;
383 383 case keys.TAB:
384 384 if (that.hint && that.options.onHint) {
385 385 that.selectHint();
386 386 return;
387 387 }
388 388 // Fall through to RETURN
389 389 case keys.RETURN:
390 390 if (that.selectedIndex === -1) {
391 391 that.hide();
392 392 return;
393 393 }
394 394 that.select(that.selectedIndex);
395 395 if (e.which === keys.TAB && that.options.tabDisabled === false) {
396 396 return;
397 397 }
398 398 break;
399 399 case keys.UP:
400 400 that.moveUp();
401 401 break;
402 402 case keys.DOWN:
403 403 that.moveDown();
404 404 break;
405 405 default:
406 406 return;
407 407 }
408 408
409 409 // Cancel event if function did not return:
410 410 e.stopImmediatePropagation();
411 411 e.preventDefault();
412 412 },
413 413
414 414 onKeyUp: function (e) {
415 415 var that = this;
416 416
417 417 if (that.disabled) {
418 418 return;
419 419 }
420 420
421 421 switch (e.which) {
422 422 case keys.UP:
423 423 case keys.DOWN:
424 424 return;
425 425 }
426 426
427 427 clearInterval(that.onChangeInterval);
428 428
429 429 if (that.currentValue !== that.el.val()) {
430 430 that.findBestHint();
431 431 if (that.options.deferRequestBy > 0) {
432 432 // Defer lookup in case when value changes very quickly:
433 433 that.onChangeInterval = setInterval(function () {
434 434 that.onValueChange();
435 435 }, that.options.deferRequestBy);
436 436 } else {
437 437 that.onValueChange();
438 438 }
439 439 }
440 440 },
441 441
442 442 onValueChange: function () {
443 443 var that = this,
444 444 options = that.options,
445 445 value = that.el.val(),
446 446 query = that.getQuery(value),
447 447 index;
448 448
449 449 if (that.selection && that.currentValue !== query) {
450 450 that.selection = null;
451 451 (options.onInvalidateSelection || $.noop).call(that.element);
452 452 }
453 453
454 454 clearInterval(that.onChangeInterval);
455 455 that.currentValue = value;
456 456 that.selectedIndex = -1;
457 457
458 458 // Check existing suggestion for the match before proceeding:
459 459 if (options.triggerSelectOnValidInput) {
460 460 index = that.findSuggestionIndex(query);
461 461 if (index !== -1) {
462 462 that.select(index);
463 463 return;
464 464 }
465 465 }
466 466
467 467 if (query.length < options.minChars) {
468 468 that.hide();
469 469 } else {
470 470 that.getSuggestions(query);
471 471 }
472 472 },
473 473
474 474 findSuggestionIndex: function (query) {
475 475 var that = this,
476 476 index = -1,
477 477 queryLowerCase = query.toLowerCase();
478 478
479 479 $.each(that.suggestions, function (i, suggestion) {
480 480 if (suggestion.value.toLowerCase() === queryLowerCase) {
481 481 index = i;
482 482 return false;
483 483 }
484 484 });
485 485
486 486 return index;
487 487 },
488 488
489 489 getQuery: function (value) {
490 490 var delimiter = this.options.delimiter,
491 491 parts;
492 492
493 493 if (!delimiter) {
494 494 return value;
495 495 }
496 496 parts = value.split(delimiter);
497 497 return $.trim(parts[parts.length - 1]);
498 498 },
499 499
500 500 getSuggestionsLocal: function (query) {
501 501 var that = this,
502 502 options = that.options,
503 503 queryLowerCase = query.toLowerCase(),
504 504 data;
505 505
506 506 // re-pack the data as it was comming from AJAX
507 507 data = {
508 508 suggestions: data
509 509 };
510 510 return data;
511 511 },
512 512
513 513 getSuggestions: function (query) {
514 514 var response,
515 515 that = this,
516 516 options = that.options,
517 517 serviceUrl = options.serviceUrl,
518 518 params,
519 519 cacheKey,
520 520 ajaxSettings;
521 521
522 522 options.params[options.paramName] = query;
523 523 params = options.ignoreParams ? null : options.params;
524 524
525 525 if (that.isLocal) {
526 526 response = that.getSuggestionsLocal(query);
527 527 } else {
528 528 if ($.isFunction(serviceUrl)) {
529 529 serviceUrl = serviceUrl.call(that.element, query);
530 530 }
531 531 cacheKey = serviceUrl + '?' + $.param(params || {});
532 532 response = that.cachedResponse[cacheKey];
533 533 }
534 534
535 535 if (response && $.isArray(response.suggestions)) {
536 536 that.suggestions = response.suggestions;
537 537 that.suggest();
538 538 } else if (!that.isBadQuery(query)) {
539 539 if (options.onSearchStart.call(that.element, options.params) === false) {
540 540 return;
541 541 }
542 542 if (that.currentRequest) {
543 543 that.currentRequest.abort();
544 544 }
545 545
546 546 ajaxSettings = {
547 547 url: serviceUrl,
548 548 data: params,
549 549 type: options.type,
550 550 dataType: options.dataType
551 551 };
552 552
553 553 $.extend(ajaxSettings, options.ajaxSettings);
554 554
555 555 that.currentRequest = $.ajax(ajaxSettings).done(function (data) {
556 556 var result;
557 557 that.currentRequest = null;
558 558 result = options.transformResult(data);
559 559 that.processResponse(result, query, cacheKey);
560 560 options.onSearchComplete.call(that.element, query, result.suggestions);
561 561 }).fail(function (jqXHR, textStatus, errorThrown) {
562 562 options.onSearchError.call(that.element, query, jqXHR, textStatus, errorThrown);
563 563 });
564 564 }
565 565 },
566 566
567 567 isBadQuery: function (q) {
568 568 if (!this.options.preventBadQueries){
569 569 return false;
570 570 }
571 571
572 572 var badQueries = this.badQueries,
573 573 i = badQueries.length;
574 574
575 575 while (i--) {
576 576 if (q.indexOf(badQueries[i]) === 0) {
577 577 return true;
578 578 }
579 579 }
580 580
581 581 return false;
582 582 },
583 583
584 584 hide: function () {
585 585 var that = this;
586 586 that.visible = false;
587 587 that.selectedIndex = -1;
588 588 $(that.suggestionsContainer).hide();
589 589 that.signalHint(null);
590 590 },
591 591
592 592 suggest: function () {
593 593
594 594 var that = this,
595 595 options = that.options,
596 596 formatResult = options.formatResult,
597 597 filterResult = options.lookupFilter,
598 598 value = that.getQuery(that.currentValue),
599 599 className = that.classes.suggestion,
600 600 classSelected = that.classes.selected,
601 601 container = $(that.suggestionsContainer),
602 602 noSuggestionsContainer = $(that.noSuggestionsContainer),
603 603 beforeRender = options.beforeRender,
604 604 limit = parseInt(that.options.lookupLimit, 10),
605 605 html = '',
606 606 index;
607 607
608 608 // filter and limit given results
609 609 var filtered_suggestions = $.grep(that.suggestions, function (suggestion) {
610 610 return filterResult(suggestion, value, value.toLowerCase(), that.element);
611 611 });
612 612
613 613 if (limit && filtered_suggestions.length > limit) {
614 614 filtered_suggestions = filtered_suggestions.slice(0, limit);
615 615 }
616 616
617 617 if (filtered_suggestions.length === 0) {
618 618 this.options.showNoSuggestionNotice ? this.noSuggestions() : this.hide();
619 619 return;
620 620 }
621 621
622 622 if (options.triggerSelectOnValidInput) {
623 623 index = that.findSuggestionIndex(value);
624 624 if (index !== -1) {
625 625 that.select(index);
626 626 return;
627 627 }
628 628 }
629 629
630 630 // Build suggestions inner HTML:
631 631 $.each(filtered_suggestions, function (i, suggestion) {
632 632 html += '<div class="' + className + '" data-index="' + i + '">' + formatResult(suggestion, value, Autocomplete.formatResult, that.element) + '</div>';
633 633 });
634 634 // set internal suggestion for INDEX pick to work correctly
635 635 that.suggestions = filtered_suggestions;
636 636 this.adjustContainerWidth();
637 637
638 638 noSuggestionsContainer.detach();
639 639 container.html(html);
640 640
641 641 // Select first value by default:
642 642 if (options.autoSelectFirst) {
643 643 that.selectedIndex = 0;
644 644 container.children().first().addClass(classSelected);
645 645 }
646 646
647 647 if ($.isFunction(beforeRender)) {
648 648 beforeRender.call(that.element, container);
649 649 }
650 650
651 651 that.fixPosition();
652 652
653 653 container.show();
654 654 that.visible = true;
655 655
656 656 that.findBestHint();
657 657 },
658 658
659 659 noSuggestions: function() {
660 660 var that = this,
661 661 container = $(that.suggestionsContainer),
662 662 noSuggestionsContainer = $(that.noSuggestionsContainer);
663 663
664 664 this.adjustContainerWidth();
665 665
666 666 // Some explicit steps. Be careful here as it easy to get
667 667 // noSuggestionsContainer removed from DOM if not detached properly.
668 668 noSuggestionsContainer.detach();
669 669 container.empty(); // clean suggestions if any
670 670 container.append(noSuggestionsContainer);
671 671
672 672 that.fixPosition();
673 673
674 674 container.show();
675 675 that.visible = true;
676 676 },
677 677
678 678 adjustContainerWidth: function() {
679 679 var that = this,
680 680 options = that.options,
681 681 width,
682 682 container = $(that.suggestionsContainer);
683 683
684 684 // If width is auto, adjust width before displaying suggestions,
685 685 // because if instance was created before input had width, it will be zero.
686 686 // Also it adjusts if input width has changed.
687 687 // -2px to account for suggestions border.
688 688 if (options.width === 'auto') {
689 689 width = that.el.outerWidth() - 2;
690 690 container.width(width > 0 ? width : 300);
691 691 }
692 692 },
693 693
694 694 findBestHint: function () {
695 695 var that = this,
696 696 value = that.el.val().toLowerCase(),
697 697 bestMatch = null;
698 698
699 699 if (!value) {
700 700 return;
701 701 }
702 702
703 703 $.each(that.suggestions, function (i, suggestion) {
704 704 var foundMatch = suggestion.value.toLowerCase().indexOf(value) === 0;
705 705 if (foundMatch) {
706 706 bestMatch = suggestion;
707 707 }
708 708 return !foundMatch;
709 709 });
710 710 that.signalHint(bestMatch);
711 711 },
712 712
713 713 signalHint: function (suggestion) {
714 714 var hintValue = '',
715 715 that = this;
716 716 if (suggestion) {
717 717 hintValue = that.currentValue + suggestion.value.substr(that.currentValue.length);
718 718 }
719 719 if (that.hintValue !== hintValue) {
720 720 that.hintValue = hintValue;
721 721 that.hint = suggestion;
722 722 (this.options.onHint || $.noop)(hintValue);
723 723 }
724 724 },
725 725
726 726 verifySuggestionsFormat: function (suggestions) {
727 727 // If suggestions is string array, convert them to supported format:
728 728 if (suggestions.length && typeof suggestions[0] === 'string') {
729 729 return $.map(suggestions, function (value) {
730 730 return { value: value, data: null };
731 731 });
732 732 }
733 733
734 734 return suggestions;
735 735 },
736 736
737 737 validateOrientation: function(orientation, fallback) {
738 738 orientation = $.trim(orientation || '').toLowerCase();
739 739
740 740 if($.inArray(orientation, ['auto', 'bottom', 'top']) === -1){
741 741 orientation = fallback;
742 742 }
743 743
744 744 return orientation;
745 745 },
746 746
747 747 processResponse: function (result, originalQuery, cacheKey) {
748 748 var that = this,
749 749 options = that.options;
750 750
751 751 result.suggestions = that.verifySuggestionsFormat(result.suggestions);
752 752
753 753 // Cache results if cache is not disabled:
754 754 if (!options.noCache) {
755 755 that.cachedResponse[cacheKey] = result;
756 756 if (options.preventBadQueries && result.suggestions.length === 0) {
757 757 that.badQueries.push(originalQuery);
758 758 }
759 759 }
760 760
761 761 // Return if originalQuery is not matching current query:
762 762 if (originalQuery !== that.getQuery(that.currentValue)) {
763 763 return;
764 764 }
765 765
766 766 that.suggestions = result.suggestions;
767 767 that.suggest();
768 768 },
769 769
770 770 activate: function (index) {
771 771 var that = this,
772 772 activeItem,
773 773 selected = that.classes.selected,
774 774 container = $(that.suggestionsContainer),
775 775 children = container.find('.' + that.classes.suggestion);
776 776
777 777 container.find('.' + selected).removeClass(selected);
778 778
779 779 that.selectedIndex = index;
780 780
781 781 if (that.selectedIndex !== -1 && children.length > that.selectedIndex) {
782 782 activeItem = children.get(that.selectedIndex);
783 783 $(activeItem).addClass(selected);
784 784 return activeItem;
785 785 }
786 786
787 787 return null;
788 788 },
789 789
790 790 selectHint: function () {
791 791 var that = this,
792 792 i = $.inArray(that.hint, that.suggestions);
793 793 that.select(i);
794 794 },
795 795
796 796 select: function (index) {
797 797 var that = this;
798 798 that.hide();
799 799 that.onSelect(index);
800 800 },
801 801
802 802 moveUp: function () {
803 803 var that = this;
804 804
805 805 if (that.selectedIndex === -1) {
806 806 return;
807 807 }
808 808
809 809 if (that.selectedIndex === 0) {
810 810 $(that.suggestionsContainer).children().first().removeClass(that.classes.selected);
811 811 that.selectedIndex = -1;
812 812 that.el.val(that.currentValue);
813 813 that.findBestHint();
814 814 return;
815 815 }
816 816
817 817 that.adjustScroll(that.selectedIndex - 1);
818 818 },
819 819
820 820 moveDown: function () {
821 821 var that = this;
822 822
823 823 if (that.selectedIndex === (that.suggestions.length - 1)) {
824 824 return;
825 825 }
826 826
827 827 that.adjustScroll(that.selectedIndex + 1);
828 828 },
829 829
830 830 adjustScroll: function (index) {
831 831 var that = this,
832 832 activeItem = that.activate(index),
833 833 offsetTop,
834 834 upperBound,
835 835 lowerBound,
836 836 heightDelta = 25;
837 837
838 838 if (!activeItem) {
839 839 return;
840 840 }
841 841
842 842 offsetTop = activeItem.offsetTop;
843 843 upperBound = $(that.suggestionsContainer).scrollTop();
844 844 lowerBound = upperBound + that.options.maxHeight - heightDelta;
845 845
846 846 if (offsetTop < upperBound) {
847 847 $(that.suggestionsContainer).scrollTop(offsetTop);
848 848 } else if (offsetTop > lowerBound) {
849 849 $(that.suggestionsContainer).scrollTop(offsetTop - that.options.maxHeight + heightDelta);
850 850 }
851 851
852 852 if (that.options.replaceOnArrowKey) {
853 853 that.el.val(that.getValue(that.suggestions[index].value));
854 854 }
855 855 that.signalHint(null);
856 856 },
857 857
858 858 onSelect: function (index) {
859 859 var that = this,
860 860 onSelectCallback = that.options.onSelect,
861 861 suggestion = that.suggestions[index];
862 862
863 863 that.currentValue = that.getValue(suggestion.value);
864 864 var prevElem = {'value': that.el.val(),
865 865 'caret': that.element.selectionStart}
866 866
867 867 if (that.currentValue !== that.el.val()) {
868 868 that.el.val(that.currentValue);
869 869 }
870 870
871 871 that.signalHint(null);
872 872 that.suggestions = [];
873 873 that.selection = suggestion;
874 874
875 875 if ($.isFunction(onSelectCallback)) {
876 876 onSelectCallback.call(this, that.element, suggestion, prevElem);
877 877 }
878 878 },
879 879
880 880 getValue: function (value) {
881 881 var that = this,
882 882 delimiter = that.options.delimiter,
883 883 currentValue,
884 884 parts;
885 885
886 886 if (!delimiter) {
887 887 return value;
888 888 }
889 889
890 890 currentValue = that.currentValue;
891 891 parts = currentValue.split(delimiter);
892 892
893 893 if (parts.length === 1) {
894 894 return value;
895 895 }
896 896
897 897 return currentValue.substr(0, currentValue.length - parts[parts.length - 1].length) + value;
898 898 },
899 899
900 900 dispose: function () {
901 901 var that = this;
902 902 that.el.off('.autocomplete').removeData('autocomplete');
903 903 that.disableKillerFn();
904 904 $(window).off('resize.autocomplete', that.fixPositionCapture);
905 905 $(that.suggestionsContainer).remove();
906 906 }
907 907 };
908 908
909 909 // Create chainable jQuery plugin:
910 910 $.fn.autocomplete = $.fn.devbridgeAutocomplete = function (options, args) {
911 911 var dataKey = 'autocomplete';
912 912 // If function invoked without argument return
913 913 // instance of the first matched element:
914 914 if (arguments.length === 0) {
915 915 return this.first().data(dataKey);
916 916 }
917 917
918 918 return this.each(function () {
919 919 var inputElement = $(this),
920 920 instance = inputElement.data(dataKey);
921 921
922 922 if (typeof options === 'string') {
923 923 if (instance && typeof instance[options] === 'function') {
924 924 instance[options](args);
925 925 }
926 926 } else {
927 927 // If instance already exists, destroy it:
928 928 if (instance && instance.dispose) {
929 929 instance.dispose();
930 930 }
931 931 instance = new Autocomplete(this, options);
932 932 inputElement.data(dataKey, instance);
933 933 }
934 934 });
935 935 };
936 936 }));
@@ -1,205 +1,190 b''
1 1 // define module
2 2 var AgeModule = (function () {
3 3 return {
4 4 age: function(prevdate, now, show_short_version, show_suffix, short_format) {
5 5
6 6 var prevdate = moment(prevdate);
7 7 var now = now || moment().utc();
8 8
9 9 var show_short_version = show_short_version || false;
10 10 var show_suffix = show_suffix || true;
11 11 var short_format = short_format || false;
12 12
13 // alias for backward compat
14 var _ = function(s) {
15 if (_TM.hasOwnProperty(s)) {
16 return _TM[s];
17 }
18 return s
19 };
20
21 var ungettext = function (singular, plural, n) {
22 if (n === 1){
23 return _(singular)
24 }
25 return _(plural)
26 };
27
28 13 var _get_relative_delta = function(now, prevdate) {
29 14
30 15 var duration = moment.duration(moment(now).diff(prevdate));
31 16 return {
32 17 'year': duration.years(),
33 18 'month': duration.months(),
34 19 'day': duration.days(),
35 20 'hour': duration.hours(),
36 21 'minute': duration.minutes(),
37 22 'second': duration.seconds()
38 23 };
39 24
40 25 };
41 26
42 27 var _is_leap_year = function(year){
43 28 return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
44 29 };
45 30
46 31 var get_month = function(prevdate) {
47 32 return prevdate.getMonth()
48 33 };
49 34
50 35 var get_year = function(prevdate) {
51 36 return prevdate.getYear()
52 37 };
53 38
54 39 var order = ['year', 'month', 'day', 'hour', 'minute', 'second'];
55 40 var deltas = {};
56 41 var future = false;
57 42
58 43 if (prevdate > now) {
59 44 var now_old = now;
60 45 now = prevdate;
61 46 prevdate = now_old;
62 47 future = true;
63 48 }
64 49 if (future) {
65 50 // ? remove microseconds, we don't have it in JS
66 51 }
67 52
68 53 // Get date parts deltas
69 54 for (part in order) {
70 55 var part = order[part];
71 56 var rel_delta = _get_relative_delta(now, prevdate);
72 57 deltas[part] = rel_delta[part]
73 58 }
74 59
75 60 //# Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
76 61 //# not 1 hour, -59 minutes and -59 seconds)
77 62 var offsets = [[5, 60], [4, 60], [3, 24]];
78 63 for (element in offsets) { //# seconds, minutes, hours
79 64 var element = offsets[element];
80 65 var num = element[0];
81 66 var length = element[1];
82 67
83 68 var part = order[num];
84 69 var carry_part = order[num - 1];
85 70
86 71 if (deltas[part] < 0){
87 72 deltas[part] += length;
88 73 deltas[carry_part] -= 1
89 74 }
90 75
91 76 }
92 77
93 78 // # Same thing for days except that the increment depends on the (variable)
94 79 // # number of days in the month
95 80 var month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
96 81 if (deltas['day'] < 0) {
97 82 if (get_month(prevdate) == 2 && _is_leap_year(get_year(prevdate))) {
98 83 deltas['day'] += 29;
99 84 } else {
100 85 deltas['day'] += month_lengths[get_month(prevdate) - 1];
101 86 }
102 87
103 88 deltas['month'] -= 1
104 89 }
105 90
106 91 if (deltas['month'] < 0) {
107 92 deltas['month'] += 12;
108 93 deltas['year'] -= 1;
109 94 }
110 95
111 96 //# Format the result
112 97 if (short_format) {
113 98 var fmt_funcs = {
114 99 'year': function(d) {return '{0}y'.format(d)},
115 100 'month': function(d) {return '{0}m'.format(d)},
116 101 'day': function(d) {return '{0}d'.format(d)},
117 102 'hour': function(d) {return '{0}h'.format(d)},
118 103 'minute': function(d) {return '{0}min'.format(d)},
119 104 'second': function(d) {return '{0}sec'.format(d)}
120 105 }
121 106
122 107 } else {
123 108 var fmt_funcs = {
124 'year': function(d) {return ungettext('{0} year', '{0} years', d).format(d)},
125 'month': function(d) {return ungettext('{0} month', '{0} months', d).format(d)},
126 'day': function(d) {return ungettext('{0} day', '{0} days', d).format(d)},
127 'hour': function(d) {return ungettext('{0} hour', '{0} hours', d).format(d)},
128 'minute': function(d) {return ungettext('{0} min', '{0} min', d).format(d)},
129 'second': function(d) {return ungettext('{0} sec', '{0} sec', d).format(d)}
109 'year': function(d) {return _ngettext('{0} year', '{0} years', d).format(d)},
110 'month': function(d) {return _ngettext('{0} month', '{0} months', d).format(d)},
111 'day': function(d) {return _ngettext('{0} day', '{0} days', d).format(d)},
112 'hour': function(d) {return _ngettext('{0} hour', '{0} hours', d).format(d)},
113 'minute': function(d) {return _ngettext('{0} min', '{0} min', d).format(d)},
114 'second': function(d) {return _ngettext('{0} sec', '{0} sec', d).format(d)}
130 115 }
131 116
132 117 }
133 118 var i = 0;
134 119 for (part in order){
135 120 var part = order[part];
136 121 var value = deltas[part];
137 122 if (value !== 0) {
138 123
139 124 if (i < 5) {
140 125 var sub_part = order[i + 1];
141 126 var sub_value = deltas[sub_part]
142 127 } else {
143 128 var sub_value = 0
144 129 }
145 130 if (sub_value == 0 || show_short_version) {
146 131 var _val = fmt_funcs[part](value);
147 132 if (future) {
148 133 if (show_suffix) {
149 return _('in {0}').format(_val)
134 return _gettext('in {0}').format(_val)
150 135 } else {
151 136 return _val
152 137 }
153 138
154 139 }
155 140 else {
156 141 if (show_suffix) {
157 return _('{0} ago').format(_val)
142 return _gettext('{0} ago').format(_val)
158 143 } else {
159 144 return _val
160 145 }
161 146 }
162 147 }
163 148
164 149 var val = fmt_funcs[part](value);
165 150 var val_detail = fmt_funcs[sub_part](sub_value);
166 151 if (short_format) {
167 152 var datetime_tmpl = '{0}, {1}';
168 153 if (show_suffix) {
169 datetime_tmpl = _('{0}, {1} ago');
154 datetime_tmpl = _gettext('{0}, {1} ago');
170 155 if (future) {
171 datetime_tmpl = _('in {0}, {1}');
156 datetime_tmpl = _gettext('in {0}, {1}');
172 157 }
173 158 }
174 159 } else {
175 var datetime_tmpl = _('{0} and {1}');
160 var datetime_tmpl = _gettext('{0} and {1}');
176 161 if (show_suffix) {
177 datetime_tmpl = _('{0} and {1} ago');
162 datetime_tmpl = _gettext('{0} and {1} ago');
178 163 if (future) {
179 datetime_tmpl = _('in {0} and {1}')
164 datetime_tmpl = _gettext('in {0} and {1}')
180 165 }
181 166 }
182 167 }
183 168
184 169 return datetime_tmpl.format(val, val_detail)
185 170 }
186 171 i += 1;
187 172 }
188 173
189 return _('just now')
174 return _gettext('just now')
190 175
191 176 },
192 177 createTimeComponent: function(dateTime, text) {
193 178 return '<time class="timeago tooltip" title="{1}" datetime="{0}+0000">{1}</time>'.format(dateTime, text);
194 179 }
195 180 }
196 181 })();
197 182
198 183
199 184 jQuery.timeago.settings.localeTitle = false;
200 185
201 186 // auto refresh the components every Ns
202 187 jQuery.timeago.settings.refreshMillis = templateContext.timeago.refresh_time;
203 188
204 189 // Display original dates older than N days
205 190 jQuery.timeago.settings.cutoff = templateContext.timeago.cutoff_limit;
@@ -1,422 +1,413 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 RhodeCode JS Files
21 21 **/
22 22
23 23 if (typeof console == "undefined" || typeof console.log == "undefined"){
24 24 console = { log: function() {} }
25 25 }
26 26
27
28 // alias for backward compat
29 var _tm = function(s) {
30 if (_TM.hasOwnProperty(s)) {
31 return _TM[s];
32 }
33 return s
34 };
35
36 27 // TODO: move the following function to submodules
37 28
38 29 /**
39 30 * show more
40 31 */
41 32 var show_more_event = function(){
42 33 $('table .show_more').click(function(e) {
43 34 var cid = e.target.id.substring(1);
44 35 var button = $(this);
45 36 if (button.hasClass('open')) {
46 37 $('#'+cid).hide();
47 38 button.removeClass('open');
48 39 } else {
49 40 $('#'+cid).show();
50 41 button.addClass('open one');
51 42 }
52 43 });
53 44 };
54 45
55 46 var compare_radio_buttons = function(repo_name, compare_ref_type){
56 47 $('#compare_action').on('click', function(e){
57 48 e.preventDefault();
58 49
59 50 var source = $('input[name=compare_source]:checked').val();
60 51 var target = $('input[name=compare_target]:checked').val();
61 52 if(source && target){
62 53 var url_data = {
63 54 repo_name: repo_name,
64 55 source_ref: source,
65 56 source_ref_type: compare_ref_type,
66 57 target_ref: target,
67 58 target_ref_type: compare_ref_type,
68 59 merge: 1
69 60 };
70 61 window.location = pyroutes.url('compare_url', url_data);
71 62 }
72 63 });
73 64 $('.compare-radio-button').on('click', function(e){
74 65 var source = $('input[name=compare_source]:checked').val();
75 66 var target = $('input[name=compare_target]:checked').val();
76 67 if(source && target){
77 68 $('#compare_action').removeAttr("disabled");
78 69 $('#compare_action').removeClass("disabled");
79 70 }
80 71 })
81 72 };
82 73
83 74 var showRepoSize = function(target, repo_name, commit_id, callback) {
84 75 var container = $('#' + target);
85 76 var url = pyroutes.url('repo_stats',
86 77 {"repo_name": repo_name, "commit_id": commit_id});
87 78
88 79 if (!container.hasClass('loaded')) {
89 80 $.ajax({url: url})
90 81 .complete(function (data) {
91 82 var responseJSON = data.responseJSON;
92 83 container.addClass('loaded');
93 84 container.html(responseJSON.size);
94 85 callback(responseJSON.code_stats)
95 86 })
96 87 .fail(function (data) {
97 88 console.log('failed to load repo stats');
98 89 });
99 90 }
100 91
101 92 };
102 93
103 94 var showRepoStats = function(target, data){
104 95 var container = $('#' + target);
105 96
106 97 if (container.hasClass('loaded')) {
107 98 return
108 99 }
109 100
110 101 var total = 0;
111 102 var no_data = true;
112 103 var tbl = document.createElement('table');
113 104 tbl.setAttribute('class', 'trending_language_tbl');
114 105
115 106 $.each(data, function(key, val){
116 107 total += val.count;
117 108 });
118 109
119 110 var sortedStats = [];
120 111 for (var obj in data){
121 112 sortedStats.push([obj, data[obj]])
122 113 }
123 114 var sortedData = sortedStats.sort(function (a, b) {
124 115 return b[1].count - a[1].count
125 116 });
126 117 var cnt = 0;
127 118 $.each(sortedData, function(idx, val){
128 119 cnt += 1;
129 120 no_data = false;
130 121
131 122 var hide = cnt > 2;
132 123 var tr = document.createElement('tr');
133 124 if (hide) {
134 125 tr.setAttribute('style', 'display:none');
135 126 tr.setAttribute('class', 'stats_hidden');
136 127 }
137 128
138 129 var key = val[0];
139 130 var obj = {"desc": val[1].desc, "count": val[1].count};
140 131
141 132 var percentage = Math.round((obj.count / total * 100), 2);
142 133
143 134 var td1 = document.createElement('td');
144 135 td1.width = 300;
145 136 var trending_language_label = document.createElement('div');
146 137 trending_language_label.innerHTML = obj.desc + " (.{0})".format(key);
147 138 td1.appendChild(trending_language_label);
148 139
149 140 var td2 = document.createElement('td');
150 141 var trending_language = document.createElement('div');
151 var nr_files = obj.count +" "+ (obj.count === 1 ? _tm('file'): _tm('files'));
142 var nr_files = obj.count +" "+ _ngettext('file', 'files', obj.count);
152 143
153 144 trending_language.title = key + " " + nr_files;
154 145
155 146 trending_language.innerHTML = "<span>" + percentage + "% " + nr_files
156 147 + "</span><b>" + percentage + "% " + nr_files + "</b>";
157 148
158 149 trending_language.setAttribute("class", 'trending_language');
159 150 $('b', trending_language)[0].style.width = percentage + "%";
160 151 td2.appendChild(trending_language);
161 152
162 153 tr.appendChild(td1);
163 154 tr.appendChild(td2);
164 155 tbl.appendChild(tr);
165 156 if (cnt == 3) {
166 157 var show_more = document.createElement('tr');
167 158 var td = document.createElement('td');
168 159 lnk = document.createElement('a');
169 160
170 161 lnk.href = '#';
171 lnk.innerHTML = _tm('Show more');
162 lnk.innerHTML = _ngettext('Show more');
172 163 lnk.id = 'code_stats_show_more';
173 164 td.appendChild(lnk);
174 165
175 166 show_more.appendChild(td);
176 167 show_more.appendChild(document.createElement('td'));
177 168 tbl.appendChild(show_more);
178 169 }
179 170 });
180 171
181 172 $(container).html(tbl);
182 173 $(container).addClass('loaded');
183 174
184 175 $('#code_stats_show_more').on('click', function (e) {
185 176 e.preventDefault();
186 177 $('.stats_hidden').each(function (idx) {
187 178 $(this).css("display", "");
188 179 });
189 180 $('#code_stats_show_more').hide();
190 181 });
191 182
192 183 };
193 184
194 185
195 186 // Toggle Collapsable Content
196 187 function collapsableContent() {
197 188
198 189 $('.collapsable-content').not('.no-hide').hide();
199 190
200 191 $('.btn-collapse').unbind(); //in case we've been here before
201 192 $('.btn-collapse').click(function() {
202 193 var button = $(this);
203 194 var togglename = $(this).data("toggle");
204 195 $('.collapsable-content[data-toggle='+togglename+']').toggle();
205 196 if ($(this).html()=="Show Less")
206 197 $(this).html("Show More");
207 198 else
208 199 $(this).html("Show Less");
209 200 });
210 201 };
211 202
212 203 var timeagoActivate = function() {
213 204 $("time.timeago").timeago();
214 205 };
215 206
216 207 // Formatting values in a Select2 dropdown of commit references
217 208 var formatSelect2SelectionRefs = function(commit_ref){
218 209 var tmpl = '';
219 210 if (!commit_ref.text || commit_ref.type === 'sha'){
220 211 return commit_ref.text;
221 212 }
222 213 if (commit_ref.type === 'branch'){
223 214 tmpl = tmpl.concat('<i class="icon-branch"></i> ');
224 215 } else if (commit_ref.type === 'tag'){
225 216 tmpl = tmpl.concat('<i class="icon-tag"></i> ');
226 217 } else if (commit_ref.type === 'book'){
227 218 tmpl = tmpl.concat('<i class="icon-bookmark"></i> ');
228 219 }
229 220 return tmpl.concat(commit_ref.text);
230 221 };
231 222
232 223 // takes a given html element and scrolls it down offset pixels
233 224 function offsetScroll(element, offset){
234 225 setTimeout(function(){
235 226 console.log(element);
236 227 var location = element.offset().top;
237 228 // some browsers use body, some use html
238 229 $('html, body').animate({ scrollTop: (location - offset) });
239 230 }, 100);
240 231 }
241 232
242 233 /**
243 234 * global hooks after DOM is loaded
244 235 */
245 236 $(document).ready(function() {
246 237 firefoxAnchorFix();
247 238
248 239 $('.navigation a.menulink').on('click', function(e){
249 240 var menuitem = $(this).parent('li');
250 241 if (menuitem.hasClass('open')) {
251 242 menuitem.removeClass('open');
252 243 } else {
253 244 menuitem.addClass('open');
254 245 $(document).on('click', function(event) {
255 246 if (!$(event.target).closest(menuitem).length) {
256 247 menuitem.removeClass('open');
257 248 }
258 249 });
259 250 }
260 251 });
261 252 // Add tooltips
262 253 $('tr.line .lineno a').attr("title","Click to select line").addClass('tooltip');
263 254 $('tr.line .add-comment-line a').attr("title","Click to comment").addClass('tooltip');
264 255
265 256 // Set colors and styles
266 257 $('tr.line .lineno a').hover(
267 258 function(){
268 259 $(this).parents('tr.line').addClass('hover');
269 260 }, function(){
270 261 $(this).parents('tr.line').removeClass('hover');
271 262 }
272 263 );
273 264
274 265 $('tr.line .lineno a').click(
275 266 function(){
276 267 if ($(this).text() != ""){
277 268 $('tr.line').removeClass('selected');
278 269 $(this).parents("tr.line").addClass('selected');
279 270
280 271 // Replace URL without jumping to it if browser supports.
281 272 // Default otherwise
282 273 if (history.pushState) {
283 274 var new_location = location.href
284 275 if (location.hash){
285 276 new_location = new_location.replace(location.hash, "");
286 277 }
287 278
288 279 // Make new anchor url
289 280 var new_location = new_location+$(this).attr('href');
290 281 history.pushState(true, document.title, new_location);
291 282
292 283 return false;
293 284 }
294 285 }
295 286 }
296 287 );
297 288
298 289 $('tr.line .add-comment-line a').hover(
299 290 function(){
300 291 $(this).parents('tr.line').addClass('commenting');
301 292 }, function(){
302 293 $(this).parents('tr.line').removeClass('commenting');
303 294 }
304 295 );
305 296
306 297 $('tr.line .add-comment-line a').on('click', function(e){
307 298 var tr = $(e.currentTarget).parents('tr.line')[0];
308 299 injectInlineForm(tr);
309 300 return false;
310 301 });
311 302
312 303
313 304 $('.collapse_file').on('click', function(e) {
314 305 e.stopPropagation();
315 306 if ($(e.target).is('a')) { return; }
316 307 var node = $(e.delegateTarget).first();
317 308 var icon = $($(node.children().first()).children().first());
318 309 var id = node.attr('fid');
319 310 var target = $('#'+id);
320 311 var tr = $('#tr_'+id);
321 312 var diff = $('#diff_'+id);
322 313 if(node.hasClass('expand_file')){
323 314 node.removeClass('expand_file');
324 315 icon.removeClass('expand_file_icon');
325 316 node.addClass('collapse_file');
326 317 icon.addClass('collapse_file_icon');
327 318 diff.show();
328 319 tr.show();
329 320 target.show();
330 321 } else {
331 322 node.removeClass('collapse_file');
332 323 icon.removeClass('collapse_file_icon');
333 324 node.addClass('expand_file');
334 325 icon.addClass('expand_file_icon');
335 326 diff.hide();
336 327 tr.hide();
337 328 target.hide();
338 329 }
339 330 });
340 331
341 332 $('#expand_all_files').click(function() {
342 333 $('.expand_file').each(function() {
343 334 var node = $(this);
344 335 var icon = $($(node.children().first()).children().first());
345 336 var id = $(this).attr('fid');
346 337 var target = $('#'+id);
347 338 var tr = $('#tr_'+id);
348 339 var diff = $('#diff_'+id);
349 340 node.removeClass('expand_file');
350 341 icon.removeClass('expand_file_icon');
351 342 node.addClass('collapse_file');
352 343 icon.addClass('collapse_file_icon');
353 344 diff.show();
354 345 tr.show();
355 346 target.show();
356 347 });
357 348 });
358 349
359 350 $('#collapse_all_files').click(function() {
360 351 $('.collapse_file').each(function() {
361 352 var node = $(this);
362 353 var icon = $($(node.children().first()).children().first());
363 354 var id = $(this).attr('fid');
364 355 var target = $('#'+id);
365 356 var tr = $('#tr_'+id);
366 357 var diff = $('#diff_'+id);
367 358 node.removeClass('collapse_file');
368 359 icon.removeClass('collapse_file_icon');
369 360 node.addClass('expand_file');
370 361 icon.addClass('expand_file_icon');
371 362 diff.hide();
372 363 tr.hide();
373 364 target.hide();
374 365 });
375 366 });
376 367
377 368 // Mouse over behavior for comments and line selection
378 369
379 370 // Select the line that comes from the url anchor
380 371 // At the time of development, Chrome didn't seem to support jquery's :target
381 372 // element, so I had to scroll manually
382 373 if (location.hash) {
383 374 var splitIx = location.hash.indexOf('/?/');
384 375 if (splitIx !== -1){
385 376 var loc = location.hash.slice(0, splitIx);
386 377 var remainder = location.hash.slice(splitIx + 2);
387 378 }
388 379 else{
389 380 var loc = location.hash;
390 381 var remainder = null;
391 382 }
392 383 if (loc.length > 1){
393 384 var lineno = $(loc+'.lineno');
394 385 if (lineno.length > 0){
395 386 var tr = lineno.parents('tr.line');
396 387 tr.addClass('selected');
397 388
398 389 // once we scrolled into our line, trigger chat app
399 390 if (remainder){
400 391 tr.find('.add-comment-line a').trigger( "click" );
401 392 setTimeout(function(){
402 393 var nextNode = $(tr).next();
403 394 if(nextNode.hasClass('inline-comments')){
404 395 nextNode.next().find('.switch-to-chat').trigger( "click" );
405 396 }
406 397 else{
407 398 nextNode.find('.switch-to-chat').trigger( "click" );
408 399 }
409 400 // trigger scroll into, later so all elements are already loaded
410 401 tr[0].scrollIntoView();
411 402 }, 250);
412 403
413 404 }
414 405 else{
415 406 tr[0].scrollIntoView();
416 407 }
417 408 }
418 409 }
419 410 };
420 411
421 412 collapsableContent();
422 413 });
@@ -1,530 +1,530 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * Code Mirror
21 21 */
22 22 // global code-mirror logger;, to enable run
23 23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
24 24
25 25 cmLog = Logger.get('CodeMirror');
26 26 cmLog.setLevel(Logger.OFF);
27 27
28 28
29 29 //global cache for inline forms
30 30 var userHintsCache = {};
31 31
32 32
33 33 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
34 34 var ta = $('#' + textAreadId).get(0);
35 35 if (focus === undefined) {
36 36 focus = true;
37 37 }
38 38
39 39 // default options
40 40 var codeMirrorOptions = {
41 41 mode: "null",
42 42 lineNumbers: true,
43 43 indentUnit: 4,
44 44 autofocus: focus
45 45 };
46 46
47 47 if (options !== undefined) {
48 48 // extend with custom options
49 49 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
50 50 }
51 51
52 52 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
53 53
54 54 $('#reset').on('click', function(e) {
55 55 window.location = resetUrl;
56 56 });
57 57
58 58 return myCodeMirror;
59 59 };
60 60
61 61 var initCommentBoxCodeMirror = function(textAreaId, triggerActions){
62 62 var initialHeight = 100;
63 63
64 64 // global timer, used to cancel async loading
65 65 var loadUserHintTimer;
66 66
67 67 if (typeof userHintsCache === "undefined") {
68 68 userHintsCache = {};
69 69 cmLog.debug('Init empty cache for mentions');
70 70 }
71 71 if (!$(textAreaId).get(0)) {
72 72 cmLog.debug('Element for textarea not found', textAreaId);
73 73 return;
74 74 }
75 75 var escapeRegExChars = function(value) {
76 76 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
77 77 };
78 78 /**
79 79 * Load hints from external source returns an array of objects in a format
80 80 * that hinting lib requires
81 81 * @returns {Array}
82 82 */
83 83 var loadUserHints = function(query, triggerHints) {
84 84 cmLog.debug('Loading mentions users via AJAX');
85 85 var _users = [];
86 86 $.ajax({
87 87 type: 'GET',
88 88 data: {query: query},
89 89 url: pyroutes.url('user_autocomplete_data'),
90 90 headers: {'X-PARTIAL-XHR': true},
91 91 async: true
92 92 })
93 93 .done(function(data) {
94 94 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
95 95 $.each(data.suggestions, function(i) {
96 96 var userObj = data.suggestions[i];
97 97
98 98 if (userObj.username !== "default") {
99 99 _users.push({
100 100 text: userObj.username + " ",
101 101 org_text: userObj.username,
102 102 displayText: userObj.value_display, // search that field
103 103 // internal caches
104 104 _icon_link: userObj.icon_link,
105 105 _text: userObj.value_display,
106 106
107 107 render: function(elt, data, completion) {
108 108 var el = document.createElement('div');
109 109 el.className = "CodeMirror-hint-entry";
110 110 el.innerHTML = tmpl.format(
111 111 completion._icon_link, completion._text);
112 112 elt.appendChild(el);
113 113 }
114 114 });
115 115 }
116 116 });
117 117 cmLog.debug('Mention users loaded');
118 118 // set to global cache
119 119 userHintsCache[query] = _users;
120 120 triggerHints(userHintsCache[query]);
121 121 })
122 122 .fail(function(data, textStatus, xhr) {
123 123 alert("error processing request: " + textStatus);
124 124 });
125 125 };
126 126
127 127 /**
128 128 * filters the results based on the current context
129 129 * @param users
130 130 * @param context
131 131 * @returns {Array}
132 132 */
133 133 var filterUsers = function(users, context) {
134 134 var MAX_LIMIT = 10;
135 135 var filtered_users = [];
136 136 var curWord = context.string;
137 137
138 138 cmLog.debug('Filtering users based on query:', curWord);
139 139 $.each(users, function(i) {
140 140 var match = users[i];
141 141 var searchText = match.displayText;
142 142
143 143 if (!curWord ||
144 144 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
145 145 // reset state
146 146 match._text = match.displayText;
147 147 if (curWord) {
148 148 // do highlighting
149 149 var pattern = '(' + escapeRegExChars(curWord) + ')';
150 150 match._text = searchText.replace(
151 151 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
152 152 }
153 153
154 154 filtered_users.push(match);
155 155 }
156 156 // to not return to many results, use limit of filtered results
157 157 if (filtered_users.length > MAX_LIMIT) {
158 158 return false;
159 159 }
160 160 });
161 161
162 162 return filtered_users;
163 163 };
164 164
165 165 /**
166 166 * Filter action based on typed in text
167 167 * @param actions
168 168 * @param context
169 169 * @returns {Array}
170 170 */
171 171
172 172 var filterActions = function(actions, context){
173 173 var MAX_LIMIT = 10;
174 174 var filtered_actions= [];
175 175 var curWord = context.string;
176 176
177 177 cmLog.debug('Filtering actions based on query:', curWord);
178 178 $.each(actions, function(i) {
179 179 var match = actions[i];
180 180 var searchText = match.displayText;
181 181
182 182 if (!curWord ||
183 183 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
184 184 // reset state
185 185 match._text = match.displayText;
186 186 if (curWord) {
187 187 // do highlighting
188 188 var pattern = '(' + escapeRegExChars(curWord) + ')';
189 189 match._text = searchText.replace(
190 190 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
191 191 }
192 192
193 193 filtered_actions.push(match);
194 194 }
195 195 // to not return to many results, use limit of filtered results
196 196 if (filtered_actions.length > MAX_LIMIT) {
197 197 return false;
198 198 }
199 199 });
200 200 return filtered_actions;
201 201 };
202 202
203 203 var completeAfter = function(cm, pred) {
204 204 var options = {
205 205 completeSingle: false,
206 206 async: true,
207 207 closeOnUnfocus: true
208 208 };
209 209 var cur = cm.getCursor();
210 210 setTimeout(function() {
211 211 if (!cm.state.completionActive) {
212 212 cmLog.debug('Trigger mentions hinting');
213 213 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
214 214 }
215 215 }, 100);
216 216
217 217 // tell CodeMirror we didn't handle the key
218 218 // trick to trigger on a char but still complete it
219 219 return CodeMirror.Pass;
220 220 };
221 221
222 222 var submitForm = function(cm, pred) {
223 223 $(cm.display.input.textarea.form).submit();
224 224 return CodeMirror.Pass;
225 225 };
226 226
227 227 var completeActions = function(cm, pred) {
228 228 var cur = cm.getCursor();
229 229 var options = {
230 230 closeOnUnfocus: true
231 231 };
232 232 setTimeout(function() {
233 233 if (!cm.state.completionActive) {
234 234 cmLog.debug('Trigger actions hinting');
235 235 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
236 236 }
237 237 }, 100);
238 238 };
239 239
240 240 var extraKeys = {
241 241 "'@'": completeAfter,
242 242 Tab: function(cm) {
243 243 // space indent instead of TABS
244 244 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
245 245 cm.replaceSelection(spaces);
246 246 }
247 247 };
248 248 // submit form on Meta-Enter
249 249 if (OSType === "mac") {
250 250 extraKeys["Cmd-Enter"] = submitForm;
251 251 }
252 252 else {
253 253 extraKeys["Ctrl-Enter"] = submitForm;
254 254 }
255 255
256 256 if (triggerActions) {
257 257 extraKeys["Ctrl-Space"] = completeActions;
258 258 }
259 259
260 260 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
261 261 lineNumbers: false,
262 262 indentUnit: 4,
263 263 viewportMargin: 30,
264 264 // this is a trick to trigger some logic behind codemirror placeholder
265 265 // it influences styling and behaviour.
266 266 placeholder: " ",
267 267 extraKeys: extraKeys,
268 268 lineWrapping: true
269 269 });
270 270
271 271 cm.setSize(null, initialHeight);
272 272 cm.setOption("mode", DEFAULT_RENDERER);
273 273 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
274 274 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
275 275 // start listening on changes to make auto-expanded editor
276 276 cm.on("change", function(self) {
277 277 var height = initialHeight;
278 278 var lines = self.lineCount();
279 279 if ( lines > 6 && lines < 20) {
280 280 height = "auto";
281 281 }
282 282 else if (lines >= 20){
283 283 zheight = 20*15;
284 284 }
285 285 self.setSize(null, height);
286 286 });
287 287
288 288 var mentionHint = function(editor, callback, options) {
289 289 var cur = editor.getCursor();
290 290 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
291 291
292 292 // match on @ +1char
293 293 var tokenMatch = new RegExp(
294 294 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
295 295
296 296 var tokenStr = '';
297 297 if (tokenMatch !== null && tokenMatch.length > 0){
298 298 tokenStr = tokenMatch[0].strip();
299 299 } else {
300 300 // skip if we didn't match our token
301 301 return;
302 302 }
303 303
304 304 var context = {
305 305 start: (cur.ch - tokenStr.length) + 1,
306 306 end: cur.ch,
307 307 string: tokenStr.slice(1),
308 308 type: null
309 309 };
310 310
311 311 // case when we put the @sign in fron of a string,
312 312 // eg <@ we put it here>sometext then we need to prepend to text
313 313 if (context.end > cur.ch) {
314 314 context.start = context.start + 1; // we add to the @ sign
315 315 context.end = cur.ch; // don't eat front part just append
316 316 context.string = context.string.slice(1, cur.ch - context.start);
317 317 }
318 318
319 319 cmLog.debug('Mention context', context);
320 320
321 321 var triggerHints = function(userHints){
322 322 return callback({
323 323 list: filterUsers(userHints, context),
324 324 from: CodeMirror.Pos(cur.line, context.start),
325 325 to: CodeMirror.Pos(cur.line, context.end)
326 326 });
327 327 };
328 328
329 329 var queryBasedHintsCache = undefined;
330 330 // if we have something in the cache, try to fetch the query based cache
331 331 if (userHintsCache !== {}){
332 332 queryBasedHintsCache = userHintsCache[context.string];
333 333 }
334 334
335 335 if (queryBasedHintsCache !== undefined) {
336 336 cmLog.debug('Users loaded from cache');
337 337 triggerHints(queryBasedHintsCache);
338 338 } else {
339 339 // this takes care for async loading, and then displaying results
340 340 // and also propagates the userHintsCache
341 341 window.clearTimeout(loadUserHintTimer);
342 342 loadUserHintTimer = setTimeout(function() {
343 343 loadUserHints(context.string, triggerHints);
344 344 }, 300);
345 345 }
346 346 };
347 347
348 348 var actionHint = function(editor, options) {
349 349 var cur = editor.getCursor();
350 350 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
351 351
352 352 var tokenMatch = new RegExp('[a-zA-Z]{1}[a-zA-Z]*$').exec(curLine);
353 353
354 354 var tokenStr = '';
355 355 if (tokenMatch !== null && tokenMatch.length > 0){
356 356 tokenStr = tokenMatch[0].strip();
357 357 }
358 358
359 359 var context = {
360 360 start: cur.ch - tokenStr.length,
361 361 end: cur.ch,
362 362 string: tokenStr,
363 363 type: null
364 364 };
365 365
366 366 var actions = [
367 367 {
368 368 text: "approve",
369 displayText: _TM['Set status to Approved'],
369 displayText: _gettext('Set status to Approved'),
370 370 hint: function(CodeMirror, data, completion) {
371 371 CodeMirror.replaceRange("", completion.from || data.from,
372 372 completion.to || data.to, "complete");
373 373 $('#change_status').select2("val", 'approved').trigger('change');
374 374 },
375 375 render: function(elt, data, completion) {
376 376 var el = document.createElement('div');
377 377 el.className = "flag_status flag_status_comment_box approved pull-left";
378 378 elt.appendChild(el);
379 379
380 380 el = document.createElement('span');
381 381 el.innerHTML = completion.displayText;
382 382 elt.appendChild(el);
383 383 }
384 384 },
385 385 {
386 386 text: "reject",
387 displayText: _TM['Set status to Rejected'],
387 displayText: _gettext('Set status to Rejected'),
388 388 hint: function(CodeMirror, data, completion) {
389 389 CodeMirror.replaceRange("", completion.from || data.from,
390 390 completion.to || data.to, "complete");
391 391 $('#change_status').select2("val", 'rejected').trigger('change');
392 392 },
393 393 render: function(elt, data, completion) {
394 394 var el = document.createElement('div');
395 395 el.className = "flag_status flag_status_comment_box rejected pull-left";
396 396 elt.appendChild(el);
397 397
398 398 el = document.createElement('span');
399 399 el.innerHTML = completion.displayText;
400 400 elt.appendChild(el);
401 401 }
402 402 }
403 403 ];
404 404
405 405 return {
406 406 list: filterActions(actions, context),
407 407 from: CodeMirror.Pos(cur.line, context.start),
408 408 to: CodeMirror.Pos(cur.line, context.end)
409 409 };
410 410 };
411 411 CodeMirror.registerHelper("hint", "mentions", mentionHint);
412 412 CodeMirror.registerHelper("hint", "actions", actionHint);
413 413 return cm;
414 414 };
415 415
416 416 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
417 417 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
418 418 codeMirrorInstance.setOption("mode", mode);
419 419 };
420 420
421 421 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
422 422 codeMirrorInstance.setOption("lineWrapping", line_wrap);
423 423 };
424 424
425 425 var setCodeMirrorModeFromSelect = function(
426 426 targetSelect, targetFileInput, codeMirrorInstance, callback){
427 427
428 428 $(targetSelect).on('change', function(e) {
429 429 cmLog.debug('codemirror select2 mode change event !');
430 430 var selected = e.currentTarget;
431 431 var node = selected.options[selected.selectedIndex];
432 432 var mimetype = node.value;
433 433 cmLog.debug('picked mimetype', mimetype);
434 434 var new_mode = $(node).attr('mode');
435 435 setCodeMirrorMode(codeMirrorInstance, new_mode);
436 436 cmLog.debug('set new mode', new_mode);
437 437
438 438 //propose filename from picked mode
439 439 cmLog.debug('setting mimetype', mimetype);
440 440 var proposed_ext = getExtFromMimeType(mimetype);
441 441 cmLog.debug('file input', $(targetFileInput).val());
442 442 var file_data = getFilenameAndExt($(targetFileInput).val());
443 443 var filename = file_data.filename || 'filename1';
444 444 $(targetFileInput).val(filename + proposed_ext);
445 445 cmLog.debug('proposed file', filename + proposed_ext);
446 446
447 447
448 448 if (typeof(callback) === 'function') {
449 449 try {
450 450 cmLog.debug('running callback', callback);
451 451 callback(filename, mimetype, new_mode);
452 452 } catch (err) {
453 453 console.log('failed to run callback', callback, err);
454 454 }
455 455 }
456 456 cmLog.debug('finish iteration...');
457 457 });
458 458 };
459 459
460 460 var setCodeMirrorModeFromInput = function(
461 461 targetSelect, targetFileInput, codeMirrorInstance, callback) {
462 462
463 463 // on type the new filename set mode
464 464 $(targetFileInput).on('keyup', function(e) {
465 465 var file_data = getFilenameAndExt(this.value);
466 466 if (file_data.ext === null) {
467 467 return;
468 468 }
469 469
470 470 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
471 471 cmLog.debug('mimetype from file', file_data, mimetypes);
472 472 var detected_mode;
473 473 var detected_option;
474 474 for (var i in mimetypes) {
475 475 var mt = mimetypes[i];
476 476 if (!detected_mode) {
477 477 detected_mode = detectCodeMirrorMode(this.value, mt);
478 478 }
479 479
480 480 if (!detected_option) {
481 481 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
482 482 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
483 483 detected_option = mt;
484 484 }
485 485 }
486 486 }
487 487
488 488 cmLog.debug('detected mode', detected_mode);
489 489 cmLog.debug('detected option', detected_option);
490 490 if (detected_mode && detected_option){
491 491
492 492 $(targetSelect).select2("val", detected_option);
493 493 setCodeMirrorMode(codeMirrorInstance, detected_mode);
494 494
495 495 if(typeof(callback) === 'function'){
496 496 try{
497 497 cmLog.debug('running callback', callback);
498 498 var filename = file_data.filename + "." + file_data.ext;
499 499 callback(filename, detected_option, detected_mode);
500 500 }catch (err){
501 501 console.log('failed to run callback', callback, err);
502 502 }
503 503 }
504 504 }
505 505
506 506 });
507 507 };
508 508
509 509 var fillCodeMirrorOptions = function(targetSelect) {
510 510 //inject new modes, based on codeMirrors modeInfo object
511 511 var modes_select = $(targetSelect);
512 512 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
513 513 var m = CodeMirror.modeInfo[i];
514 514 var opt = new Option(m.name, m.mime);
515 515 $(opt).attr('mode', m.mode);
516 516 modes_select.append(opt);
517 517 }
518 518 };
519 519
520 520 var CodeMirrorPreviewEnable = function(edit_mode) {
521 521 // in case it a preview enabled mode enable the button
522 522 if (['markdown', 'rst', 'gfm'].indexOf(edit_mode) !== -1) {
523 523 $('#render_preview').removeClass('hidden');
524 524 }
525 525 else {
526 526 if (!$('#render_preview').hasClass('hidden')) {
527 527 $('#render_preview').addClass('hidden');
528 528 }
529 529 }
530 530 };
@@ -1,664 +1,664 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28 28 // returns a node from given html;
29 29 var fromHTML = function(html){
30 30 var _html = document.createElement('element');
31 31 _html.innerHTML = html;
32 32 return _html;
33 33 };
34 34
35 35 var tableTr = function(cls, body){
36 36 var _el = document.createElement('div');
37 37 var _body = $(body).attr('id');
38 38 var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
39 39 var id = 'comment-tr-{0}'.format(comment_id);
40 40 var _html = ('<table><tbody><tr id="{0}" class="{1}">'+
41 41 '<td class="add-comment-line"><span class="add-comment-content"></span></td>'+
42 42 '<td></td>'+
43 43 '<td></td>'+
44 44 '<td>{2}</td>'+
45 45 '</tr></tbody></table>').format(id, cls, body);
46 46 $(_el).html(_html);
47 47 return _el.children[0].children[0].children[0];
48 48 };
49 49
50 50 var removeInlineForm = function(form) {
51 51 form.parentNode.removeChild(form);
52 52 };
53 53
54 54 var createInlineForm = function(parent_tr, f_path, line) {
55 55 var tmpl = $('#comment-inline-form-template').html();
56 56 tmpl = tmpl.format(f_path, line);
57 57 var form = tableTr('comment-form-inline', tmpl);
58 58 var form_hide_button = $(form).find('.hide-inline-form');
59 59
60 60 $(form_hide_button).click(function(e) {
61 61 $('.inline-comments').removeClass('hide-comment-button');
62 62 var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
63 63 if ($(newtr.nextElementSibling).hasClass('inline-comments-button')) {
64 64 $(newtr.nextElementSibling).show();
65 65 }
66 66 $(newtr).parents('.comment-form-inline').remove();
67 67 $(parent_tr).removeClass('form-open');
68 68 $(parent_tr).removeClass('hl-comment');
69 69 });
70 70
71 71 return form;
72 72 };
73 73
74 74 var getLineNo = function(tr) {
75 75 var line;
76 76 // Try to get the id and return "" (empty string) if it doesn't exist
77 77 var o = ($(tr).find('.lineno.old').attr('id')||"").split('_');
78 78 var n = ($(tr).find('.lineno.new').attr('id')||"").split('_');
79 79 if (n.length >= 2) {
80 80 line = n[n.length-1];
81 81 } else if (o.length >= 2) {
82 82 line = o[o.length-1];
83 83 }
84 84 return line;
85 85 };
86 86
87 87 /**
88 88 * make a single inline comment and place it inside
89 89 */
90 90 var renderInlineComment = function(json_data, show_add_button) {
91 91 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
92 92 try {
93 93 var html = json_data.rendered_text;
94 94 var lineno = json_data.line_no;
95 95 var target_id = json_data.target_id;
96 96 placeInline(target_id, lineno, html, show_add_button);
97 97 } catch (e) {
98 98 console.error(e);
99 99 }
100 100 };
101 101
102 102 function bindDeleteCommentButtons() {
103 103 $('.delete-comment').one('click', function() {
104 104 var comment_id = $(this).data("comment-id");
105 105
106 106 if (comment_id){
107 107 deleteComment(comment_id);
108 108 }
109 109 });
110 110 }
111 111
112 112 /**
113 113 * Inject inline comment for on given TR this tr should be always an .line
114 114 * tr containing the line. Code will detect comment, and always put the comment
115 115 * block at the very bottom
116 116 */
117 117 var injectInlineForm = function(tr){
118 118 if (!$(tr).hasClass('line')) {
119 119 return;
120 120 }
121 121
122 122 var _td = $(tr).find('.code').get(0);
123 123 if ($(tr).hasClass('form-open') ||
124 124 $(tr).hasClass('context') ||
125 125 $(_td).hasClass('no-comment')) {
126 126 return;
127 127 }
128 128 $(tr).addClass('form-open');
129 129 $(tr).addClass('hl-comment');
130 130 var node = $(tr.parentNode.parentNode.parentNode).find('.full_f_path').get(0);
131 131 var f_path = $(node).attr('path');
132 132 var lineno = getLineNo(tr);
133 133 var form = createInlineForm(tr, f_path, lineno);
134 134
135 135 var parent = tr;
136 136 while (1) {
137 137 var n = parent.nextElementSibling;
138 138 // next element are comments !
139 139 if ($(n).hasClass('inline-comments')) {
140 140 parent = n;
141 141 }
142 142 else {
143 143 break;
144 144 }
145 145 }
146 146 var _parent = $(parent).get(0);
147 147 $(_parent).after(form);
148 148 $('.comment-form-inline').prev('.inline-comments').addClass('hide-comment-button');
149 149 var f = $(form).get(0);
150 150
151 151 var _form = $(f).find('.inline-form').get(0);
152 152
153 153 $('.switch-to-chat', _form).on('click', function(evt){
154 154 var fParent = $(_parent).closest('.injected_diff').parent().prev('*[fid]');
155 155 var fid = fParent.attr('fid');
156 156
157 157 // activate chat and trigger subscription to channels
158 158 $.Topic('/chat_controller').publish({
159 159 action:'subscribe_to_channels',
160 160 data: ['/chat${0}$/fid/{1}/{2}'.format(templateContext.repo_name, fid, lineno)]
161 161 });
162 162 $(_form).closest('td').find('.comment-inline-form').addClass('hidden');
163 163 $(_form).closest('td').find('.chat-holder').removeClass('hidden');
164 164 });
165 165
166 166 var pullRequestId = templateContext.pull_request_data.pull_request_id;
167 167 var commitId = templateContext.commit_data.commit_id;
168 168
169 169 var commentForm = new CommentForm(_form, commitId, pullRequestId, lineno, false);
170 170 var cm = commentForm.getCmInstance();
171 171
172 172 // set a CUSTOM submit handler for inline comments.
173 173 commentForm.setHandleFormSubmit(function(o) {
174 174 var text = commentForm.cm.getValue();
175 175
176 176 if (text === "") {
177 177 return;
178 178 }
179 179
180 180 if (lineno === undefined) {
181 181 alert('missing line !');
182 182 return;
183 183 }
184 184 if (f_path === undefined) {
185 185 alert('missing file path !');
186 186 return;
187 187 }
188 188
189 189 var excludeCancelBtn = false;
190 190 var submitEvent = true;
191 191 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
192 192 commentForm.cm.setOption("readOnly", true);
193 193 var postData = {
194 194 'text': text,
195 195 'f_path': f_path,
196 196 'line': lineno,
197 197 'csrf_token': CSRF_TOKEN
198 198 };
199 199 var submitSuccessCallback = function(o) {
200 200 $(tr).removeClass('form-open');
201 201 removeInlineForm(f);
202 202 renderInlineComment(o);
203 203 $('.inline-comments').removeClass('hide-comment-button');
204 204
205 205 // re trigger the linkification of next/prev navigation
206 206 linkifyComments($('.inline-comment-injected'));
207 207 timeagoActivate();
208 208 tooltip_activate();
209 209 bindDeleteCommentButtons();
210 210 commentForm.setActionButtonsDisabled(false);
211 211
212 212 };
213 213 var submitFailCallback = function(){
214 214 commentForm.resetCommentFormState(text)
215 215 };
216 216 commentForm.submitAjaxPOST(
217 217 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
218 218 });
219 219
220 220 setTimeout(function() {
221 221 // callbacks
222 222 if (cm !== undefined) {
223 223 cm.focus();
224 224 }
225 225 }, 10);
226 226
227 227 };
228 228
229 229 var deleteComment = function(comment_id) {
230 230 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
231 231 var postData = {
232 232 '_method': 'delete',
233 233 'csrf_token': CSRF_TOKEN
234 234 };
235 235
236 236 var success = function(o) {
237 237 window.location.reload();
238 238 };
239 239 ajaxPOST(url, postData, success);
240 240 };
241 241
242 242 var createInlineAddButton = function(tr){
243 var label = _TM['Add another comment'];
243 var label = _gettext('Add another comment');
244 244 var html_el = document.createElement('div');
245 245 $(html_el).addClass('add-comment');
246 246 html_el.innerHTML = '<span class="btn btn-secondary">{0}</span>'.format(label);
247 247 var add = new $(html_el);
248 248 add.on('click', function(e) {
249 249 injectInlineForm(tr);
250 250 });
251 251 return add;
252 252 };
253 253
254 254 var placeAddButton = function(target_tr){
255 255 if(!target_tr){
256 256 return;
257 257 }
258 258 var last_node = target_tr;
259 259 // scan
260 260 while (1){
261 261 var n = last_node.nextElementSibling;
262 262 // next element are comments !
263 263 if($(n).hasClass('inline-comments')){
264 264 last_node = n;
265 265 // also remove the comment button from previous
266 266 var comment_add_buttons = $(last_node).find('.add-comment');
267 267 for(var i=0; i<comment_add_buttons.length; i++){
268 268 var b = comment_add_buttons[i];
269 269 b.parentNode.removeChild(b);
270 270 }
271 271 }
272 272 else{
273 273 break;
274 274 }
275 275 }
276 276 var add = createInlineAddButton(target_tr);
277 277 // get the comment div
278 278 var comment_block = $(last_node).find('.comment')[0];
279 279 // attach add button
280 280 $(add).insertAfter(comment_block);
281 281 };
282 282
283 283 /**
284 284 * Places the inline comment into the changeset block in proper line position
285 285 */
286 286 var placeInline = function(target_container, lineno, html, show_add_button) {
287 287 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
288 288
289 289 var lineid = "{0}_{1}".format(target_container, lineno);
290 290 var target_line = $('#' + lineid).get(0);
291 291 var comment = new $(tableTr('inline-comments', html));
292 292 // check if there are comments already !
293 293 var parent_node = target_line.parentNode;
294 294 var root_parent = parent_node;
295 295 while (1) {
296 296 var n = parent_node.nextElementSibling;
297 297 // next element are comments !
298 298 if ($(n).hasClass('inline-comments')) {
299 299 parent_node = n;
300 300 }
301 301 else {
302 302 break;
303 303 }
304 304 }
305 305 // put in the comment at the bottom
306 306 $(comment).insertAfter(parent_node);
307 307 $(comment).find('.comment-inline').addClass('inline-comment-injected');
308 308 // scan nodes, and attach add button to last one
309 309 if (show_add_button) {
310 310 placeAddButton(root_parent);
311 311 }
312 312
313 313 return target_line;
314 314 };
315 315
316 316 var linkifyComments = function(comments) {
317 317
318 318 for (var i = 0; i < comments.length; i++) {
319 319 var comment_id = $(comments[i]).data('comment-id');
320 320 var prev_comment_id = $(comments[i - 1]).data('comment-id');
321 321 var next_comment_id = $(comments[i + 1]).data('comment-id');
322 322
323 323 // place next/prev links
324 324 if (prev_comment_id) {
325 325 $('#prev_c_' + comment_id).show();
326 326 $('#prev_c_' + comment_id + " a.arrow_comment_link").attr(
327 327 'href', '#comment-' + prev_comment_id).removeClass('disabled');
328 328 }
329 329 if (next_comment_id) {
330 330 $('#next_c_' + comment_id).show();
331 331 $('#next_c_' + comment_id + " a.arrow_comment_link").attr(
332 332 'href', '#comment-' + next_comment_id).removeClass('disabled');
333 333 }
334 334 // place a first link to the total counter
335 335 if (i === 0) {
336 336 $('#inline-comments-counter').attr('href', '#comment-' + comment_id);
337 337 }
338 338 }
339 339
340 340 };
341 341
342 342 /**
343 343 * Iterates over all the inlines, and places them inside proper blocks of data
344 344 */
345 345 var renderInlineComments = function(file_comments, show_add_button) {
346 346 show_add_button = typeof show_add_button !== 'undefined' ? show_add_button : true;
347 347
348 348 for (var i = 0; i < file_comments.length; i++) {
349 349 var box = file_comments[i];
350 350
351 351 var target_id = $(box).attr('target_id');
352 352
353 353 // actually comments with line numbers
354 354 var comments = box.children;
355 355
356 356 for (var j = 0; j < comments.length; j++) {
357 357 var data = {
358 358 'rendered_text': comments[j].outerHTML,
359 359 'line_no': $(comments[j]).attr('line'),
360 360 'target_id': target_id
361 361 };
362 362 renderInlineComment(data, show_add_button);
363 363 }
364 364 }
365 365
366 366 // since order of injection is random, we're now re-iterating
367 367 // from correct order and filling in links
368 368 linkifyComments($('.inline-comment-injected'));
369 369 bindDeleteCommentButtons();
370 370 firefoxAnchorFix();
371 371 };
372 372
373 373
374 374 /* Comment form for main and inline comments */
375 375 var CommentForm = (function() {
376 376 "use strict";
377 377
378 378 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions) {
379 379
380 380 this.withLineNo = function(selector) {
381 381 var lineNo = this.lineNo;
382 382 if (lineNo === undefined) {
383 383 return selector
384 384 } else {
385 385 return selector + '_' + lineNo;
386 386 }
387 387 };
388 388
389 389 this.commitId = commitId;
390 390 this.pullRequestId = pullRequestId;
391 391 this.lineNo = lineNo;
392 392 this.initAutocompleteActions = initAutocompleteActions;
393 393
394 394 this.previewButton = this.withLineNo('#preview-btn');
395 395 this.previewContainer = this.withLineNo('#preview-container');
396 396
397 397 this.previewBoxSelector = this.withLineNo('#preview-box');
398 398
399 399 this.editButton = this.withLineNo('#edit-btn');
400 400 this.editContainer = this.withLineNo('#edit-container');
401 401
402 402 this.cancelButton = this.withLineNo('#cancel-btn');
403 403
404 404 this.statusChange = '#change_status';
405 405 this.cmBox = this.withLineNo('#text');
406 406 this.cm = initCommentBoxCodeMirror(this.cmBox, this.initAutocompleteActions);
407 407
408 408 this.submitForm = formElement;
409 409 this.submitButton = $(this.submitForm).find('input[type="submit"]');
410 410 this.submitButtonText = this.submitButton.val();
411 411
412 412 this.previewUrl = pyroutes.url('changeset_comment_preview',
413 413 {'repo_name': templateContext.repo_name});
414 414
415 415 // based on commitId, or pullReuqestId decide where do we submit
416 416 // out data
417 417 if (this.commitId){
418 418 this.submitUrl = pyroutes.url('changeset_comment',
419 419 {'repo_name': templateContext.repo_name,
420 420 'revision': this.commitId});
421 421
422 422 } else if (this.pullRequestId) {
423 423 this.submitUrl = pyroutes.url('pullrequest_comment',
424 424 {'repo_name': templateContext.repo_name,
425 425 'pull_request_id': this.pullRequestId});
426 426
427 427 } else {
428 428 throw new Error(
429 429 'CommentForm requires pullRequestId, or commitId to be specified.')
430 430 }
431 431
432 432 this.getCmInstance = function(){
433 433 return this.cm
434 434 };
435 435
436 436 var self = this;
437 437
438 438 this.getCommentStatus = function() {
439 439 return $(this.submitForm).find(this.statusChange).val();
440 440 };
441 441
442 442 this.isAllowedToSubmit = function() {
443 443 return !$(this.submitButton).prop('disabled');
444 444 };
445 445
446 446 this.initStatusChangeSelector = function(){
447 447 var formatChangeStatus = function(state, escapeMarkup) {
448 448 var originalOption = state.element;
449 449 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
450 450 '<span>' + escapeMarkup(state.text) + '</span>';
451 451 };
452 452 var formatResult = function(result, container, query, escapeMarkup) {
453 453 return formatChangeStatus(result, escapeMarkup);
454 454 };
455 455
456 456 var formatSelection = function(data, container, escapeMarkup) {
457 457 return formatChangeStatus(data, escapeMarkup);
458 458 };
459 459
460 460 $(this.submitForm).find(this.statusChange).select2({
461 placeholder: _TM['Status Review'],
461 placeholder: _gettext('Status Review'),
462 462 formatResult: formatResult,
463 463 formatSelection: formatSelection,
464 464 containerCssClass: "drop-menu status_box_menu",
465 465 dropdownCssClass: "drop-menu-dropdown",
466 466 dropdownAutoWidth: true,
467 467 minimumResultsForSearch: -1
468 468 });
469 469 $(this.submitForm).find(this.statusChange).on('change', function() {
470 470 var status = self.getCommentStatus();
471 471 if (status && !self.lineNo) {
472 472 $(self.submitButton).prop('disabled', false);
473 473 }
474 474 //todo, fix this name
475 var placeholderText = _TM['Comment text will be set automatically based on currently selected status ({0}) ...'].format(status);
475 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
476 476 self.cm.setOption('placeholder', placeholderText);
477 477 })
478 478 };
479 479
480 480 // reset the comment form into it's original state
481 481 this.resetCommentFormState = function(content) {
482 482 content = content || '';
483 483
484 484 $(this.editContainer).show();
485 485 $(this.editButton).hide();
486 486
487 487 $(this.previewContainer).hide();
488 488 $(this.previewButton).show();
489 489
490 490 this.setActionButtonsDisabled(true);
491 491 self.cm.setValue(content);
492 492 self.cm.setOption("readOnly", false);
493 493 };
494 494
495 495 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
496 496 failHandler = failHandler || function() {};
497 497 var postData = toQueryString(postData);
498 498 var request = $.ajax({
499 499 url: url,
500 500 type: 'POST',
501 501 data: postData,
502 502 headers: {'X-PARTIAL-XHR': true}
503 503 })
504 504 .done(function(data) {
505 505 successHandler(data);
506 506 })
507 507 .fail(function(data, textStatus, errorThrown){
508 508 alert(
509 509 "Error while submitting comment.\n" +
510 510 "Error code {0} ({1}).".format(data.status, data.statusText));
511 511 failHandler()
512 512 });
513 513 return request;
514 514 };
515 515
516 516 // overwrite a submitHandler, we need to do it for inline comments
517 517 this.setHandleFormSubmit = function(callback) {
518 518 this.handleFormSubmit = callback;
519 519 };
520 520
521 521 // default handler for for submit for main comments
522 522 this.handleFormSubmit = function() {
523 523 var text = self.cm.getValue();
524 524 var status = self.getCommentStatus();
525 525
526 526 if (text === "" && !status) {
527 527 return;
528 528 }
529 529
530 530 var excludeCancelBtn = false;
531 531 var submitEvent = true;
532 532 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
533 533 self.cm.setOption("readOnly", true);
534 534 var postData = {
535 535 'text': text,
536 536 'changeset_status': status,
537 537 'csrf_token': CSRF_TOKEN
538 538 };
539 539
540 540 var submitSuccessCallback = function(o) {
541 541 if (status) {
542 542 location.reload(true);
543 543 } else {
544 544 $('#injected_page_comments').append(o.rendered_text);
545 545 self.resetCommentFormState();
546 546 bindDeleteCommentButtons();
547 547 timeagoActivate();
548 548 tooltip_activate();
549 549 }
550 550 };
551 551 var submitFailCallback = function(){
552 552 self.resetCommentFormState(text)
553 553 };
554 554 self.submitAjaxPOST(
555 555 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
556 556 };
557 557
558 558 this.previewSuccessCallback = function(o) {
559 559 $(self.previewBoxSelector).html(o);
560 560 $(self.previewBoxSelector).removeClass('unloaded');
561 561
562 562 // swap buttons
563 563 $(self.previewButton).hide();
564 564 $(self.editButton).show();
565 565
566 566 // unlock buttons
567 567 self.setActionButtonsDisabled(false);
568 568 };
569 569
570 570 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
571 571 excludeCancelBtn = excludeCancelBtn || false;
572 572 submitEvent = submitEvent || false;
573 573
574 574 $(this.editButton).prop('disabled', state);
575 575 $(this.previewButton).prop('disabled', state);
576 576
577 577 if (!excludeCancelBtn) {
578 578 $(this.cancelButton).prop('disabled', state);
579 579 }
580 580
581 581 var submitState = state;
582 582 if (!submitEvent && this.getCommentStatus() && !this.lineNo) {
583 583 // if the value of commit review status is set, we allow
584 584 // submit button, but only on Main form, lineNo means inline
585 585 submitState = false
586 586 }
587 587 $(this.submitButton).prop('disabled', submitState);
588 588 if (submitEvent) {
589 $(this.submitButton).val(_TM['Submitting...']);
589 $(this.submitButton).val(_gettext('Submitting...'));
590 590 } else {
591 591 $(this.submitButton).val(this.submitButtonText);
592 592 }
593 593
594 594 };
595 595
596 596 // lock preview/edit/submit buttons on load, but exclude cancel button
597 597 var excludeCancelBtn = true;
598 598 this.setActionButtonsDisabled(true, excludeCancelBtn);
599 599
600 600 // anonymous users don't have access to initialized CM instance
601 601 if (this.cm !== undefined){
602 602 this.cm.on('change', function(cMirror) {
603 603 if (cMirror.getValue() === "") {
604 604 self.setActionButtonsDisabled(true, excludeCancelBtn)
605 605 } else {
606 606 self.setActionButtonsDisabled(false, excludeCancelBtn)
607 607 }
608 608 });
609 609 }
610 610
611 611 $(this.editButton).on('click', function(e) {
612 612 e.preventDefault();
613 613
614 614 $(self.previewButton).show();
615 615 $(self.previewContainer).hide();
616 616 $(self.editButton).hide();
617 617 $(self.editContainer).show();
618 618
619 619 });
620 620
621 621 $(this.previewButton).on('click', function(e) {
622 622 e.preventDefault();
623 623 var text = self.cm.getValue();
624 624
625 625 if (text === "") {
626 626 return;
627 627 }
628 628
629 629 var postData = {
630 630 'text': text,
631 631 'renderer': DEFAULT_RENDERER,
632 632 'csrf_token': CSRF_TOKEN
633 633 };
634 634
635 635 // lock ALL buttons on preview
636 636 self.setActionButtonsDisabled(true);
637 637
638 638 $(self.previewBoxSelector).addClass('unloaded');
639 $(self.previewBoxSelector).html(_TM['Loading ...']);
639 $(self.previewBoxSelector).html(_gettext('Loading ...'));
640 640 $(self.editContainer).hide();
641 641 $(self.previewContainer).show();
642 642
643 643 // by default we reset state of comment preserving the text
644 644 var previewFailCallback = function(){
645 645 self.resetCommentFormState(text)
646 646 };
647 647 self.submitAjaxPOST(
648 648 self.previewUrl, postData, self.previewSuccessCallback, previewFailCallback);
649 649
650 650 });
651 651
652 652 $(this.submitForm).submit(function(e) {
653 653 e.preventDefault();
654 654 var allowedToSubmit = self.isAllowedToSubmit();
655 655 if (!allowedToSubmit){
656 656 return false;
657 657 }
658 658 self.handleFormSubmit();
659 659 });
660 660
661 661 }
662 662
663 663 return CommentForm;
664 664 })();
@@ -1,309 +1,309 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * Search file list
21 21 */
22 22 // global reference to file-node filter
23 23 var _NODEFILTER = {};
24 24
25 25 var fileBrowserListeners = function(node_list_url, url_base){
26 26 var n_filter = $('#node_filter').get(0);
27 27
28 28 _NODEFILTER.filterTimeout = null;
29 29 var nodes = null;
30 30
31 31 _NODEFILTER.fetchNodes = function(callback) {
32 32 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': true}})
33 33 .done(function(data){
34 34 nodes = data.nodes;
35 35 if (callback) {
36 36 callback();
37 37 }
38 38 })
39 39 .fail(function(data){
40 40 console.log('failed to load');
41 41 });
42 42 };
43 43
44 44 _NODEFILTER.fetchNodesCallback = function() {
45 45 $('#node_filter_box_loading').hide();
46 46 $('#node_filter_box').removeClass('hidden').show();
47 47 n_filter.focus();
48 48 if ($('#node_filter').hasClass('init')){
49 49 n_filter.value = '';
50 50 $('#node_filter').removeClass('init');
51 51 }
52 52 };
53 53
54 54 _NODEFILTER.initFilter = function(){
55 55 $('#node_filter_box_loading').removeClass('hidden').show();
56 56 $('#search_activate_id').hide();
57 57 $('#search_deactivate_id').removeClass('hidden').show();
58 58 $('#add_node_id').hide();
59 59 _NODEFILTER.fetchNodes(_NODEFILTER.fetchNodesCallback);
60 60 };
61 61
62 62 _NODEFILTER.resetFilter = function(){
63 63 $('#node_filter_box_loading').hide();
64 64 $('#node_filter_box').hide();
65 65 $('#search_activate_id').show();
66 66 $('#search_deactivate_id').hide();
67 67 $('#add_node_id').show();
68 68 $('#tbody').show();
69 69 $('#tbody_filtered').hide();
70 70 $('#node_filter').val('');
71 71 };
72 72
73 73 _NODEFILTER.fuzzy_match = function(filepath, query) {
74 74 var highlight = [];
75 75 var order = 0;
76 76 for (var i = 0; i < query.length; i++) {
77 77 var match_position = filepath.indexOf(query[i]);
78 78 if (match_position !== -1) {
79 79 var prev_match_position = highlight[highlight.length-1];
80 80 if (prev_match_position === undefined) {
81 81 highlight.push(match_position);
82 82 } else {
83 83 var current_match_position = prev_match_position + match_position + 1;
84 84 highlight.push(current_match_position);
85 85 order = order + current_match_position - prev_match_position;
86 86 }
87 87 filepath = filepath.substring(match_position+1);
88 88 } else {
89 89 return false;
90 90 }
91 91 }
92 92 return {'order': order,
93 93 'highlight': highlight};
94 94 };
95 95
96 96 _NODEFILTER.sortPredicate = function(a, b) {
97 97 if (a.order < b.order) return -1;
98 98 if (a.order > b.order) return 1;
99 99 if (a.filepath < b.filepath) return -1;
100 100 if (a.filepath > b.filepath) return 1;
101 101 return 0;
102 102 };
103 103
104 104 _NODEFILTER.updateFilter = function(elem, e) {
105 105 return function(){
106 106 // Reset timeout
107 107 _NODEFILTER.filterTimeout = null;
108 108 var query = elem.value.toLowerCase();
109 109 var match = [];
110 110 var matches_max = 20;
111 111 if (query !== ""){
112 112 var results = [];
113 113 for(var k=0;k<nodes.length;k++){
114 114 var result = _NODEFILTER.fuzzy_match(
115 115 nodes[k].name.toLowerCase(), query);
116 116 if (result) {
117 117 result.type = nodes[k].type;
118 118 result.filepath = nodes[k].name;
119 119 results.push(result);
120 120 }
121 121 }
122 122 results = results.sort(_NODEFILTER.sortPredicate);
123 123 var limit = matches_max;
124 124 if (results.length < matches_max) {
125 125 limit = results.length;
126 126 }
127 127 for (var i=0; i<limit; i++){
128 128 if(query && results.length > 0){
129 129 var n = results[i].filepath;
130 130 var t = results[i].type;
131 131 var n_hl = n.split("");
132 132 var pos = results[i].highlight;
133 133 for (var j = 0; j < pos.length; j++) {
134 134 n_hl[pos[j]] = "<em>" + n_hl[pos[j]] + "</em>";
135 135 }
136 136 n_hl = n_hl.join("");
137 137 var new_url = url_base.replace('__FPATH__',n);
138 138
139 139 var typeObj = {
140 140 dir: 'icon-folder browser-dir',
141 141 file: 'icon-file browser-file'
142 142 };
143 143 var typeIcon = '<i class="{0}"></i>'.format(typeObj[t]);
144 144 match.push('<tr class="browser-result"><td><a class="browser-{0} pjax-link" href="{1}">{2}{3}</a></td><td colspan="5"></td></tr>'.format(t,new_url,typeIcon, n_hl));
145 145 }
146 146 }
147 147 if(results.length > limit){
148 148 var truncated_count = results.length - matches_max;
149 149 if (truncated_count === 1) {
150 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _TM['truncated result']));
150 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated result')));
151 151 } else {
152 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _TM['truncated results']));
152 match.push('<tr><td>{0} {1}</td><td colspan="5"></td></tr>'.format(truncated_count, _gettext('truncated results')));
153 153 }
154 154 }
155 155 }
156 156 if (query !== ""){
157 157 $('#tbody').hide();
158 158 $('#tbody_filtered').show();
159 159
160 160 if (match.length === 0){
161 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
161 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_gettext('No matching files')));
162 162 }
163 163 $('#tbody_filtered').html(match.join(""));
164 164 }
165 165 else{
166 166 $('#tbody').show();
167 167 $('#tbody_filtered').hide();
168 168 }
169 169
170 170 };
171 171 };
172 172
173 173 var scrollDown = function(element){
174 174 var elementBottom = element.offset().top + $(element).outerHeight();
175 175 var windowBottom = window.innerHeight + $(window).scrollTop();
176 176 if (elementBottom > windowBottom) {
177 177 var offset = elementBottom - window.innerHeight;
178 178 $('html,body').scrollTop(offset);
179 179 return false;
180 180 }
181 181 return true;
182 182 };
183 183
184 184 var scrollUp = function(element){
185 185 if (element.offset().top < $(window).scrollTop()) {
186 186 $('html,body').scrollTop(element.offset().top);
187 187 return false;
188 188 }
189 189 return true;
190 190 };
191 191
192 192 $('#filter_activate').click(function() {
193 193 _NODEFILTER.initFilter();
194 194 });
195 195
196 196 $('#filter_deactivate').click(function() {
197 197 _NODEFILTER.resetFilter();
198 198 });
199 199
200 200 $(n_filter).click(function() {
201 201 if ($('#node_filter').hasClass('init')){
202 202 n_filter.value = '';
203 203 $('#node_filter').removeClass('init');
204 204 }
205 205 });
206 206
207 207 $(n_filter).keydown(function(e) {
208 208 if (e.keyCode === 40){ // Down
209 209 if ($('.browser-highlight').length === 0){
210 210 $('.browser-result').first().addClass('browser-highlight');
211 211 } else {
212 212 var next = $('.browser-highlight').next();
213 213 if (next.length !== 0) {
214 214 $('.browser-highlight').removeClass('browser-highlight');
215 215 next.addClass('browser-highlight');
216 216 }
217 217 }
218 218 scrollDown($('.browser-highlight'));
219 219 }
220 220 if (e.keyCode === 38){ // Up
221 221 e.preventDefault();
222 222 if ($('.browser-highlight').length !== 0){
223 223 var next = $('.browser-highlight').prev();
224 224 if (next.length !== 0) {
225 225 $('.browser-highlight').removeClass('browser-highlight');
226 226 next.addClass('browser-highlight');
227 227 }
228 228 }
229 229 scrollUp($('.browser-highlight'));
230 230 }
231 231 if (e.keyCode === 13){ // Enter
232 232 if ($('.browser-highlight').length !== 0){
233 233 var url = $('.browser-highlight').find('.pjax-link').attr('href');
234 234 $.pjax({url: url, container: '#pjax-container', timeout: pjaxTimeout});
235 235 }
236 236 }
237 237 if (e.keyCode === 27){ // Esc
238 238 _NODEFILTER.resetFilter();
239 239 $('html,body').scrollTop(0);
240 240 }
241 241 });
242 242 var capture_keys = [40, 38, 39, 37, 13, 27];
243 243 $(n_filter).keyup(function(e) {
244 244 if ($.inArray(e.keyCode, capture_keys) === -1){
245 245 clearTimeout(_NODEFILTER.filterTimeout);
246 246 _NODEFILTER.filterTimeout = setTimeout(_NODEFILTER.updateFilter(n_filter, e),200);
247 247 }
248 248 });
249 249 };
250 250
251 251 var getIdentNode = function(n){
252 252 // iterate through nodes until matched interesting node
253 253 if (typeof n === 'undefined'){
254 254 return -1;
255 255 }
256 256 if(typeof n.id !== "undefined" && n.id.match('L[0-9]+')){
257 257 return n;
258 258 }
259 259 else{
260 260 return getIdentNode(n.parentNode);
261 261 }
262 262 };
263 263
264 264 var getSelectionLink = function(e) {
265 265 // get selection from start/to nodes
266 266 if (typeof window.getSelection !== "undefined") {
267 267 s = window.getSelection();
268 268
269 269 from = getIdentNode(s.anchorNode);
270 270 till = getIdentNode(s.focusNode);
271 271
272 272 f_int = parseInt(from.id.replace('L',''));
273 273 t_int = parseInt(till.id.replace('L',''));
274 274
275 275 if (f_int > t_int){
276 276 // highlight from bottom
277 277 offset = -35;
278 278 ranges = [t_int,f_int];
279 279 }
280 280 else{
281 281 // highligth from top
282 282 offset = 35;
283 283 ranges = [f_int,t_int];
284 284 }
285 285 // if we select more than 2 lines
286 286 if (ranges[0] !== ranges[1]){
287 287 if($('#linktt').length === 0){
288 288 hl_div = document.createElement('div');
289 289 hl_div.id = 'linktt';
290 290 }
291 291 hl_div.innerHTML = '';
292 292
293 293 anchor = '#L'+ranges[0]+'-'+ranges[1];
294 294 var link = document.createElement('a');
295 295 link.href = location.href.substring(0,location.href.indexOf('#'))+anchor;
296 link.innerHTML = _TM['Selection link'];
296 link.innerHTML = _gettext('Selection link');
297 297 hl_div.appendChild(link);
298 298 $('#codeblock').append(hl_div);
299 299
300 300 var xy = $(till).offset();
301 301 $('#linktt').addClass('hl-tip-box tip-box');
302 302 $('#linktt').offset({top: xy.top + offset, left: xy.left});
303 303 $('#linktt').css('visibility','visible');
304 304 }
305 305 else{
306 306 $('#linktt').css('visibility','hidden');
307 307 }
308 308 }
309 309 };
@@ -1,74 +1,74 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var onSuccessFollow = function(target){
20 20 var f = $(target);
21 21 var f_cnt = $('#current_followers_count');
22 22
23 23 if(f.hasClass('follow')){
24 24 f.removeClass('follow');
25 25 f.addClass('following');
26 f.attr('title', _TM['Stop following this repository']);
27 $(f).html(_TM['Unfollow']);
26 f.attr('title', _gettext('Stop following this repository'));
27 $(f).html(_gettext('Unfollow'));
28 28 if(f_cnt.length){
29 29 var cnt = Number(f_cnt.html())+1;
30 30 f_cnt.html(cnt);
31 31 }
32 32 }
33 33 else{
34 34 f.removeClass('following');
35 35 f.addClass('follow');
36 f.attr('title', _TM['Start following this repository']);
37 $(f).html(_TM['Follow']);
36 f.attr('title', _gettext('Start following this repository'));
37 $(f).html(_gettext('Follow'));
38 38 if(f_cnt.length){
39 39 var cnt = Number(f_cnt.html())-1;
40 40 f_cnt.html(cnt);
41 41 }
42 42 }
43 43 };
44 44
45 45 // TODO:: check if the function is needed. 0 usage found
46 46 var toggleFollowingUser = function(target,follows_user_id,token,user_id){
47 47 var args = {
48 48 'follows_user_id': follows_user_id,
49 49 'auth_token': token,
50 50 'csrf_token': CSRF_TOKEN
51 51 };
52 52 if(user_id != undefined){
53 53 args.user_id = user_id
54 54 }
55 55 ajaxPOST(pyroutes.url('toggle_following'), args, function(){
56 56 onSuccessFollow(target);
57 57 });
58 58 return false;
59 59 };
60 60
61 61 var toggleFollowingRepo = function(target,follows_repo_id,token,user_id){
62 62 var args = {
63 63 'follows_repo_id': follows_repo_id,
64 64 'auth_token': token,
65 65 'csrf_token': CSRF_TOKEN
66 66 };
67 67 if(user_id != undefined){
68 68 args.user_id = user_id
69 69 }
70 70 ajaxPOST(pyroutes.url('toggle_following'), args, function(){
71 71 onSuccessFollow(target);
72 72 });
73 73 return false;
74 74 };
@@ -1,126 +1,126 b''
1 1 // # Copyright (C) 2010-2016 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * TOOLTIP IMPL.
21 21 */
22 22
23 23 var TTIP = {};
24 24
25 25 TTIP.main = {
26 26 offset: [15,15],
27 27 maxWidth: 600,
28 28
29 29 set_listeners: function(tt){
30 30 $(tt).mouseover(tt, yt.show_tip);
31 31 $(tt).mousemove(tt, yt.move_tip);
32 32 $(tt).mouseout(tt, yt.close_tip);
33 33 },
34 34
35 35 init: function(){
36 36 $('#tip-box').remove();
37 37 yt.tipBox = document.createElement('div');
38 38 document.body.appendChild(yt.tipBox);
39 39 yt.tipBox.id = 'tip-box';
40 40
41 41 $(yt.tipBox).hide();
42 42 $(yt.tipBox).css('position', 'absolute');
43 43 if(yt.maxWidth !== null){
44 44 $(yt.tipBox).css('max-width', yt.maxWidth+'px');
45 45 }
46 46
47 47 var tooltips = $('.tooltip');
48 48 var ttLen = tooltips.length;
49 49
50 50 for(i=0;i<ttLen;i++){
51 51 yt.set_listeners(tooltips[i]);
52 52 }
53 53 },
54 54
55 55 show_tip: function(e, el){
56 56 e.stopPropagation();
57 57 e.preventDefault();
58 58 var el = e.data || el;
59 59 if(el.tagName.toLowerCase() === 'img'){
60 60 yt.tipText = el.alt ? el.alt : '';
61 61 } else {
62 62 yt.tipText = el.title ? el.title : '';
63 63 }
64 64
65 65 if(yt.tipText !== ''){
66 66 // save org title
67 67 $(el).attr('tt_title', yt.tipText);
68 68 // reset title to not show org tooltips
69 69 $(el).attr('title', '');
70 70
71 71 yt.tipBox.innerHTML = yt.tipText;
72 72 $(yt.tipBox).show();
73 73 }
74 74 },
75 75
76 76 move_tip: function(e, el){
77 77 e.stopPropagation();
78 78 e.preventDefault();
79 79 var el = e.data || el;
80 80 var movePos = [e.pageX, e.pageY];
81 81 $(yt.tipBox).css('top', (movePos[1] + yt.offset[1]) + 'px')
82 82 $(yt.tipBox).css('left', (movePos[0] + yt.offset[0]) + 'px')
83 83 },
84 84
85 85 close_tip: function(e, el){
86 86 e.stopPropagation();
87 87 e.preventDefault();
88 88 var el = e.data || el;
89 89 $(yt.tipBox).hide();
90 90 $(el).attr('title', $(el).attr('tt_title'));
91 91 $('#tip-box').hide();
92 92 }
93 93 };
94 94
95 95 /**
96 96 * tooltip activate
97 97 */
98 98 var tooltip_activate = function(){
99 99 yt = TTIP.main;
100 100 $(document).ready(yt.init);
101 101 };
102 102
103 103 /**
104 104 * show changeset tooltip
105 105 */
106 106 var show_changeset_tooltip = function(){
107 107 $('.lazy-cs').mouseover(function(e) {
108 108 var target = e.currentTarget;
109 109 var rid = $(target).attr('raw_id');
110 110 var repo_name = $(target).attr('repo_name');
111 111 var ttid = 'tt-'+rid;
112 112 var success = function(o){
113 113 $(target).addClass('tooltip')
114 114 $(target).attr('title', o['message']);
115 115 TTIP.main.show_tip(e, target);
116 116 }
117 117 if(rid && !$(target).hasClass('tooltip')){
118 118 $(target).attr('id', ttid);
119 $(target).attr('title', _TM['loading ...']);
119 $(target).attr('title', _gettext('loading ...'));
120 120 TTIP.main.set_listeners(target);
121 121 TTIP.main.show_tip(e, target);
122 122 var url = pyroutes.url('changeset_info', {"repo_name":repo_name, "revision": rid});
123 123 ajaxGET(url, success);
124 124 }
125 125 });
126 126 };
@@ -1,116 +1,116 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Authentication Settings')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 ${h.link_to(_('Admin'),h.url('admin_home'))}
13 13 &raquo;
14 14 ${_('Authentication Plugins')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_nav()">
18 18 ${self.menu_items(active='admin')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 27
28 28 <div class='sidebar-col-wrapper'>
29 29
30 30 <div class="sidebar">
31 31 <ul class="nav nav-pills nav-stacked">
32 32 % for item in resource.get_root().get_nav_list():
33 33 <li ${'class=active' if item == resource else ''}>
34 34 <a href="${request.resource_path(item, route_name='auth_home')}">${item.display_name}</a>
35 35 </li>
36 36 % endfor
37 37 </ul>
38 38 </div>
39 39
40 40 <div class="main-content-full-width">
41 41 ${h.secure_form(request.resource_path(resource, route_name='auth_home'))}
42 42 <div class="form">
43 43
44 44 <div class="panel panel-default">
45 45
46 46 <div class="panel-heading">
47 47 <h3 class="panel-title">${_("Enabled and Available Plugins")}</h3>
48 48 </div>
49 49
50 50 <div class="fields panel-body">
51 51
52 52 <div class="field">
53 53 <div class="label">${_("Enabled Plugins")}</div>
54 54 <div class="textarea text-area editor">
55 55 ${h.textarea('auth_plugins',cols=23,rows=5,class_="medium")}
56 56 </div>
57 57 <p class="help-block">
58 58 ${_('Add a list of plugins, separated by commas. '
59 59 'The order of the plugins is also the order in which '
60 60 'RhodeCode Enterprise will try to authenticate a user.')}
61 61 </p>
62 62 </div>
63 63
64 64 <div class="field">
65 65 <div class="label">${_('Available Built-in Plugins')}</div>
66 66 <ul class="auth_plugins">
67 67 %for plugin in available_plugins:
68 68 <li>
69 69 <div class="auth_buttons">
70 70 <span plugin_id="${plugin.get_id()}" class="toggle-plugin btn ${'btn-success' if plugin.get_id() in enabled_plugins else ''}">
71 71 ${_('enabled') if plugin.get_id() in enabled_plugins else _('disabled')}
72 72 </span>
73 73 ${plugin.get_display_name()} (${plugin.get_id()})
74 74 </div>
75 75 </li>
76 76 %endfor
77 77 </ul>
78 78 </div>
79 79
80 80 <div class="buttons">
81 81 ${h.submit('save',_('Save'),class_="btn")}
82 82 </div>
83 83 </div>
84 84 </div>
85 85 </div>
86 86 ${h.end_form()}
87 87 </div>
88 88 </div>
89 89 </div>
90 90
91 91 <script>
92 92 $('.toggle-plugin').click(function(e){
93 93 var auth_plugins_input = $('#auth_plugins');
94 94 var notEmpty = function(element, index, array) {
95 95 return (element != "");
96 96 }
97 97 var elems = auth_plugins_input.val().split(',').filter(notEmpty);
98 98 var cur_button = e.currentTarget;
99 99 var plugin_id = $(cur_button).attr('plugin_id');
100 100 if($(cur_button).hasClass('btn-success')){
101 101 elems.splice(elems.indexOf(plugin_id), 1);
102 102 auth_plugins_input.val(elems.join(','));
103 103 $(cur_button).removeClass('btn-success');
104 cur_button.innerHTML = _TM['disabled'];
104 cur_button.innerHTML = _gettext('disabled');
105 105 }
106 106 else{
107 107 if(elems.indexOf(plugin_id) == -1){
108 108 elems.push(plugin_id);
109 109 }
110 110 auth_plugins_input.val(elems.join(','));
111 111 $(cur_button).addClass('btn-success');
112 cur_button.innerHTML = _TM['enabled'];
112 cur_button.innerHTML = _gettext('enabled');
113 113 }
114 114 });
115 115 </script>
116 116 </%def>
@@ -1,141 +1,141 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('Users administration')}
6 6 %if c.rhodecode_name:
7 7 &middot; ${h.branding(c.rhodecode_name)}
8 8 %endif
9 9 </%def>
10 10
11 11 <%def name="breadcrumbs_links()">
12 12 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
13 13 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; <span id="user_count">0</span>
14 14 </%def>
15 15
16 16 <%def name="menu_bar_nav()">
17 17 ${self.menu_items(active='admin')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 <ul class="links">
26 26 <li>
27 27 <a href="${h.url('new_user')}" class="btn btn-small btn-success">${_(u'Add User')}</a>
28 28 </li>
29 29 </ul>
30 30 </div>
31 31
32 32 <div id="repos_list_wrap">
33 33 <table id="user_list_table" class="display"></table>
34 34 </div>
35 35 </div>
36 36
37 37 <script>
38 38 $(document).ready(function() {
39 39
40 40 var get_datatable_count = function(){
41 41 var datatable = $('#user_list_table').dataTable();
42 42 var api = datatable.api();
43 43 var total = api.page.info().recordsDisplay;
44 44 var active = datatable.fnGetFilteredData();
45
46 $('#user_count').text(_TM["{0} active out of {1} users"].format(active, total));
45 var _text = _gettext("{0} active out of {1} users").format(active, total);
46 $('#user_count').text(_text);
47 47 };
48 48
49 49 // custom filter that filters by username OR email
50 50 $.fn.dataTable.ext.search.push(
51 51 function( settings, data, dataIndex ) {
52 52 var query = $('#q_filter').val();
53 53 var username = data[1];
54 54 var email = data[2];
55 55 var first_name = data[3];
56 56 var last_name = data[4];
57 57
58 58 var query_str = username + " " +
59 59 email + " " +
60 60 first_name + " " +
61 61 last_name;
62 62 if((query_str).indexOf(query) !== -1){
63 63 return true;
64 64 }
65 65 return false;
66 66 }
67 67 );
68 68 // filtered data plugin
69 69 $.fn.dataTableExt.oApi.fnGetFilteredData = function ( oSettings ) {
70 70 var res = [];
71 71 for ( var i=0, iLen=oSettings.fnRecordsDisplay() ; i<iLen ; i++ ) {
72 72 var record = oSettings.aoData[i]._aData;
73 73 if(record['active_raw']){
74 74 res.push(record);
75 75 }
76 76 }
77 77 return res.length;
78 78 };
79 79
80 80 // user list
81 81 $('#user_list_table').DataTable({
82 82 data: ${c.data|n},
83 83 dom: 'rtp',
84 84 pageLength: ${c.visual.admin_grid_items},
85 85 order: [[ 1, "asc" ]],
86 86 columns: [
87 87 { data: {"_": "gravatar"}, className: "td-gravatar" },
88 88 { data: {"_": "username",
89 89 "sort": "username_raw"}, title: "${_('Username')}", className: "td-user" },
90 90 { data: {"_": "email",
91 91 "sort": "email"}, title: "${_('Email')}", className: "td-email" },
92 92 { data: {"_": "first_name",
93 93 "sort": "first_name"}, title: "${_('First Name')}", className: "td-user" },
94 94 { data: {"_": "last_name",
95 95 "sort": "last_name"}, title: "${_('Last Name')}", className: "td-user" },
96 96 { data: {"_": "last_login",
97 97 "sort": "last_login_raw",
98 98 "type": Number}, title: "${_('Last login')}", className: "td-time" },
99 99 { data: {"_": "active",
100 100 "sort": "active_raw"}, title: "${_('Active')}", className: "td-active" },
101 101 { data: {"_": "admin",
102 102 "sort": "admin_raw"}, title: "${_('Admin')}", className: "td-admin" },
103 103 { data: {"_": "extern_type",
104 104 "sort": "extern_type"}, title: "${_('Authentication type')}", className: "td-type" },
105 105 { data: {"_": "action",
106 106 "sort": "action"}, title: "${_('Action')}", className: "td-action" }
107 107 ],
108 108 language: {
109 109 paginate: DEFAULT_GRID_PAGINATION
110 110 },
111 111 "initComplete": function( settings, json ) {
112 112 get_datatable_count();
113 113 tooltip_activate();
114 114 },
115 115 "createdRow": function ( row, data, index ) {
116 116 if (!data['active_raw']){
117 117 $(row).addClass('closed')
118 118 }
119 119 }
120 120 });
121 121
122 122 // update the counter when doing search
123 123 $('#user_list_table').on( 'search.dt', function (e,settings) {
124 124 get_datatable_count();
125 125 });
126 126
127 127 // filter, filter both grids
128 128 $('#q_filter').on( 'keyup', function () {
129 129 var user_api = $('#user_list_table').dataTable().api();
130 130 user_api
131 131 .draw();
132 132 });
133 133
134 134 // refilter table if page load via back button
135 135 $("#q_filter").trigger('keyup');
136 136
137 137 });
138 138
139 139 </script>
140 140
141 141 </%def>
@@ -1,415 +1,415 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name}
7 7 %if c.changelog_for_path:
8 8 /${c.changelog_for_path}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.changelog_for_path:
17 17 /${c.changelog_for_path}
18 18 %endif
19 19 ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_nav()">
23 23 ${self.menu_items(active='repositories')}
24 24 </%def>
25 25
26 26 <%def name="menu_bar_subnav()">
27 27 ${self.repo_menu(active='changelog')}
28 28 </%def>
29 29
30 30 <%def name="main()">
31 31
32 32 <div class="box">
33 33 <div class="title">
34 34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 35 <ul class="links">
36 36 <li>
37 37 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
38 38 %if c.rhodecode_db_repo.fork:
39 39 <span>
40 40 <a id="compare_fork_button"
41 41 title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
42 42 class="btn btn-small"
43 43 href="${h.url('compare_url',
44 44 repo_name=c.rhodecode_db_repo.fork.repo_name,
45 45 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
46 46 source_ref=c.rhodecode_db_repo.landing_rev[1],
47 47 target_repo=c.repo_name,
48 48 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
49 49 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
50 50 merge=1)
51 51 }">
52 52 <i class="icon-loop"></i>
53 53 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
54 54 </a>
55 55 </span>
56 56 %endif
57 57
58 58 ## pr open link
59 59 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
60 60 <span>
61 61 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
62 62 ${_('Open new pull request')}
63 63 </a>
64 64 </span>
65 65 %endif
66 66
67 67 ## clear selection
68 68 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
69 69 ${_('Clear selection')}
70 70 </div>
71 71
72 72 </li>
73 73 </ul>
74 74 </div>
75 75
76 76 % if c.pagination:
77 77
78 78 <div class="graph-header">
79 79 <div id="filter_changelog">
80 80 ${h.hidden('branch_filter')}
81 81 %if c.selected_name:
82 82 <div class="btn btn-default" id="clear_filter" >
83 83 ${_('Clear filter')}
84 84 </div>
85 85 %endif
86 86 </div>
87 87 ${self.breadcrumbs('breadcrumbs_light')}
88 88 </div>
89 89
90 90 <div id="graph">
91 91 <div class="graph-col-wrapper">
92 92 <div id="graph_nodes">
93 93 <div id="graph_canvas" data-graph='${c.jsdata|n}'></div>
94 94 </div>
95 95 <div id="graph_content" class="main-content graph_full_width">
96 96
97 97 <div class="table">
98 98 <table id="changesets" class="rctable">
99 99 <tr>
100 100 <th></th>
101 101 <th></th>
102 102 <th>${_('Author')}</th>
103 103 <th>${_('Age')}</th>
104 104 <th></th>
105 105 <th>${_('Commit Message')}</th>
106 106 <th>${_('Commit')}</th>
107 107 <th></th>
108 108 <th>${_('Refs')}</th>
109 109 </tr>
110 110 <tbody>
111 111 %for cnt,commit in enumerate(c.pagination):
112 112 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
113 113
114 114 <td class="td-checkbox">
115 115 ${h.checkbox(commit.raw_id,class_="commit-range")}
116 116 </td>
117 117 <td class="td-status">
118 118
119 119 %if c.statuses.get(commit.raw_id):
120 120 <div class="changeset-status-ico">
121 121 %if c.statuses.get(commit.raw_id)[2]:
122 122 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
123 123 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
124 124 </a>
125 125 %else:
126 126 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
127 127 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
128 128 </a>
129 129 %endif
130 130 </div>
131 131 %endif
132 132 </td>
133 133 <td class="td-user">
134 134 ${self.gravatar(h.email_or_none(commit.author))}
135 135 <span title="${commit.author}" class="user">${h.link_to_user(commit.author, length=22)}</span>
136 136 </td>
137 137 <td class="td-time">
138 138 ${h.age_component(commit.date)}
139 139 </td>
140 140
141 141 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
142 142 <div class="show_more_col">
143 143 <i class="show_more"></i>&nbsp;
144 144 </div>
145 145 </td>
146 146 <td class="mid td-description">
147 147 <div class="log-container truncate-wrap">
148 148 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
149 149 </div>
150 150 </td>
151 151
152 152 <td class="td-hash">
153 153 <code>
154 154 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
155 155 <span class="commit_hash">${h.show_id(commit)}</span>
156 156 </a>
157 157 </code>
158 158 </td>
159 159
160 160 <td class="td-comments comments-col">
161 161 %if c.comments.get(commit.raw_id):
162 162 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
163 163 ${len(c.comments[commit.raw_id])} <i class="icon-comment icon-comment-colored"></i>
164 164 </a>
165 165 %endif
166 166 </td>
167 167
168 168 <td class="td-tags tags-col truncate-wrap">
169 169 <div class="truncate tags-truncate" id="t-${commit.raw_id}">
170 170 ## branch
171 171 %if commit.branch:
172 172 <span class="branchtag tag" title="${_('Branch %s') % commit.branch}">
173 173 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
174 174 </span>
175 175 %endif
176 176
177 177 ## bookmarks
178 178 %if h.is_hg(c.rhodecode_repo):
179 179 %for book in commit.bookmarks:
180 180 <span class="tag booktag" title="${_('Bookmark %s') % book}">
181 181 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
182 182 </span>
183 183 %endfor
184 184 %endif
185 185
186 186 ## tags
187 187 %for tag in commit.tags:
188 188 <span class="tagtag tag" title="${_('Tag %s') % tag}">
189 189 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
190 190 </span>
191 191 %endfor
192 192
193 193 </div>
194 194 </td>
195 195 </tr>
196 196 %endfor
197 197 </tbody>
198 198 </table>
199 199 </div>
200 200 </div>
201 201 </div>
202 202 <div class="pagination-wh pagination-left">
203 203 ${c.pagination.pager('$link_previous ~2~ $link_next')}
204 204 </div>
205 205
206 206 <script type="text/javascript" src="${h.url('/js/jquery.commits-graph.js')}"></script>
207 207 <script type="text/javascript">
208 208 var cache = {};
209 209 $(function(){
210 210
211 211 // Create links to commit ranges when range checkboxes are selected
212 212 var $commitCheckboxes = $('.commit-range');
213 213 // cache elements
214 214 var $commitRangeContainer = $('#rev_range_container');
215 215 var $commitRangeClear = $('#rev_range_clear');
216 216
217 217 var checkboxRangeSelector = function(e){
218 218 var selectedCheckboxes = [];
219 219 for (pos in $commitCheckboxes){
220 220 if($commitCheckboxes[pos].checked){
221 221 selectedCheckboxes.push($commitCheckboxes[pos]);
222 222 }
223 223 }
224 224 var open_new_pull_request = $('#open_new_pull_request');
225 225 if(open_new_pull_request){
226 226 var selected_changes = selectedCheckboxes.length;
227 227 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
228 228 open_new_pull_request.hide();
229 229 } else {
230 230 if (selected_changes == 1) {
231 open_new_pull_request.html(_TM['Open new pull request for selected commit']);
231 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
232 232 } else if (selected_changes == 0) {
233 open_new_pull_request.html(_TM['Open new pull request']);
233 open_new_pull_request.html(_gettext('Open new pull request'));
234 234 }
235 235 open_new_pull_request.show();
236 236 }
237 237 }
238 238
239 239 if (selectedCheckboxes.length>0){
240 240 var revEnd = selectedCheckboxes[0].name;
241 241 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
242 242 var url = pyroutes.url('changeset_home',
243 243 {'repo_name': '${c.repo_name}',
244 244 'revision': revStart+'...'+revEnd});
245 245
246 246 var link = (revStart == revEnd)
247 ? _TM['Show selected commit __S']
248 : _TM['Show selected commits __S ... __E'];
247 ? _gettext('Show selected commit __S')
248 : _gettext('Show selected commits __S ... __E');
249 249
250 250 link = link.replace('__S', revStart.substr(0,6));
251 251 link = link.replace('__E', revEnd.substr(0,6));
252 252
253 253 $commitRangeContainer
254 254 .attr('href',url)
255 255 .html(link)
256 256 .show();
257 257
258 258 $commitRangeClear.show();
259 259 var _url = pyroutes.url('pullrequest_home',
260 260 {'repo_name': '${c.repo_name}',
261 261 'commit': revEnd});
262 262 open_new_pull_request.attr('href', _url);
263 263 $('#compare_fork_button').hide();
264 264 } else {
265 265 $commitRangeContainer.hide();
266 266 $commitRangeClear.hide();
267 267
268 268 %if c.branch_name:
269 269 var _url = pyroutes.url('pullrequest_home',
270 270 {'repo_name': '${c.repo_name}',
271 271 'branch':'${c.branch_name}'});
272 272 open_new_pull_request.attr('href', _url);
273 273 %else:
274 274 var _url = pyroutes.url('pullrequest_home',
275 275 {'repo_name': '${c.repo_name}'});
276 276 open_new_pull_request.attr('href', _url);
277 277 %endif
278 278 $('#compare_fork_button').show();
279 279 }
280 280 };
281 281
282 282 $commitCheckboxes.on('click', checkboxRangeSelector);
283 283
284 284 $commitRangeClear.on('click',function(e) {
285 285 $commitCheckboxes.attr('checked', false)
286 286 checkboxRangeSelector();
287 287 e.preventDefault();
288 288 });
289 289
290 290 // make sure the buttons are consistent when navigate back and forth
291 291 checkboxRangeSelector();
292 292
293 293
294 294 var msgs = $('.message');
295 295 // get first element height
296 296 var el = $('#graph_content .container')[0];
297 297 var row_h = el.clientHeight;
298 298 for (var i=0; i < msgs.length; i++) {
299 299 var m = msgs[i];
300 300
301 301 var h = m.clientHeight;
302 302 var pad = $(m).css('padding');
303 303 if (h > row_h) {
304 304 var offset = row_h - (h+12);
305 305 $(m.nextElementSibling).css('display','block');
306 306 $(m.nextElementSibling).css('margin-top',offset+'px');
307 307 }
308 308 }
309 309
310 310 $('.expand_commit').on('click',function(e){
311 311 var target_expand = $(this);
312 312 var cid = target_expand.data('commitId');
313 313
314 314 if (target_expand.hasClass('open')){
315 315 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
316 316 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
317 317 target_expand.removeClass('open');
318 318 }
319 319 else {
320 320 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
321 321 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
322 322 target_expand.addClass('open');
323 323 }
324 324 // redraw the graph
325 325 graph_options.height = $("#changesets").height();
326 326 $("canvas").remove();
327 327 $("[data-graph]").commits(graph_options);
328 328 });
329 329
330 330 $("#clear_filter").on("click", function() {
331 331 var filter = {'repo_name': '${c.repo_name}'};
332 332 window.location = pyroutes.url('changelog_home', filter);
333 333 });
334 334
335 335 $("#branch_filter").select2({
336 336 'dropdownAutoWidth': true,
337 337 'width': 'resolve',
338 338 'placeholder': "${c.selected_name or _('Filter changelog')}",
339 339 containerCssClass: "drop-menu",
340 340 dropdownCssClass: "drop-menu-dropdown",
341 341 query: function(query){
342 342 var key = 'cache';
343 343 var cached = cache[key] ;
344 344 if(cached) {
345 345 var data = {results: []};
346 346 //filter results
347 347 $.each(cached.results, function(){
348 348 var section = this.text;
349 349 var children = [];
350 350 $.each(this.children, function(){
351 351 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
352 352 children.push({'id': this.id, 'text': this.text, 'type': this.type})
353 353 }
354 354 });
355 355 data.results.push({'text': section, 'children': children});
356 356 query.callback({results: data.results});
357 357 });
358 358 }else{
359 359 $.ajax({
360 360 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
361 361 data: {},
362 362 dataType: 'json',
363 363 type: 'GET',
364 364 success: function(data) {
365 365 cache[key] = data;
366 366 query.callback({results: data.results});
367 367 }
368 368 })
369 369 }
370 370 }
371 371 });
372 372
373 373 $('#branch_filter').on('change', function(e){
374 374 var data = $('#branch_filter').select2('data');
375 375 var selected = data.text;
376 376 var filter = {'repo_name': '${c.repo_name}'};
377 377 if(data.type == 'branch' || data.type == 'branch_closed'){
378 378 filter["branch"] = selected;
379 379 }
380 380 else if (data.type == 'book'){
381 381 filter["bookmark"] = selected;
382 382 }
383 383 window.location = pyroutes.url('changelog_home', filter);
384 384 });
385 385
386 386 // Determine max number of edges per row in graph
387 387 var jsdata = $.parseJSON($("[data-graph]").attr('data-graph'));
388 388 var edgeCount = 1;
389 389 $.each(jsdata, function(i, item){
390 390 $.each(item[2], function(key, value) {
391 391 if (value[1] > edgeCount){
392 392 edgeCount = value[1];
393 393 }
394 394 });
395 395 });
396 396 var x_step = Math.min(18, Math.floor(86 / edgeCount));
397 397 var graph_options = {
398 398 width: 100,
399 399 height: $("#changesets").height(),
400 400 x_step: x_step,
401 401 y_step: 42,
402 402 dotRadius: 3.5,
403 403 lineWidth: 2.5
404 404 };
405 405 $("[data-graph]").commits(graph_options);
406 406
407 407 });
408 408
409 409 </script>
410 410 %else:
411 411 ${_('There are no changes yet')}
412 412 %endif
413 413 </div>
414 414 </div>
415 415 </%def>
@@ -1,312 +1,312 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 6 <%namespace name="base" file="/base/base.html"/>
7 7
8 8 <%def name="comment_block(comment, inline=False)">
9 9 <div class="comment ${'comment-inline' if inline else ''}" id="comment-${comment.comment_id}" line="${comment.line_no}" data-comment-id="${comment.comment_id}">
10 10 <div class="meta">
11 11 <div class="author">
12 12 ${base.gravatar_with_user(comment.author.email, 16)}
13 13 </div>
14 14 <div class="date">
15 15 ${h.age_component(comment.modified_at, time_is_local=True)}
16 16 </div>
17 17 <div class="status-change">
18 18 %if comment.pull_request:
19 19 <a href="${h.url('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
20 20 %if comment.status_change:
21 21 ${_('Vote on pull request #%s') % comment.pull_request.pull_request_id}:
22 22 %else:
23 23 ${_('Comment on pull request #%s') % comment.pull_request.pull_request_id}
24 24 %endif
25 25 </a>
26 26 %else:
27 27 %if comment.status_change:
28 28 ${_('Status change on commit')}:
29 29 %else:
30 30 ${_('Comment on commit')}
31 31 %endif
32 32 %endif
33 33 </div>
34 34 %if comment.status_change:
35 35 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
36 36 <div title="${_('Commit status')}" class="changeset-status-lbl">
37 37 ${comment.status_change[0].status_lbl}
38 38 </div>
39 39 %endif
40 40 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
41 41
42 42
43 43 <div class="comment-links-block">
44 44
45 45 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
46 46 ## only super-admin, repo admin OR comment owner can delete
47 47 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
48 48 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
49 49 <div onClick="deleteComment(${comment.comment_id})" class="delete-comment"> ${_('Delete')}</div>
50 50 %if inline:
51 51 <div class="comment-links-divider"> | </div>
52 52 %endif
53 53 %endif
54 54 %endif
55 55
56 56 %if inline:
57 57
58 58 <div id="prev_c_${comment.comment_id}" class="comment-previous-link" title="${_('Previous comment')}">
59 59 <a class="arrow_comment_link disabled"><i class="icon-left"></i></a>
60 60 </div>
61 61
62 62 <div id="next_c_${comment.comment_id}" class="comment-next-link" title="${_('Next comment')}">
63 63 <a class="arrow_comment_link disabled"><i class="icon-right"></i></a>
64 64 </div>
65 65 %endif
66 66
67 67 </div>
68 68 </div>
69 69 <div class="text">
70 70 ${comment.render(mentions=True)|n}
71 71 </div>
72 72 </div>
73 73 </%def>
74 74
75 75 <%def name="comment_block_outdated(comment)">
76 76 <div class="comments" id="comment-${comment.comment_id}">
77 77 <div class="comment comment-wrapp">
78 78 <div class="meta">
79 79 <div class="author">
80 80 ${base.gravatar_with_user(comment.author.email, 16)}
81 81 </div>
82 82 <div class="date">
83 83 ${h.age_component(comment.modified_at, time_is_local=True)}
84 84 </div>
85 85 %if comment.status_change:
86 86 <span class="changeset-status-container">
87 87 <span class="changeset-status-ico">
88 88 <div class="${'flag_status %s' % comment.status_change[0].status}"></div>
89 89 </span>
90 90 <span title="${_('Commit status')}" class="changeset-status-lbl"> ${comment.status_change[0].status_lbl}</span>
91 91 </span>
92 92 %endif
93 93 <a class="permalink" href="#comment-${comment.comment_id}">&para;</a>
94 94 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
95 95 ## only super-admin, repo admin OR comment owner can delete
96 96 %if not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed()):
97 97 <div class="comment-links-block">
98 98 %if h.HasPermissionAny('hg.admin')() or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
99 99 <div data-comment-id=${comment.comment_id} class="delete-comment">${_('Delete')}</div>
100 100 %endif
101 101 </div>
102 102 %endif
103 103 </div>
104 104 <div class="text">
105 105 ${comment.render(mentions=True)|n}
106 106 </div>
107 107 </div>
108 108 </div>
109 109 </%def>
110 110
111 111 <%def name="comment_inline_form()">
112 112 <div id="comment-inline-form-template" style="display: none;">
113 113 <div class="comment-inline-form ac">
114 114 %if c.rhodecode_user.username != h.DEFAULT_USER:
115 115 ${h.form('#', class_='inline-form', method='get')}
116 116 <div id="edit-container_{1}" class="clearfix">
117 117 <div class="comment-title pull-left">
118 118 ${_('Create a comment on line {1}.')}
119 119 </div>
120 120 <div class="comment-help pull-right">
121 121 ${(_('Comments parsed using %s syntax with %s support.') % (
122 122 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
123 123 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
124 124 )
125 125 )|n
126 126 }
127 127 </div>
128 128 <div style="clear: both"></div>
129 129 <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
130 130 </div>
131 131 <div id="preview-container_{1}" class="clearfix" style="display: none;">
132 132 <div class="comment-help">
133 133 ${_('Comment preview')}
134 134 </div>
135 135 <div id="preview-box_{1}" class="preview-box"></div>
136 136 </div>
137 137 <div class="comment-footer">
138 138 <div class="comment-button hide-inline-form-button cancel-button">
139 139 ${h.reset('hide-inline-form', _('Cancel'), class_='btn hide-inline-form', id_="cancel-btn_{1}")}
140 140 </div>
141 141 <div class="action-buttons">
142 142 <input type="hidden" name="f_path" value="{0}">
143 143 <input type="hidden" name="line" value="{1}">
144 144 <button id="preview-btn_{1}" class="btn btn-secondary">${_('Preview')}</button>
145 145 <button id="edit-btn_{1}" class="btn btn-secondary" style="display: none;">${_('Edit')}</button>
146 146 ${h.submit('save', _('Comment'), class_='btn btn-success save-inline-form')}
147 147 </div>
148 148 ${h.end_form()}
149 149 </div>
150 150 %else:
151 151 ${h.form('', class_='inline-form comment-form-login', method='get')}
152 152 <div class="pull-left">
153 153 <div class="comment-help pull-right">
154 ${_('You need to be logged in to comment.')} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
154 ${_('You need to be logged in to comment.')} <a href="${h.route_path('login', _query={'came_from': h.url.current()})}">${_('Login now')}</a>
155 155 </div>
156 156 </div>
157 157 <div class="comment-button pull-right">
158 158 ${h.reset('hide-inline-form', _('Hide'), class_='btn hide-inline-form')}
159 159 </div>
160 160 <div class="clearfix"></div>
161 161 ${h.end_form()}
162 162 %endif
163 163 </div>
164 164 </div>
165 165 </%def>
166 166
167 167
168 168 ## generates inlines taken from c.comments var
169 169 <%def name="inlines(is_pull_request=False)">
170 170 %if is_pull_request:
171 171 <h2 id="comments">${ungettext("%d Pull Request Comment", "%d Pull Request Comments", len(c.comments)) % len(c.comments)}</h2>
172 172 %else:
173 173 <h2 id="comments">${ungettext("%d Commit Comment", "%d Commit Comments", len(c.comments)) % len(c.comments)}</h2>
174 174 %endif
175 175 %for path, lines_comments in c.inline_comments:
176 176 % for line, comments in lines_comments.iteritems():
177 177 <div style="display: none;" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
178 178 ## for each comment in particular line
179 179 %for comment in comments:
180 180 ${comment_block(comment, inline=True)}
181 181 %endfor
182 182 </div>
183 183 %endfor
184 184 %endfor
185 185
186 186 </%def>
187 187
188 188 ## generate inline comments and the main ones
189 189 <%def name="generate_comments(include_pull_request=False, is_pull_request=False)">
190 190 ## generate inlines for this changeset
191 191 ${inlines(is_pull_request)}
192 192
193 193 %for comment in c.comments:
194 194 <div id="comment-tr-${comment.comment_id}">
195 195 ## only render comments that are not from pull request, or from
196 196 ## pull request and a status change
197 197 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
198 198 ${comment_block(comment)}
199 199 %endif
200 200 </div>
201 201 %endfor
202 202 ## to anchor ajax comments
203 203 <div id="injected_page_comments"></div>
204 204 </%def>
205 205
206 206 ## MAIN COMMENT FORM
207 207 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
208 208 %if is_compare:
209 209 <% form_id = "comments_form_compare" %>
210 210 %else:
211 211 <% form_id = "comments_form" %>
212 212 %endif
213 213
214 214
215 215 %if is_pull_request:
216 216 <div class="pull-request-merge">
217 217 %if c.allowed_to_merge:
218 218 <div class="pull-request-wrap">
219 219 <div class="pull-right">
220 220 ${h.secure_form(url('pullrequest_merge', repo_name=c.repo_name, pull_request_id=c.pull_request.pull_request_id), id='merge_pull_request_form')}
221 221 <span data-role="merge-message">${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
222 222 <% merge_disabled = ' disabled' if c.pr_merge_status is False else '' %>
223 223 <input type="submit" id="merge_pull_request" value="${_('Merge Pull Request')}" class="btn${merge_disabled}"${merge_disabled}>
224 224 ${h.end_form()}
225 225 </div>
226 226 </div>
227 227 %else:
228 228 <div class="pull-request-wrap">
229 229 <div class="pull-right">
230 230 <span>${c.pr_merge_msg} ${c.approval_msg if c.approval_msg else ''}</span>
231 231 </div>
232 232 </div>
233 233 %endif
234 234 </div>
235 235 %endif
236 236 <div class="comments">
237 237 %if c.rhodecode_user.username != h.DEFAULT_USER:
238 238 <div class="comment-form ac">
239 239 ${h.secure_form(post_url, id_=form_id)}
240 240 <div id="edit-container" class="clearfix">
241 241 <div class="comment-title pull-left">
242 242 %if is_pull_request:
243 243 ${(_('Create a comment on this Pull Request.'))}
244 244 %elif is_compare:
245 245 ${(_('Create comments on this Commit range.'))}
246 246 %else:
247 247 ${(_('Create a comment on this Commit.'))}
248 248 %endif
249 249 </div>
250 250 <div class="comment-help pull-right">
251 251 ${(_('Comments parsed using %s syntax with %s support.') % (
252 252 ('<a href="%s">%s</a>' % (h.url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
253 253 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user'))
254 254 )
255 255 )|n
256 256 }
257 257 </div>
258 258 <div style="clear: both"></div>
259 259 ${h.textarea('text', class_="comment-block-ta")}
260 260 </div>
261 261
262 262 <div id="preview-container" class="clearfix" style="display: none;">
263 263 <div class="comment-title">
264 264 ${_('Comment preview')}
265 265 </div>
266 266 <div id="preview-box" class="preview-box"></div>
267 267 </div>
268 268
269 269 <div id="comment_form_extras">
270 270 %if form_extras and isinstance(form_extras, (list, tuple)):
271 271 % for form_ex_el in form_extras:
272 272 ${form_ex_el|n}
273 273 % endfor
274 274 %endif
275 275 </div>
276 276 <div class="comment-footer">
277 277 %if change_status:
278 278 <div class="status_box">
279 279 <select id="change_status" name="changeset_status">
280 280 <option></option> # Placeholder
281 281 %for status,lbl in c.commit_statuses:
282 282 <option value="${status}" data-status="${status}">${lbl}</option>
283 283 %if is_pull_request and change_status and status in ('approved', 'rejected'):
284 284 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
285 285 %endif
286 286 %endfor
287 287 </select>
288 288 </div>
289 289 %endif
290 290 <div class="action-buttons">
291 291 <button id="preview-btn" class="btn btn-secondary">${_('Preview')}</button>
292 292 <button id="edit-btn" class="btn btn-secondary" style="display:none;">${_('Edit')}</button>
293 293 <div class="comment-button">${h.submit('save', _('Comment'), class_="btn btn-success comment-button-input")}</div>
294 294 </div>
295 295 </div>
296 296 ${h.end_form()}
297 297 </div>
298 298 %endif
299 299 </div>
300 300 <script>
301 301 // init active elements of commentForm
302 302 var commitId = templateContext.commit_data.commit_id;
303 303 var pullRequestId = templateContext.pull_request_data.pull_request_id;
304 304 var lineNo;
305 305
306 306 var mainCommentForm = new CommentForm(
307 307 "#${form_id}", commitId, pullRequestId, lineNo, true);
308 308
309 309 mainCommentForm.initStatusChangeSelector();
310 310
311 311 </script>
312 312 </%def>
@@ -1,322 +1,322 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 %if c.compare_home:
6 6 ${_('%s Compare') % c.repo_name}
7 7 %else:
8 8 ${_('%s Compare') % c.repo_name} - ${'%s@%s' % (c.source_repo.repo_name, c.source_ref)} &gt; ${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 ${ungettext('%s commit','%s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
17 17 </%def>
18 18
19 19 <%def name="menu_bar_nav()">
20 20 ${self.menu_items(active='repositories')}
21 21 </%def>
22 22
23 23 <%def name="menu_bar_subnav()">
24 24 ${self.repo_menu(active='compare')}
25 25 </%def>
26 26
27 27 <%def name="main()">
28 28 <script type="text/javascript">
29 29 // set fake commitId on this commit-range page
30 30 templateContext.commit_data.commit_id = "${h.EmptyCommit().raw_id}";
31 31 </script>
32 32
33 33 <div class="box">
34 34 <div class="title">
35 35 ${self.repo_page_title(c.rhodecode_db_repo)}
36 36 <div class="breadcrumbs">
37 37 ${_('Compare Commits')}
38 38 </div>
39 39 </div>
40 40
41 41 <div class="table">
42 42 <div id="codeblock" class="diffblock">
43 43 <div class="code-header" >
44 44 <div class="compare_header">
45 45 ## The hidden elements are replaced with a select2 widget
46 46 <div class="compare-label">${_('Target')}</div>${h.hidden('compare_source')}
47 47 <div class="compare-label">${_('Source')}</div>${h.hidden('compare_target')}
48 48
49 49 %if not c.preview_mode:
50 50 <div class="compare-label"></div>
51 51 <div class="compare-buttons">
52 52 %if not c.compare_home:
53 53 <a id="btn-swap" class="btn btn-primary" href="${c.swap_url}"><i class="icon-refresh"></i> ${_('Swap')}</a>
54 54 %endif
55 55 <div id="compare_revs" class="btn btn-primary"><i class ="icon-loop"></i> ${_('Compare Commits')}</div>
56 56 %if c.files:
57 57 <div id="compare_changeset_status_toggle" class="btn btn-primary">${_('Comment')}</div>
58 58 %endif
59 59 </div>
60 60 %endif
61 61 </div>
62 62 </div>
63 63 </div>
64 64 ## use JS script to load it quickly before potentially large diffs render long time
65 65 ## this prevents from situation when large diffs block rendering of select2 fields
66 66 <script type="text/javascript">
67 67
68 68 var cache = {};
69 69
70 70 var formatSelection = function(repoName){
71 71 return function(data, container, escapeMarkup) {
72 72 var selection = data ? this.text(data) : "";
73 73 return escapeMarkup('{0}@{1}'.format(repoName, selection));
74 74 }
75 75 };
76 76
77 77 var feedCompareData = function(query, cachedValue){
78 78 var data = {results: []};
79 79 //filter results
80 80 $.each(cachedValue.results, function() {
81 81 var section = this.text;
82 82 var children = [];
83 83 $.each(this.children, function() {
84 84 if (query.term.length === 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) {
85 85 children.push({
86 86 'id': this.id,
87 87 'text': this.text,
88 88 'type': this.type
89 89 })
90 90 }
91 91 });
92 92 data.results.push({
93 93 'text': section,
94 94 'children': children
95 95 })
96 96 });
97 97 //push the typed in changeset
98 98 data.results.push({
99 'text': _TM['specify commit'],
99 'text': _gettext('specify commit'),
100 100 'children': [{
101 101 'id': query.term,
102 102 'text': query.term,
103 103 'type': 'rev'
104 104 }]
105 105 });
106 106 query.callback(data);
107 107 };
108 108
109 109 var loadCompareData = function(repoName, query, cache){
110 110 $.ajax({
111 111 url: pyroutes.url('repo_refs_data', {'repo_name': repoName}),
112 112 data: {},
113 113 dataType: 'json',
114 114 type: 'GET',
115 115 success: function(data) {
116 116 cache[repoName] = data;
117 117 query.callback({results: data.results});
118 118 }
119 119 })
120 120 };
121 121
122 122 var enable_fields = ${"false" if c.preview_mode else "true"};
123 123 $("#compare_source").select2({
124 124 placeholder: "${'%s@%s' % (c.source_repo.repo_name, c.source_ref)}",
125 125 containerCssClass: "drop-menu",
126 126 dropdownCssClass: "drop-menu-dropdown",
127 127 formatSelection: formatSelection("${c.source_repo.repo_name}"),
128 128 dropdownAutoWidth: true,
129 129 query: function(query) {
130 130 var repoName = '${c.source_repo.repo_name}';
131 131 var cachedValue = cache[repoName];
132 132
133 133 if (cachedValue){
134 134 feedCompareData(query, cachedValue);
135 135 }
136 136 else {
137 137 loadCompareData(repoName, query, cache);
138 138 }
139 139 }
140 140 }).select2("enable", enable_fields);
141 141
142 142 $("#compare_target").select2({
143 143 placeholder: "${'%s@%s' % (c.target_repo.repo_name, c.target_ref)}",
144 144 dropdownAutoWidth: true,
145 145 containerCssClass: "drop-menu",
146 146 dropdownCssClass: "drop-menu-dropdown",
147 147 formatSelection: formatSelection("${c.target_repo.repo_name}"),
148 148 query: function(query) {
149 149 var repoName = '${c.target_repo.repo_name}';
150 150 var cachedValue = cache[repoName];
151 151
152 152 if (cachedValue){
153 153 feedCompareData(query, cachedValue);
154 154 }
155 155 else {
156 156 loadCompareData(repoName, query, cache);
157 157 }
158 158 }
159 159 }).select2("enable", enable_fields);
160 160 var initial_compare_source = {id: "${c.source_ref}", type:"${c.source_ref_type}"};
161 161 var initial_compare_target = {id: "${c.target_ref}", type:"${c.target_ref_type}"};
162 162
163 163 $('#compare_revs').on('click', function(e) {
164 164 var source = $('#compare_source').select2('data') || initial_compare_source;
165 165 var target = $('#compare_target').select2('data') || initial_compare_target;
166 166 if (source && target) {
167 167 var url_data = {
168 168 repo_name: "${c.repo_name}",
169 169 source_ref: source.id,
170 170 source_ref_type: source.type,
171 171 target_ref: target.id,
172 172 target_ref_type: target.type
173 173 };
174 174 window.location = pyroutes.url('compare_url', url_data);
175 175 }
176 176 });
177 177 $('#compare_changeset_status_toggle').on('click', function(e) {
178 178 $('#compare_changeset_status').toggle();
179 179 });
180 180
181 181 </script>
182 182
183 183 ## changeset status form
184 184 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
185 185 ## main comment form and it status
186 186 <%
187 187 def revs(_revs):
188 188 form_inputs = []
189 189 for cs in _revs:
190 190 tmpl = '<input type="hidden" data-commit-id="%(cid)s" name="commit_ids" value="%(cid)s">' % {'cid': cs.raw_id}
191 191 form_inputs.append(tmpl)
192 192 return form_inputs
193 193 %>
194 194 <div id="compare_changeset_status" style="display: none;">
195 195 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision='0'*16), None, is_compare=True, form_extras=revs(c.commit_ranges))}
196 196 <script type="text/javascript">
197 197
198 198 mainCommentForm.setHandleFormSubmit(function(o) {
199 199 var text = mainCommentForm.cm.getValue();
200 200 var status = mainCommentForm.getCommentStatus();
201 201
202 202 if (text === "" && !status) {
203 203 return;
204 204 }
205 205
206 206 // we can pick which commits we want to make the comment by
207 207 // selecting them via click on preview pane, this will alter the hidden inputs
208 208 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
209 209
210 210 var commitIds = [];
211 211 $('#changeset_compare_view_content .compare_select').each(function(el) {
212 212 var commitId = this.id.replace('row-', '');
213 213 if ($(this).hasClass('hl') || !cherryPicked) {
214 214 $("input[data-commit-id='{0}']".format(commitId)).val(commitId)
215 215 commitIds.push(commitId);
216 216 } else {
217 217 $("input[data-commit-id='{0}']".format(commitId)).val('')
218 218 }
219 219 });
220 220
221 221 mainCommentForm.setActionButtonsDisabled(true);
222 222 mainCommentForm.cm.setOption("readOnly", true);
223 223 var postData = {
224 224 'text': text,
225 225 'changeset_status': status,
226 226 'commit_ids': commitIds,
227 227 'csrf_token': CSRF_TOKEN
228 228 };
229 229
230 230 var submitSuccessCallback = function(o) {
231 231 location.reload(true);
232 232 };
233 233 var submitFailCallback = function(){
234 234 mainCommentForm.resetCommentFormState(text)
235 235 };
236 236 mainCommentForm.submitAjaxPOST(
237 237 mainCommentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
238 238 });
239 239 </script>
240 240
241 241 </div>
242 242
243 243 %if c.compare_home:
244 244 <div id="changeset_compare_view_content">
245 245 <div class="help-block">${_('Compare commits, branches, bookmarks or tags.')}</div>
246 246 </div>
247 247 %else:
248 248 <div id="changeset_compare_view_content">
249 249 ##CS
250 250 <%include file="compare_commits.html"/>
251 251
252 252 ## FILES
253 253 <div class="cs_files_title">
254 254 <span class="cs_files_expand">
255 255 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
256 256 </span>
257 257 <h2>
258 258 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
259 259 </h2>
260 260 </div>
261 261 <div class="cs_files">
262 262 %if not c.files:
263 263 <p class="empty_data">${_('No files')}</p>
264 264 %endif
265 265 <table class="compare_view_files">
266 266 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
267 267 %for FID, change, path, stats, file in c.files:
268 268 <tr class="cs_${change} collapse_file" fid="${FID}">
269 269 <td class="cs_icon_td">
270 270 <span class="collapse_file_icon" fid="${FID}"></span>
271 271 </td>
272 272 <td class="cs_icon_td">
273 273 <div class="flag_status not_reviewed hidden"></div>
274 274 </td>
275 275 <td class="cs_${change}" id="a_${FID}">
276 276 <div class="node">
277 277 <a href="#a_${FID}">
278 278 <i class="icon-file-${change.lower()}"></i>
279 279 ${h.safe_unicode(path)}
280 280 </a>
281 281 </div>
282 282 </td>
283 283 <td>
284 284 <div class="changes pull-right">${h.fancy_file_stats(stats)}</div>
285 285 <div class="comment-bubble pull-right" data-path="${path}">
286 286 <i class="icon-comment"></i>
287 287 </div>
288 288 </td>
289 289 </tr>
290 290 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
291 291 <td></td>
292 292 <td></td>
293 293 <td class="cs_${change}">
294 294 %if c.target_repo.repo_name == c.repo_name:
295 295 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), c.source_ref, c.target_ref, change, file)}
296 296 %else:
297 297 ## this is slightly different case later, since the target repo can have this
298 298 ## file in target state than the source repo
299 299 ${diff_block.diff_menu(c.target_repo.repo_name, h.safe_unicode(path), c.source_ref, c.target_ref, change, file)}
300 300 %endif
301 301 </td>
302 302 <td class="td-actions rc-form">
303 303 </td>
304 304 </tr>
305 305 <tr id="tr_${FID}">
306 306 <td></td>
307 307 <td></td>
308 308 <td class="injected_diff" colspan="2">
309 309 ${diff_block.diff_block_simple([c.changes[FID]])}
310 310 </td>
311 311 </tr>
312 312 %endfor
313 313 </table>
314 314 % if c.limited_diff:
315 315 ${diff_block.changeset_message()}
316 316 % endif
317 317 </div>
318 318 %endif
319 319 </div>
320 320 </div>
321 321 </div>
322 322 </%def>
@@ -1,967 +1,967 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/debug_style/index.html"/>
3 3
4 4 <%def name="breadcrumbs_links()">
5 5 ${h.link_to(_('Style'), h.url('debug_style_home'))}
6 6 &raquo;
7 7 ${c.active}
8 8 </%def>
9 9
10 10
11 11 <%def name="real_main()">
12 12 <div class="box">
13 13 <div class="title">
14 14 ${self.breadcrumbs()}
15 15 </div>
16 16
17 17 <div class='sidebar-col-wrapper'>
18 18 ${self.sidebar()}
19 19
20 20 <div class="main-content">
21 21
22 22 <h2>Collapsable Content</h2>
23 23 <p>Where a section may have a very long list of information, it can be desirable to use collapsable content. There is a premade function for showing/hiding elements, though its use may or may not be practical, depending on the situation. Use it, or don't, on a case-by-case basis.</p>
24 24
25 25 <p><strong>To use the collapsable-content function:</strong> Create a toggle button using <code>&lt;div class="btn-collapse"&gt;Show More&lt;/div&gt;</code> and a data attribute using <code>data-toggle</code>. Clicking this button will toggle any sibling element(s) containing the class <code>collapsable-content</code> and an identical <code>data-toggle</code> attribute. It will also change the button to read "Show Less"; another click toggles it back to the previous state. Ideally, use pre-existing elements and add the class and attribute; creating a new div around the existing content may lead to unexpected results, as the toggle function will use <code>display:block</code> if no previous display specification was found.
26 26 </p>
27 27 <p>Notes:</p>
28 28 <ul>
29 29 <li>Changes made to the text of the button will require adjustment to the function, but for the sake of consistency and user experience, this is best avoided. </li>
30 30 <li>Collapsable content inside of a pjax loaded container will require <code>collapsableContent();</code> to be called from within the container. No variables are necessary.</li>
31 31 </ul>
32 32
33 33 </div> <!-- .main-content -->
34 34 </div> <!-- .sidebar-col-wrapper -->
35 35 </div> <!-- .box -->
36 36
37 37 <!-- CONTENT -->
38 38 <div id="content" class="wrapper">
39 39
40 40 <div class="main">
41 41
42 42 <div class="box">
43 43 <div class="title">
44 44 <h1>
45 45 Diff: enable filename with spaces on diffs
46 46 </h1>
47 47 <h1>
48 48 <i class="icon-hg" ></i>
49 49
50 50 <i class="icon-lock"></i>
51 51 <span><a href="/rhodecode-momentum">rhodecode-momentum</a></span>
52 52
53 53 </h1>
54 54 </div>
55 55
56 56 <div class="box pr-summary">
57 57 <div class="summary-details block-left">
58 58
59 59 <div class="pr-details-title">
60 60
61 61 Pull request #720 From Tue, 17 Feb 2015 16:21:38
62 62 <div class="btn-collapse" data-toggle="description">Show More</div>
63 63 </div>
64 64 <div id="summary" class="fields pr-details-content">
65 65 <div class="field">
66 66 <div class="label-summary">
67 67 <label>Origin:</label>
68 68 </div>
69 69 <div class="input">
70 70 <div>
71 71 <span class="tag">
72 72 <a href="/andersonsantos/rhodecode-momentum-fork#fix_574">book: fix_574</a>
73 73 </span>
74 74 <span class="clone-url">
75 75 <a href="/andersonsantos/rhodecode-momentum-fork">https://code.rhodecode.com/andersonsantos/rhodecode-momentum-fork</a>
76 76 </span>
77 77 </div>
78 78 <div>
79 79 <br>
80 80 <input type="text" value="hg pull -r 46b3d50315f0 https://code.rhodecode.com/andersonsantos/rhodecode-momentum-fork" readonly="readonly">
81 81 </div>
82 82 </div>
83 83 </div>
84 84 <div class="field">
85 85 <div class="label-summary">
86 86 <label>Review:</label>
87 87 </div>
88 88 <div class="input">
89 89 <div class="flag_status under_review tooltip pull-left" title="Pull request status calculated from votes"></div>
90 90 <span class="changeset-status-lbl tooltip" title="Pull request status calculated from votes">
91 91 Under Review
92 92 </span>
93 93
94 94 </div>
95 95 </div>
96 96 <div class="field collapsable-content" data-toggle="description">
97 97 <div class="label-summary">
98 98 <label>Description:</label>
99 99 </div>
100 100 <div class="input">
101 101 <div class="pr-description">Fixing issue <a class="issue- tracker-link" href="http://bugs.rhodecode.com/issues/574"># 574</a>, changing regex for capturing filenames</div>
102 102 </div>
103 103 </div>
104 104 <div class="field collapsable-content" data-toggle="description">
105 105 <div class="label-summary">
106 106 <label>Comments:</label>
107 107 </div>
108 108 <div class="input">
109 109 <div>
110 110 <div class="comments-number">
111 111 <a href="#inline-comments-container">0 Pull request comments</a>,
112 112 0 Inline Comments
113 113 </div>
114 114 </div>
115 115 </div>
116 116 </div>
117 117 </div>
118 118 </div>
119 119 <div>
120 120 <div class="reviewers-title block-right">
121 121 <div class="pr-details-title">
122 122 Author
123 123 </div>
124 124 </div>
125 125 <div class="block-right pr-details-content reviewers">
126 126 <ul class="group_members">
127 127 <li>
128 128 <img class="gravatar" src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=32" height="16" width="16">
129 129 <span class="user"> <a href="/_profiles/lolek">lolek (Lolek Santos)</a></span>
130 130 </li>
131 131 </ul>
132 132 </div>
133 133 <div class="reviewers-title block-right">
134 134 <div class="pr-details-title">
135 135 Pull request reviewers
136 136 <span class="btn-collapse" data-toggle="reviewers">Show More</span>
137 137 </div>
138 138
139 139 </div>
140 140 <div id="reviewers" class="block-right pr-details-content reviewers">
141 141
142 142 <ul id="review_members" class="group_members">
143 143 <li id="reviewer_70">
144 144 <div class="reviewers_member">
145 145 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
146 146 <div class="flag_status rejected pull-left reviewer_member_status"></div>
147 147 </div>
148 148 <img class="gravatar" src="https://secure.gravatar.com/avatar/153a0fab13160b3e64a2cbc7c0373506?d=identicon&amp;s=32" height="16" width="16">
149 149 <span class="user"> <a href="/_profiles/jenkins-tests">jenkins-tests</a> (reviewer)</span>
150 150 </div>
151 151 <input id="reviewer_70_input" type="hidden" value="70" name="review_members">
152 152 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(70, true)" style="visibility: hidden;">
153 153 <i class="icon-remove-sign"></i>
154 154 </div>
155 155 </li>
156 156 <li id="reviewer_33" class="collapsable-content" data-toggle="reviewers">
157 157 <div class="reviewers_member">
158 158 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
159 159 <div class="flag_status approved pull-left reviewer_member_status"></div>
160 160 </div>
161 161 <img class="gravatar" src="https://secure.gravatar.com/avatar/ffd6a317ec2b66be880143cd8459d0d9?d=identicon&amp;s=32" height="16" width="16">
162 162 <span class="user"> <a href="/_profiles/jenkins-tests">garbas (Rok Garbas)</a> (reviewer)</span>
163 163 </div>
164 164 </li>
165 165 <li id="reviewer_2" class="collapsable-content" data-toggle="reviewers">
166 166 <div class="reviewers_member">
167 167 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
168 168 <div class="flag_status not_reviewed pull-left reviewer_member_status"></div>
169 169 </div>
170 170 <img class="gravatar" src="https://secure.gravatar.com/avatar/aad9d40cac1259ea39b5578554ad9d64?d=identicon&amp;s=32" height="16" width="16">
171 171 <span class="user"> <a href="/_profiles/jenkins-tests">marcink (Marcin Kuzminski)</a> (reviewer)</span>
172 172 </div>
173 173 </li>
174 174 <li id="reviewer_36" class="collapsable-content" data-toggle="reviewers">
175 175 <div class="reviewers_member">
176 176 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
177 177 <div class="flag_status approved pull-left reviewer_member_status"></div>
178 178 </div>
179 179 <img class="gravatar" src="https://secure.gravatar.com/avatar/7a4da001a0af0016ed056ab523255db9?d=identicon&amp;s=32" height="16" width="16">
180 180 <span class="user"> <a href="/_profiles/jenkins-tests">johbo (Johannes Bornhold)</a> (reviewer)</span>
181 181 </div>
182 182 </li>
183 183 <li id="reviewer_47" class="collapsable-content" data-toggle="reviewers">
184 184 <div class="reviewers_member">
185 185 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
186 186 <div class="flag_status under_review pull-left reviewer_member_status"></div>
187 187 </div>
188 188 <img class="gravatar" src="https://secure.gravatar.com/avatar/8f6dc00dce79d6bd7d415be5cea6a008?d=identicon&amp;s=32" height="16" width="16">
189 189 <span class="user"> <a href="/_profiles/jenkins-tests">lisaq (Lisa Quatmann)</a> (reviewer)</span>
190 190 </div>
191 191 </li>
192 192 <li id="reviewer_49" class="collapsable-content" data-toggle="reviewers">
193 193 <div class="reviewers_member">
194 194 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
195 195 <div class="flag_status approved pull-left reviewer_member_status"></div>
196 196 </div>
197 197 <img class="gravatar" src="https://secure.gravatar.com/avatar/89f722927932a8f737a0feafb03a606e?d=identicon&amp;s=32" height="16" width="16">
198 198 <span class="user"> <a href="/_profiles/jenkins-tests">paris (Paris Kolios)</a> (reviewer)</span>
199 199 </div>
200 200 </li>
201 201 <li id="reviewer_50" class="collapsable-content" data-toggle="reviewers">
202 202 <div class="reviewers_member">
203 203 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
204 204 <div class="flag_status approved pull-left reviewer_member_status"></div>
205 205 </div>
206 206 <img class="gravatar" src="https://secure.gravatar.com/avatar/081322c975e8545ec269372405fbd016?d=identicon&amp;s=32" height="16" width="16">
207 207 <span class="user"> <a href="/_profiles/jenkins-tests">ergo (Marcin Lulek)</a> (reviewer)</span>
208 208 </div>
209 209 </li>
210 210 <li id="reviewer_54" class="collapsable-content" data-toggle="reviewers">
211 211 <div class="reviewers_member">
212 212 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
213 213 <div class="flag_status under_review pull-left reviewer_member_status"></div>
214 214 </div>
215 215 <img class="gravatar" src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=32" height="16" width="16">
216 216 <span class="user"> <a href="/_profiles/jenkins-tests">anderson (Anderson Santos)</a> (reviewer)</span>
217 217 </div>
218 218 </li>
219 219 <li id="reviewer_57" class="collapsable-content" data-toggle="reviewers">
220 220 <div class="reviewers_member">
221 221 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
222 222 <div class="flag_status approved pull-left reviewer_member_status"></div>
223 223 </div>
224 224 <img class="gravatar" src="https://secure.gravatar.com/avatar/23e2ee8f5fd462cba8129a40cc1e896c?d=identicon&amp;s=32" height="16" width="16">
225 225 <span class="user"> <a href="/_profiles/jenkins-tests">gmgauthier (Greg Gauthier)</a> (reviewer)</span>
226 226 </div>
227 227 </li>
228 228 <li id="reviewer_31" class="collapsable-content" data-toggle="reviewers">
229 229 <div class="reviewers_member">
230 230 <div class="reviewer_status tooltip pull-left" title="Not Reviewed">
231 231 <div class="flag_status under_review pull-left reviewer_member_status"></div>
232 232 </div>
233 233 <img class="gravatar" src="https://secure.gravatar.com/avatar/0c9a7e6674b6f0b35d98dbe073e3f0ab?d=identicon&amp;s=32" height="16" width="16">
234 234 <span class="user"> <a href="/_profiles/jenkins-tests">ostrobel (Oliver Strobel)</a> (reviewer)</span>
235 235 </div>
236 236 </li>
237 237 </ul>
238 238 <div id="add_reviewer_input" class="ac" style="display: none;">
239 239 </div>
240 240 </div>
241 241 </div>
242 242 </div>
243 243 </div>
244 244 <div class="box">
245 245 <div class="table" >
246 246 <div id="changeset_compare_view_content">
247 247 <div class="compare_view_commits_title">
248 248 <h2>Compare View: 6 commits<span class="btn-collapse" data-toggle="commits">Show More</span></h2>
249 249
250 250 </div>
251 251 <div class="container">
252 252
253 253
254 254 <table class="rctable compare_view_commits">
255 255 <tr>
256 256 <th>Time</th>
257 257 <th>Author</th>
258 258 <th>Commit</th>
259 259 <th></th>
260 260 <th>Title</th>
261 261 </tr>
262 262 <tr id="row-7e83e5cd7812dd9e055ce30e77c65cdc08154b43" commit_id="7e83e5cd7812dd9e055ce30e77c65cdc08154b43" class="compare_select">
263 263 <td class="td-time">
264 264 <span class="tooltip" title="3 hours and 23 minutes ago" tt_title="3 hours and 23 minutes ago">2015-02-18 10:13:34</span>
265 265 </td>
266 266 <td class="td-user">
267 267 <div class="gravatar_with_user">
268 268 <img class="gravatar" alt="gravatar" src="https://secure.gravatar.com/avatar/02cc31cea73b88b7209ba302c5967a9d?d=identicon&amp;s=16">
269 269 <span title="Lolek Santos <lolek@rhodecode.com>" class="user">brian (Brian Butler)</span>
270 270 </div>
271 271 </td>
272 272 <td class="td-hash">
273 273 <code>
274 274 <a href="/brian/documentation-rep/changeset/7e83e5cd7812dd9e055ce30e77c65cdc08154b43">r395:7e83e5cd7812</a>
275 275 </code>
276 276 </td>
277 277 <td class="expand_commit" data-commit-id="7e83e5cd7812dd9e055ce30e77c65cdc08154b43" title="Expand commit message">
278 278 <div class="show_more_col">
279 279 <i class="show_more"></i>
280 280 </div>
281 281 </td>
282 282 <td class="mid td-description">
283 283 <div class="log-container truncate-wrap">
284 284 <div id="c-7e83e5cd7812dd9e055ce30e77c65cdc08154b43" class="message truncate">rep: added how we doc to guide</div>
285 285 </div>
286 286 </td>
287 287 </tr>
288 288 <tr id="row-48ce1581bdb3aa7679c246cbdd3fb030623f5c87" commit_id="48ce1581bdb3aa7679c246cbdd3fb030623f5c87" class="compare_select">
289 289 <td class="td-time">
290 290 <span class="tooltip" title="4 hours and 18 minutes ago">2015-02-18 09:18:31</span>
291 291 </td>
292 292 <td class="td-user">
293 293 <div class="gravatar_with_user">
294 294 <img class="gravatar" alt="gravatar" src="https://secure.gravatar.com/avatar/02cc31cea73b88b7209ba302c5967a9d?d=identicon&amp;s=16">
295 295 <span title="Lolek Santos <lolek@rhodecode.com>" class="user">brian (Brian Butler)</span>
296 296 </div>
297 297 </td>
298 298 <td class="td-hash">
299 299 <code>
300 300 <a href="/brian/documentation-rep/changeset/48ce1581bdb3aa7679c246cbdd3fb030623f5c87">r394:48ce1581bdb3</a>
301 301 </code>
302 302 </td>
303 303 <td class="expand_commit" data-commit-id="48ce1581bdb3aa7679c246cbdd3fb030623f5c87" title="Expand commit message">
304 304 <div class="show_more_col">
305 305 <i class="show_more"></i>
306 306 </div>
307 307 </td>
308 308 <td class="mid td-description">
309 309 <div class="log-container truncate-wrap">
310 310 <div id="c-48ce1581bdb3aa7679c246cbdd3fb030623f5c87" class="message truncate">repo 0004 - typo</div>
311 311 </div>
312 312 </td>
313 313 </tr>
314 314 <tr id="row-982d857aafb4c71e7686e419c32b71c9a837257d" commit_id="982d857aafb4c71e7686e419c32b71c9a837257d" class="compare_select collapsable-content" data-toggle="commits">
315 315 <td class="td-time">
316 316 <span class="tooltip" title="4 hours and 22 minutes ago">2015-02-18 09:14:45</span>
317 317 </td>
318 318 <td class="td-user">
319 319 <span class="gravatar" commit_id="982d857aafb4c71e7686e419c32b71c9a837257d">
320 320 <img alt="gravatar" src="https://secure.gravatar.com/avatar/02cc31cea73b88b7209ba302c5967a9d?d=identicon&amp;s=28" height="14" width="14">
321 321 </span>
322 322 <span class="author">brian (Brian Butler)</span>
323 323 </td>
324 324 <td class="td-hash">
325 325 <code>
326 326 <a href="/brian/documentation-rep/changeset/982d857aafb4c71e7686e419c32b71c9a837257d">r393:982d857aafb4</a>
327 327 </code>
328 328 </td>
329 329 <td class="expand_commit" data-commit-id="982d857aafb4c71e7686e419c32b71c9a837257d" title="Expand commit message">
330 330 <div class="show_more_col">
331 331 <i class="show_more"></i>
332 332 </div>
333 333 </td>
334 334 <td class="mid td-description">
335 335 <div class="log-container truncate-wrap">
336 336 <div id="c-982d857aafb4c71e7686e419c32b71c9a837257d" class="message truncate">internals: how to doc section added</div>
337 337 </div>
338 338 </td>
339 339 </tr>
340 340 <tr id="row-4c7258ad1af6dae91bbaf87a933e3597e676fab8" commit_id="4c7258ad1af6dae91bbaf87a933e3597e676fab8" class="compare_select collapsable-content" data-toggle="commits">
341 341 <td class="td-time">
342 342 <span class="tooltip" title="20 hours and 16 minutes ago">2015-02-17 17:20:44</span>
343 343 </td>
344 344 <td class="td-user">
345 345 <span class="gravatar" commit_id="4c7258ad1af6dae91bbaf87a933e3597e676fab8">
346 346 <img alt="gravatar" src="https://secure.gravatar.com/avatar/02cc31cea73b88b7209ba302c5967a9d?d=identicon&amp;s=28" height="14" width="14">
347 347 </span>
348 348 <span class="author">brian (Brian Butler)</span>
349 349 </td>
350 350 <td class="td-hash">
351 351 <code>
352 352 <a href="/brian/documentation-rep/changeset/4c7258ad1af6dae91bbaf87a933e3597e676fab8">r392:4c7258ad1af6</a>
353 353 </code>
354 354 </td>
355 355 <td class="expand_commit" data-commit-id="4c7258ad1af6dae91bbaf87a933e3597e676fab8" title="Expand commit message">
356 356 <div class="show_more_col">
357 357 <i class="show_more"></i>
358 358 </div>
359 359 </td>
360 360 <td class="mid td-description">
361 361 <div class="log-container truncate-wrap">
362 362 <div id="c-4c7258ad1af6dae91bbaf87a933e3597e676fab8" class="message truncate">REP: 0004 Documentation standards</div>
363 363 </div>
364 364 </td>
365 365 </tr>
366 366 <tr id="row-46b3d50315f0f2b1f64485ac95af4f384948f9cb" commit_id="46b3d50315f0f2b1f64485ac95af4f384948f9cb" class="compare_select collapsable-content" data-toggle="commits">
367 367 <td class="td-time">
368 368 <span class="tooltip" title="18 hours and 19 minutes ago">2015-02-17 16:18:49</span>
369 369 </td>
370 370 <td class="td-user">
371 371 <span class="gravatar" commit_id="46b3d50315f0f2b1f64485ac95af4f384948f9cb">
372 372 <img alt="gravatar" src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=28" height="14" width="14">
373 373 </span>
374 374 <span class="author">anderson (Anderson Santos)</span>
375 375 </td>
376 376 <td class="td-hash">
377 377 <code>
378 378 <a href="/andersonsantos/rhodecode-momentum-fork/changeset/46b3d50315f0f2b1f64485ac95af4f384948f9cb">r8743:46b3d50315f0</a>
379 379 </code>
380 380 </td>
381 381 <td class="expand_commit" data-commit-id="46b3d50315f0f2b1f64485ac95af4f384948f9cb" title="Expand commit message">
382 382 <div class="show_more_col">
383 383 <i class="show_more" ></i>
384 384 </div>
385 385 </td>
386 386 <td class="mid td-description">
387 387 <div class="log-container truncate-wrap">
388 388 <div id="c-46b3d50315f0f2b1f64485ac95af4f384948f9cb" class="message truncate">Diff: created tests for the diff with filenames with spaces</div>
389 389
390 390 </div>
391 391 </td>
392 392 </tr>
393 393 <tr id="row-1e57d2549bd6c34798075bf05ac39f708bb33b90" commit_id="1e57d2549bd6c34798075bf05ac39f708bb33b90" class="compare_select collapsable-content" data-toggle="commits">
394 394 <td class="td-time">
395 395 <span class="tooltip" title="2 days ago">2015-02-16 10:06:08</span>
396 396 </td>
397 397 <td class="td-user">
398 398 <span class="gravatar" commit_id="1e57d2549bd6c34798075bf05ac39f708bb33b90">
399 399 <img alt="gravatar" src="https://secure.gravatar.com/avatar/72706ebd30734451af9ff3fb59f05ff1?d=identicon&amp;s=28" height="14" width="14">
400 400 </span>
401 401 <span class="author">anderson (Anderson Santos)</span>
402 402 </td>
403 403 <td class="td-hash">
404 404 <code>
405 405 <a href="/andersonsantos/rhodecode-momentum-fork/changeset/1e57d2549bd6c34798075bf05ac39f708bb33b90">r8742:1e57d2549bd6</a>
406 406 </code>
407 407 </td>
408 408 <td class="expand_commit" data-commit-id="1e57d2549bd6c34798075bf05ac39f708bb33b90" title="Expand commit message">
409 409 <div class="show_more_col">
410 410 <i class="show_more" ></i>
411 411 </div>
412 412 </td>
413 413 <td class="mid td-description">
414 414 <div class="log-container truncate-wrap">
415 415 <div id="c-1e57d2549bd6c34798075bf05ac39f708bb33b90" class="message truncate">Diff: fix renaming files with spaces <a class="issue-tracker-link" href="http://bugs.rhodecode.com/issues/574">#574</a></div>
416 416
417 417 </div>
418 418 </td>
419 419 </tr>
420 420 </table>
421 421 </div>
422 422
423 423 <script>
424 424 $('.expand_commit').on('click',function(e){
425 425 $(this).children('i').hide();
426 426 var cid = $(this).data('commitId');
427 427 $('#c-'+cid).css({'height': 'auto', 'margin': '.65em 1em .65em 0','white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'})
428 428 $('#t-'+cid).css({'height': 'auto', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'})
429 429 });
430 430 $('.compare_select').on('click',function(e){
431 431 var cid = $(this).attr('commit_id');
432 432 $('#row-'+cid).toggleClass('hl', !$('#row-'+cid).hasClass('hl'));
433 433 });
434 434 </script>
435 435 <div class="cs_files_title">
436 436 <span class="cs_files_expand">
437 437 <span id="expand_all_files">Expand All</span> | <span id="collapse_all_files">Collapse All</span>
438 438 </span>
439 439 <h2>
440 440 7 files changed: 55 inserted, 9 deleted
441 441 </h2>
442 442 </div>
443 443 <div class="cs_files">
444 444 <table class="compare_view_files">
445 445
446 446 <tr class="cs_A expand_file" fid="c--efbe5b7a3f13">
447 447 <td class="cs_icon_td">
448 448 <span class="expand_file_icon" fid="c--efbe5b7a3f13"></span>
449 449 </td>
450 450 <td class="cs_icon_td">
451 451 <div class="flag_status not_reviewed hidden"></div>
452 452 </td>
453 453 <td id="a_c--efbe5b7a3f13">
454 454 <a class="compare_view_filepath" href="#a_c--efbe5b7a3f13">
455 455 rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff
456 456 </a>
457 457 <span id="diff_c--efbe5b7a3f13" class="diff_links" style="display: none;">
458 458 <a href="/andersonsantos/rhodecode-momentum-fork/diff/rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
459 459 Unified Diff
460 460 </a>
461 461 |
462 462 <a href="/andersonsantos/rhodecode-momentum-fork/diff-2way/rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
463 463 Side-by-side Diff
464 464 </a>
465 465 </span>
466 466 </td>
467 467 <td>
468 468 <div class="changes pull-right"><div style="width:100px"><div class="added top-right-rounded-corner-mid bottom-right-rounded-corner-mid top-left-rounded-corner-mid bottom-left-rounded-corner-mid" style="width:100.0%">4</div><div class="deleted top-right-rounded-corner-mid bottom-right-rounded-corner-mid" style="width:0%"></div></div></div>
469 469 <div class="comment-bubble pull-right" data-path="rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff">
470 470 <i class="icon-comment"></i>
471 471 </div>
472 472 </td>
473 473 </tr>
474 474 <tr id="tr_c--efbe5b7a3f13">
475 475 <td></td>
476 476 <td></td>
477 477 <td class="injected_diff" colspan="2">
478 478
479 479 <div class="diff-container" id="diff-container-140716195039928">
480 480 <div id="c--efbe5b7a3f13_target" ></div>
481 481 <div id="c--efbe5b7a3f13" class="diffblock margined comm" >
482 482 <div class="code-body">
483 483 <div class="full_f_path" path="rhodecode/tests/fixtures/git_diff_rename_file_with_spaces.diff" style="display: none;"></div>
484 484 <table class="code-difftable">
485 485 <tr class="line context">
486 486 <td class="add-comment-line"><span class="add-comment-content"></span></td>
487 487 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o"></a></td>
488 488 <td class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n"></a></td>
489 489 <td class="code no-comment">
490 490 <pre>new file 100644</pre>
491 491 </td>
492 492 </tr>
493 493 <tr class="line add">
494 494 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
495 495 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o"></a></td>
496 496 <td id="rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n1" class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n1">1</a></td>
497 497 <td class="code">
498 498 <pre>diff --git a/file_with_ spaces.txt b/file_with_ two spaces.txt
499 499 </pre>
500 500 </td>
501 501 </tr>
502 502 <tr class="line add">
503 503 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
504 504 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o"></a></td>
505 505 <td id="rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n2" class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n2">2</a></td>
506 506 <td class="code">
507 507 <pre>similarity index 100%
508 508 </pre>
509 509 </td>
510 510 </tr>
511 511 <tr class="line add">
512 512 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
513 513 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o"></a></td>
514 514 <td id="rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n3" class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n3">3</a></td>
515 515 <td class="code">
516 516 <pre>rename from file_with_ spaces.txt
517 517 </pre>
518 518 </td>
519 519 </tr>
520 520 <tr class="line add">
521 521 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
522 522 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o"></a></td>
523 523 <td id="rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n4" class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n4">4</a></td>
524 524 <td class="code">
525 525 <pre>rename to file_with_ two spaces.txt
526 526 </pre>
527 527 </td>
528 528 </tr>
529 529 <tr class="line context">
530 530 <td class="add-comment-line"><span class="add-comment-content"></span></td>
531 531 <td class="lineno old"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_o...">...</a></td>
532 532 <td class="lineno new"><a href="#rhodecodetestsfixturesgit_diff_rename_file_with_spacesdiff_n...">...</a></td>
533 533 <td class="code no-comment">
534 534 <pre> No newline at end of file</pre>
535 535 </td>
536 536 </tr>
537 537 </table>
538 538 </div>
539 539 </div>
540 540 </div>
541 541
542 542 </td>
543 543 </tr>
544 544 <tr class="cs_A expand_file" fid="c--c21377f778f9">
545 545 <td class="cs_icon_td">
546 546 <span class="expand_file_icon" fid="c--c21377f778f9"></span>
547 547 </td>
548 548 <td class="cs_icon_td">
549 549 <div class="flag_status not_reviewed hidden"></div>
550 550 </td>
551 551 <td id="a_c--c21377f778f9">
552 552 <a class="compare_view_filepath" href="#a_c--c21377f778f9">
553 553 rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff
554 554 </a>
555 555 <span id="diff_c--c21377f778f9" class="diff_links" style="display: none;">
556 556 <a href="/andersonsantos/rhodecode-momentum-fork/diff/rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
557 557 Unified Diff
558 558 </a>
559 559 |
560 560 <a href="/andersonsantos/rhodecode-momentum-fork/diff-2way/rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
561 561 Side-by-side Diff
562 562 </a>
563 563 </span>
564 564 </td>
565 565 <td>
566 566 <div class="changes pull-right"><div style="width:100px"><div class="added top-right-rounded-corner-mid bottom-right-rounded-corner-mid top-left-rounded-corner-mid bottom-left-rounded-corner-mid" style="width:100.0%">3</div><div class="deleted top-right-rounded-corner-mid bottom-right-rounded-corner-mid" style="width:0%"></div></div></div>
567 567 <div class="comment-bubble pull-right" data-path="rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff">
568 568 <i class="icon-comment"></i>
569 569 </div>
570 570 </td>
571 571 </tr>
572 572 <tr id="tr_c--c21377f778f9">
573 573 <td></td>
574 574 <td></td>
575 575 <td class="injected_diff" colspan="2">
576 576
577 577 <div class="diff-container" id="diff-container-140716195038344">
578 578 <div id="c--c21377f778f9_target" ></div>
579 579 <div id="c--c21377f778f9" class="diffblock margined comm" >
580 580 <div class="code-body">
581 581 <div class="full_f_path" path="rhodecode/tests/fixtures/hg_diff_copy_file_with_spaces.diff" style="display: none;"></div>
582 582 <table class="code-difftable">
583 583 <tr class="line context">
584 584 <td class="add-comment-line"><span class="add-comment-content"></span></td>
585 585 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_o"></a></td>
586 586 <td class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n"></a></td>
587 587 <td class="code no-comment">
588 588 <pre>new file 100644</pre>
589 589 </td>
590 590 </tr>
591 591 <tr class="line add">
592 592 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
593 593 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_o"></a></td>
594 594 <td id="rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n1" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n1">1</a></td>
595 595 <td class="code">
596 596 <pre>diff --git a/file_changed_without_spaces.txt b/file_copied_ with spaces.txt
597 597 </pre>
598 598 </td>
599 599 </tr>
600 600 <tr class="line add">
601 601 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
602 602 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_o"></a></td>
603 603 <td id="rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n2" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n2">2</a></td>
604 604 <td class="code">
605 605 <pre>copy from file_changed_without_spaces.txt
606 606 </pre>
607 607 </td>
608 608 </tr>
609 609 <tr class="line add">
610 610 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
611 611 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_o"></a></td>
612 612 <td id="rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n3" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n3">3</a></td>
613 613 <td class="code">
614 614 <pre>copy to file_copied_ with spaces.txt
615 615 </pre>
616 616 </td>
617 617 </tr>
618 618 <tr class="line context">
619 619 <td class="add-comment-line"><span class="add-comment-content"></span></td>
620 620 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_o...">...</a></td>
621 621 <td class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_copy_file_with_spacesdiff_n...">...</a></td>
622 622 <td class="code no-comment">
623 623 <pre> No newline at end of file</pre>
624 624 </td>
625 625 </tr>
626 626 </table>
627 627 </div>
628 628 </div>
629 629 </div>
630 630
631 631 </td>
632 632 </tr>
633 633 <tr class="cs_A expand_file" fid="c--ee62085ad7a8">
634 634 <td class="cs_icon_td">
635 635 <span class="expand_file_icon" fid="c--ee62085ad7a8"></span>
636 636 </td>
637 637 <td class="cs_icon_td">
638 638 <div class="flag_status not_reviewed hidden"></div>
639 639 </td>
640 640 <td id="a_c--ee62085ad7a8">
641 641 <a class="compare_view_filepath" href="#a_c--ee62085ad7a8">
642 642 rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff
643 643 </a>
644 644 <span id="diff_c--ee62085ad7a8" class="diff_links" style="display: none;">
645 645 <a href="/andersonsantos/rhodecode-momentum-fork/diff/rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
646 646 Unified Diff
647 647 </a>
648 648 |
649 649 <a href="/andersonsantos/rhodecode-momentum-fork/diff-2way/rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff?diff2=46b3d50315f0f2b1f64485ac95af4f384948f9cb&amp;diff1=b78e2376b986b2cf656a2b4390b09f303291c886&amp;fulldiff=1&amp;diff=diff">
650 650 Side-by-side Diff
651 651 </a>
652 652 </span>
653 653 </td>
654 654 <td>
655 655 <div class="changes pull-right"><div style="width:100px"><div class="added top-right-rounded-corner-mid bottom-right-rounded-corner-mid top-left-rounded-corner-mid bottom-left-rounded-corner-mid" style="width:100.0%">3</div><div class="deleted top-right-rounded-corner-mid bottom-right-rounded-corner-mid" style="width:0%"></div></div></div>
656 656 <div class="comment-bubble pull-right" data-path="rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff">
657 657 <i class="icon-comment"></i>
658 658 </div>
659 659 </td>
660 660 </tr>
661 661 <tr id="tr_c--ee62085ad7a8">
662 662 <td></td>
663 663 <td></td>
664 664 <td class="injected_diff" colspan="2">
665 665
666 666 <div class="diff-container" id="diff-container-140716195039496">
667 667 <div id="c--ee62085ad7a8_target" ></div>
668 668 <div id="c--ee62085ad7a8" class="diffblock margined comm" >
669 669 <div class="code-body">
670 670 <div class="full_f_path" path="rhodecode/tests/fixtures/hg_diff_rename_file_with_spaces.diff" style="display: none;"></div>
671 671 <table class="code-difftable">
672 672 <tr class="line context">
673 673 <td class="add-comment-line"><span class="add-comment-content"></span></td>
674 674 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_o"></a></td>
675 675 <td class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n"></a></td>
676 676 <td class="code no-comment">
677 677 <pre>new file 100644</pre>
678 678 </td>
679 679 </tr>
680 680 <tr class="line add">
681 681 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
682 682 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_o"></a></td>
683 683 <td id="rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n1" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n1">1</a></td>
684 684 <td class="code">
685 685 <pre>diff --git a/file_ with update.txt b/file_changed _.txt
686 686 </pre>
687 687 </td>
688 688 </tr>
689 689 <tr class="line add">
690 690 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
691 691 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_o"></a></td>
692 692 <td id="rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n2" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n2">2</a></td>
693 693 <td class="code">
694 694 <pre>rename from file_ with update.txt
695 695 </pre>
696 696 </td>
697 697 </tr>
698 698 <tr class="line add">
699 699 <td class="add-comment-line"><span class="add-comment-content"><a href="#"><span class="icon-comment-add"></span></a></span></td>
700 700 <td class="lineno old"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_o"></a></td>
701 701 <td id="rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n3" class="lineno new"><a href="#rhodecodetestsfixtureshg_diff_rename_file_with_spacesdiff_n3">3</a></td>
702 702 <td class="code">
703 703 <pre>rename to file_changed _.txt</pre>
704 704 </td>
705 705 </tr>
706 706 </table>
707 707 </div>
708 708 </div>
709 709 </div>
710 710
711 711 </td>
712 712 </tr>
713 713
714 714 </table>
715 715 </div>
716 716 </div>
717 717 </div>
718 718
719 719 </td>
720 720 </tr>
721 721 </table>
722 722 </div>
723 723 </div>
724 724 </div>
725 725
726 726
727 727
728 728
729 729 <div id="comment-inline-form-template" style="display: none;">
730 730 <div class="comment-inline-form ac">
731 731 <div class="overlay"><div class="overlay-text">Submitting...</div></div>
732 732 <form action="#" class="inline-form" method="get">
733 733 <div id="edit-container_{1}" class="clearfix">
734 734 <div class="comment-title pull-left">
735 735 Commenting on line {1}.
736 736 </div>
737 737 <div class="comment-help pull-right">
738 738 Comments parsed using <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">RST</a> syntax with <span class="tooltip" title="Use @username inside this text to send notification to this RhodeCode user">@mention</span> support.
739 739 </div>
740 740 <div style="clear: both"></div>
741 741 <textarea id="text_{1}" name="text" class="comment-block-ta ac-input"></textarea>
742 742 </div>
743 743 <div id="preview-container_{1}" class="clearfix" style="display: none;">
744 744 <div class="comment-help">
745 745 Comment preview
746 746 </div>
747 747 <div id="preview-box_{1}" class="preview-box"></div>
748 748 </div>
749 749 <div class="comment-button pull-right">
750 750 <input type="hidden" name="f_path" value="{0}">
751 751 <input type="hidden" name="line" value="{1}">
752 752 <div id="preview-btn_{1}" class="btn btn-default">Preview</div>
753 753 <div id="edit-btn_{1}" class="btn" style="display: none;">Edit</div>
754 754 <input class="btn btn-success save-inline-form" id="save" name="save" type="submit" value="Comment" />
755 755 </div>
756 756 <div class="comment-button hide-inline-form-button">
757 757 <input class="btn hide-inline-form" id="hide-inline-form" name="hide-inline-form" type="reset" value="Cancel" />
758 758 </div>
759 759 </form>
760 760 </div>
761 761 </div>
762 762
763 763
764 764
765 765 <div class="comments">
766 766 <div id="inline-comments-container">
767 767
768 768 <h2>0 Pull Request Comments</h2>
769 769
770 770
771 771 </div>
772 772
773 773 </div>
774 774
775 775
776 776
777 777
778 778 <div class="pull-request-merge">
779 779 </div>
780 780 <div class="comments">
781 781 <div class="comment-form ac">
782 782 <form action="/rhodecode-momentum/pull-request-comment/720" id="comments_form" method="POST">
783 783 <div style="display: none;"><input id="csrf_token" name="csrf_token" type="hidden" value="6dbc0b19ac65237df65d57202a3e1f2df4153e38" /></div>
784 784 <div id="edit-container" class="clearfix">
785 785 <div class="comment-title pull-left">
786 786 Create a comment on this Pull Request.
787 787 </div>
788 788 <div class="comment-help pull-right">
789 789 Comments parsed using <a href="http://docutils.sourceforge.net/docs/user/rst/quickref.html">RST</a> syntax with <span class="tooltip" title="Use @username inside this text to send notification to this RhodeCode user">@mention</span> support.
790 790 </div>
791 791 <div style="clear: both"></div>
792 792 <textarea class="comment-block-ta" id="text" name="text"></textarea>
793 793 </div>
794 794
795 795 <div id="preview-container" class="clearfix" style="display: none;">
796 796 <div class="comment-title">
797 797 Comment preview
798 798 </div>
799 799 <div id="preview-box" class="preview-box"></div>
800 800 </div>
801 801
802 802 <div id="comment_form_extras">
803 803 </div>
804 804 <div class="action-button pull-right">
805 805 <div id="preview-btn" class="btn">
806 806 Preview
807 807 </div>
808 808 <div id="edit-btn" class="btn" style="display: none;">
809 809 Edit
810 810 </div>
811 811 <div class="comment-button">
812 812 <input class="btn btn-small btn-success comment-button-input" id="save" name="save" type="submit" value="Comment" />
813 813 </div>
814 814 </div>
815 815 </form>
816 816 </div>
817 817 </div>
818 818 <script>
819 819
820 820 $(document).ready(function() {
821 821
822 822 var cm = initCommentBoxCodeMirror('#text');
823 823
824 824 // main form preview
825 825 $('#preview-btn').on('click', function(e) {
826 826 $('#preview-btn').hide();
827 827 $('#edit-btn').show();
828 828 var _text = cm.getValue();
829 829 if (!_text) {
830 830 return;
831 831 }
832 832 var post_data = {
833 833 'text': _text,
834 834 'renderer': DEFAULT_RENDERER,
835 835 'csrf_token': CSRF_TOKEN
836 836 };
837 837 var previewbox = $('#preview-box');
838 838 previewbox.addClass('unloaded');
839 previewbox.html(_TM['Loading ...']);
839 previewbox.html(_gettext('Loading ...'));
840 840 $('#edit-container').hide();
841 841 $('#preview-container').show();
842 842
843 843 var url = pyroutes.url('changeset_comment_preview', {'repo_name': 'rhodecode-momentum'});
844 844
845 845 ajaxPOST(url, post_data, function(o) {
846 846 previewbox.html(o);
847 847 previewbox.removeClass('unloaded');
848 848 });
849 849 });
850 850 $('#edit-btn').on('click', function(e) {
851 851 $('#preview-btn').show();
852 852 $('#edit-btn').hide();
853 853 $('#edit-container').show();
854 854 $('#preview-container').hide();
855 855 });
856 856
857 857 var formatChangeStatus = function(state, escapeMarkup) {
858 858 var originalOption = state.element;
859 859 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
860 860 '<span>' + escapeMarkup(state.text) + '</span>';
861 861 };
862 862
863 863 var formatResult = function(result, container, query, escapeMarkup) {
864 864 return formatChangeStatus(result, escapeMarkup);
865 865 };
866 866
867 867 var formatSelection = function(data, container, escapeMarkup) {
868 868 return formatChangeStatus(data, escapeMarkup);
869 869 };
870 870
871 871 $('#change_status').select2({
872 872 placeholder: "Status Review",
873 873 formatResult: formatResult,
874 874 formatSelection: formatSelection,
875 875 containerCssClass: "drop-menu status_box_menu",
876 876 dropdownCssClass: "drop-menu-dropdown",
877 877 dropdownAutoWidth: true,
878 878 minimumResultsForSearch: -1
879 879 });
880 880 });
881 881 </script>
882 882
883 883
884 884 <script type="text/javascript">
885 885 // TODO: switch this to pyroutes
886 886 AJAX_COMMENT_DELETE_URL = "/rhodecode-momentum/pull-request-comment/__COMMENT_ID__/delete";
887 887
888 888 $(function(){
889 889 ReviewerAutoComplete('user');
890 890
891 891 $('#open_edit_reviewers').on('click', function(e){
892 892 $('#open_edit_reviewers').hide();
893 893 $('#close_edit_reviewers').show();
894 894 $('#add_reviewer_input').show();
895 895 $('.reviewer_member_remove').css('visibility', 'visible');
896 896 });
897 897
898 898 $('#close_edit_reviewers').on('click', function(e){
899 899 $('#open_edit_reviewers').show();
900 900 $('#close_edit_reviewers').hide();
901 901 $('#add_reviewer_input').hide();
902 902 $('.reviewer_member_remove').css('visibility', 'hidden');
903 903 });
904 904
905 905 $('.show-inline-comments').on('change', function(e){
906 906 var show = 'none';
907 907 var target = e.currentTarget;
908 908 if(target.checked){
909 909 show = ''
910 910 }
911 911 var boxid = $(target).attr('id_for');
912 912 var comments = $('#{0} .inline-comments'.format(boxid));
913 913 var fn_display = function(idx){
914 914 $(this).css('display', show);
915 915 };
916 916 $(comments).each(fn_display);
917 917 var btns = $('#{0} .inline-comments-button'.format(boxid));
918 918 $(btns).each(fn_display);
919 919 });
920 920
921 921 // inject comments into they proper positions
922 922 var file_comments = $('.inline-comment-placeholder');
923 923 renderInlineComments(file_comments);
924 924 var commentTotals = {};
925 925 $.each(file_comments, function(i, comment) {
926 926 var path = $(comment).attr('path');
927 927 var comms = $(comment).children().length;
928 928 if (path in commentTotals) {
929 929 commentTotals[path] += comms;
930 930 } else {
931 931 commentTotals[path] = comms;
932 932 }
933 933 });
934 934 $.each(commentTotals, function(path, total) {
935 935 var elem = $('.comment-bubble[data-path="'+ path +'"]')
936 936 elem.css('visibility', 'visible');
937 937 elem.html(elem.html() + ' ' + total );
938 938 });
939 939
940 940 $('#merge_pull_request_form').submit(function() {
941 941 if (!$('#merge_pull_request').attr('disabled')) {
942 942 $('#merge_pull_request').attr('disabled', 'disabled');
943 943 }
944 944 return true;
945 945 });
946 946
947 947 $('#update_pull_request').on('click', function(e){
948 948 updateReviewers(undefined, "rhodecode-momentum", "720");
949 949 });
950 950
951 951 $('#update_commits').on('click', function(e){
952 952 updateCommits("rhodecode-momentum", "720");
953 953 });
954 954
955 955 $('#close_pull_request').on('click', function(e){
956 956 closePullRequest("rhodecode-momentum", "720");
957 957 });
958 958 })
959 959 </script>
960 960
961 961 </div>
962 962 </div></div>
963 963
964 964 </div>
965 965
966 966
967 967 </%def>
@@ -1,336 +1,336 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title(*args)">
4 4 ${_('%s Files') % c.repo_name}
5 5 %if hasattr(c,'file'):
6 6 &middot; ${h.safe_unicode(c.file.path) or '\\'}
7 7 %endif
8 8
9 9 %if c.rhodecode_name:
10 10 &middot; ${h.branding(c.rhodecode_name)}
11 11 %endif
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Files')}
16 16 %if c.file:
17 17 @ ${h.show_id(c.commit)}
18 18 %endif
19 19 </%def>
20 20
21 21 <%def name="menu_bar_nav()">
22 22 ${self.menu_items(active='repositories')}
23 23 </%def>
24 24
25 25 <%def name="menu_bar_subnav()">
26 26 ${self.repo_menu(active='files')}
27 27 </%def>
28 28
29 29 <%def name="main()">
30 30 <div class="title">
31 31 ${self.repo_page_title(c.rhodecode_db_repo)}
32 32 </div>
33 33
34 34 <div id="pjax-container" class="summary">
35 35 <div id="files_data">
36 36 <%include file='files_pjax.html'/>
37 37 </div>
38 38 </div>
39 39 <script>
40 40 var curState = {
41 41 commit_id: "${c.commit.raw_id}"
42 42 };
43 43
44 44 var getState = function(context) {
45 45 var url = $(location).attr('href');
46 46 var _base_url = '${h.url("files_home",repo_name=c.repo_name,revision='',f_path='')}';
47 47 var _annotate_url = '${h.url("files_annotate_home",repo_name=c.repo_name,revision='',f_path='')}';
48 48 _base_url = _base_url.replace('//', '/');
49 49 _annotate_url = _annotate_url.replace('//', '/');
50 50
51 51 //extract f_path from url.
52 52 var parts = url.split(_base_url);
53 53 if (parts.length != 2) {
54 54 parts = url.split(_annotate_url);
55 55 if (parts.length != 2) {
56 56 var rev = "tip";
57 57 var f_path = "";
58 58 } else {
59 59 var parts2 = parts[1].split('/');
60 60 var rev = parts2.shift(); // pop the first element which is the revision
61 61 var f_path = parts2.join('/');
62 62 }
63 63
64 64 } else {
65 65 var parts2 = parts[1].split('/');
66 66 var rev = parts2.shift(); // pop the first element which is the revision
67 67 var f_path = parts2.join('/');
68 68 }
69 69
70 70 var _node_list_url = pyroutes.url('files_nodelist_home',
71 71 {repo_name: templateContext.repo_name,
72 72 revision: rev, f_path: f_path});
73 73 var _url_base = pyroutes.url('files_home',
74 74 {repo_name: templateContext.repo_name,
75 75 revision: rev, f_path:'__FPATH__'});
76 76 return {
77 77 url: url,
78 78 f_path: f_path,
79 79 rev: rev,
80 80 commit_id: curState.commit_id,
81 81 node_list_url: _node_list_url,
82 82 url_base: _url_base
83 83 };
84 84 };
85 85
86 86 var metadataRequest = null;
87 87 var getFilesMetadata = function() {
88 88 if (metadataRequest && metadataRequest.readyState != 4) {
89 89 metadataRequest.abort();
90 90 }
91 91 if (source_page) {
92 92 return false;
93 93 }
94 94 var state = getState('metadata');
95 95 var url_data = {
96 96 'repo_name': templateContext.repo_name,
97 97 'revision': state.commit_id,
98 98 'f_path': state.f_path
99 99 };
100 100
101 101 var url = pyroutes.url('files_metadata_list_home', url_data);
102 102
103 103 metadataRequest = $.ajax({url: url});
104 104
105 105 metadataRequest.done(function(data) {
106 106 var data = data.metadata;
107 107 var dataLength = data.length;
108 108 for (var i = 0; i < dataLength; i++) {
109 109 var rowData = data[i];
110 110 var name = rowData.name.replace('\\', '\\\\');
111 111
112 112 $('td[title="size-' + name + '"]').html(rowData.size);
113 113 var timeComponent = AgeModule.createTimeComponent(
114 114 rowData.modified_ts, rowData.modified_at);
115 115 $('td[title="modified_at-' + name + '"]').html(timeComponent);
116 116
117 117 $('td[title="revision-' + name + '"]').html(
118 118 '<div class="tooltip" title="{0}"><pre>r{1}:{2}</pre></div>'.format(
119 119 data[i].message, data[i].revision, data[i].short_id));
120 120 $('td[title="author-' + name + '"]').html(
121 121 '<span title="{0}">{1}</span>'.format(
122 122 data[i].author, data[i].user_profile));
123 123 }
124 124 tooltip_activate();
125 125 timeagoActivate();
126 126 });
127 127 metadataRequest.fail(function (data, textStatus, errorThrown) {
128 128 console.log(data);
129 129 if (data.status != 0) {
130 130 alert("Error while fetching metadata.\nError code {0} ({1}).Please consider reloading the page".format(data.status,data.statusText));
131 131 }
132 132 });
133 133 };
134 134
135 135 var callbacks = function() {
136 136 var state = getState('callbacks');
137 137 tooltip_activate();
138 138 timeagoActivate();
139 139
140 140 // used for history, and switch to
141 141 var initialCommitData = {
142 142 id: null,
143 143 text: "${_("Switch To Commit")}",
144 144 type: 'sha',
145 145 raw_id: null,
146 146 files_url: null
147 147 };
148 148
149 149 if ($('#trimmed_message_box').height() < 50) {
150 150 $('#message_expand').hide();
151 151 }
152 152
153 153 $('#message_expand').on('click', function(e) {
154 154 $('#trimmed_message_box').css('max-height', 'none');
155 155 $(this).hide();
156 156 });
157 157
158 158
159 159 if (source_page) {
160 160 // variants for with source code, not tree view
161 161
162 162 if (location.href.indexOf('#') != -1) {
163 163 page_highlights = location.href.substring(location.href.indexOf('#') + 1).split('L');
164 164 if (page_highlights.length == 2) {
165 165 highlight_ranges = page_highlights[1].split(",");
166 166
167 167 var h_lines = [];
168 168 for (pos in highlight_ranges) {
169 169 var _range = highlight_ranges[pos].split('-');
170 170 if (_range.length == 2) {
171 171 var start = parseInt(_range[0]);
172 172 var end = parseInt(_range[1]);
173 173 if (start < end) {
174 174 for (var i = start; i <= end; i++) {
175 175 h_lines.push(i);
176 176 }
177 177 }
178 178 }
179 179 else {
180 180 h_lines.push(parseInt(highlight_ranges[pos]));
181 181 }
182 182 }
183 183
184 184 for (pos in h_lines) {
185 185 // @comment-highlight-color
186 186 $('#L' + h_lines[pos]).css('background-color', '#ffd887');
187 187 }
188 188
189 189 var _first_line = $('#L' + h_lines[0]).get(0);
190 190 if (_first_line) {
191 191 var line = $('#L' + h_lines[0]);
192 192 offsetScroll(line, 70);
193 193 }
194 194 }
195 195 }
196 196
197 197 // select code link event
198 198 $("#hlcode").mouseup(getSelectionLink);
199 199
200 200 // file history select2
201 201 select2FileHistorySwitcher('#diff1', initialCommitData, state);
202 202 $('#diff1').on('change', function(e) {
203 203 $('#diff').removeClass('disabled').removeAttr("disabled");
204 204 $('#show_rev').removeClass('disabled').removeAttr("disabled");
205 205 });
206 206
207 207 // show more authors
208 208 $('#show_authors').on('click', function(e) {
209 209 e.preventDefault();
210 210 var url = pyroutes.url('files_authors_home',
211 211 {'repo_name': templateContext.repo_name,
212 212 'revision': state.rev, 'f_path': state.f_path});
213 213
214 214 $.pjax({
215 215 url: url,
216 216 data: 'annotate=${"1" if c.annotate else "0"}',
217 217 container: '#file_authors',
218 218 push: false,
219 219 timeout: pjaxTimeout
220 220 }).complete(function(){
221 221 $('#show_authors').hide();
222 222 })
223 223 });
224 224
225 225 // load file short history
226 226 $('#file_history_overview').on('click', function(e) {
227 227 e.preventDefault();
228 228 path = state.f_path;
229 229 if (path.indexOf("#") >= 0) {
230 230 path = path.slice(0, path.indexOf("#"));
231 231 }
232 232 var url = pyroutes.url('changelog_file_home',
233 233 {'repo_name': templateContext.repo_name,
234 234 'revision': state.rev, 'f_path': path, 'limit': 6});
235 235 $('#file_history_container').show();
236 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_TM['Loading ...']));
236 $('#file_history_container').html('<div class="file-history-inner">{0}</div>'.format(_gettext('Loading ...')));
237 237
238 238 $.pjax({
239 239 url: url,
240 240 container: '#file_history_container',
241 241 push: false,
242 242 timeout: pjaxTimeout
243 243 })
244 244 });
245 245
246 246 }
247 247 else {
248 248 getFilesMetadata();
249 249
250 250 // fuzzy file filter
251 251 fileBrowserListeners(state.node_list_url, state.url_base);
252 252
253 253 // switch to widget
254 254 select2RefSwitcher('#refs_filter', initialCommitData);
255 255 $('#refs_filter').on('change', function(e) {
256 256 var data = $('#refs_filter').select2('data');
257 257 curState.commit_id = data.raw_id;
258 258 $.pjax({url: data.files_url, container: '#pjax-container', timeout: pjaxTimeout});
259 259 });
260 260
261 261 $("#prev_commit_link").on('click', function(e) {
262 262 var data = $(this).data();
263 263 curState.commit_id = data.commitId;
264 264 });
265 265
266 266 $("#next_commit_link").on('click', function(e) {
267 267 var data = $(this).data();
268 268 curState.commit_id = data.commitId;
269 269 });
270 270
271 271 $('#at_rev').on("keypress", function(e) {
272 272 /* ENTER PRESSED */
273 273 if (e.keyCode === 13) {
274 274 var rev = $('#at_rev').val();
275 275 // explicit reload page here. with pjax entering bad input
276 276 // produces not so nice results
277 277 window.location = pyroutes.url('files_home',
278 278 {'repo_name': templateContext.repo_name,
279 279 'revision': rev, 'f_path': state.f_path});
280 280 }
281 281 });
282 282 }
283 283 };
284 284
285 285 var pjaxTimeout = 5000;
286 286
287 287 $(document).pjax(".pjax-link", "#pjax-container", {
288 288 "fragment": "#pjax-content",
289 289 "maxCacheLength": 1000,
290 290 "timeout": pjaxTimeout
291 291 });
292 292
293 293 // define global back/forward states
294 294 var isPjaxPopState = false;
295 295 $(document).on('pjax:popstate', function() {
296 296 isPjaxPopState = true;
297 297 });
298 298
299 299 $(document).on('pjax:end', function(xhr, options) {
300 300 if (isPjaxPopState) {
301 301 isPjaxPopState = false;
302 302 callbacks();
303 303 _NODEFILTER.resetFilter();
304 304 }
305 305
306 306 // run callback for tracking if defined for google analytics etc.
307 307 // this is used to trigger tracking on pjax
308 308 if (typeof window.rhodecode_statechange_callback !== 'undefined') {
309 309 var state = getState('statechange');
310 310 rhodecode_statechange_callback(state.url, null)
311 311 }
312 312 });
313 313
314 314 $(document).on('pjax:success', function(event, xhr, options) {
315 315 if (event.target.id == "file_history_container") {
316 316 $('#file_history_overview').hide();
317 317 $('#file_history_overview_full').show();
318 318 timeagoActivate();
319 319 tooltip_activate();
320 320 } else {
321 321 callbacks();
322 322 }
323 323 });
324 324
325 325 $(document).ready(function() {
326 326 callbacks();
327 327 var search_GET = "${request.GET.get('search','')}";
328 328 if (search_GET == "1") {
329 329 _NODEFILTER.initFilter();
330 330 }
331 331 });
332 332
333 333 </script>
334 334
335 335
336 336 </%def>
@@ -1,232 +1,232 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Files Add') % c.repo_name}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Add new file')} @ ${h.show_id(c.commit)}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_subnav()">
19 19 ${self.repo_menu(active='files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <div class="box">
24 24 <div class="title">
25 25 ${self.repo_page_title(c.rhodecode_db_repo)}
26 26 </div>
27 27 <div class="edit-file-title">
28 28 ${self.breadcrumbs()}
29 29 </div>
30 30 ${h.secure_form(h.url.current(),method='post',id='eform',enctype="multipart/form-data", class_="form-horizontal")}
31 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
34 34 ${_('Path')}:
35 35 </div>
36 36 <div class="right-content">
37 37 <div id="specify-custom-path-container">
38 38 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
39 39 <a class="custom-path-link" id="specify-custom-path" href="#">${_('Specify Custom Path')}</a>
40 40 </div>
41 41 <div id="remove-custom-path-container" style="display: none;">
42 42 ${c.repo_name}/
43 43 <input type="input-small" value="${c.f_path}" size="46" name="location" id="location">
44 44 <a class="custom-path-link" id="remove-custom-path" href="#">${_('Remove Custom Path')}</a>
45 45 </div>
46 46 </div>
47 47 </div>
48 48 <div id="filename_container" class="fieldset">
49 49 <div class="filename-label left-label">
50 50 ${_('Filename')}:
51 51 </div>
52 52 <div class="right-content">
53 53 <input class="input-small" type="text" value="" size="46" name="filename" id="filename">
54 54 <p>${_('or')} <a id="upload_file_enable" href="#">${_('Upload File')}</a></p>
55 55 </div>
56 56 </div>
57 57 <div id="upload_file_container" class="fieldset" style="display: none;">
58 58 <div class="filename-label left-label">
59 59 ${_('Upload file')}:
60 60 </div>
61 61 <div class="right-content file-upload-input">
62 62 <label for="upload_file" class="btn btn-default">Browse</label>
63 63 <span id="selected-file">${_('No file selected')}</span>
64 64 <input type="file" name="upload_file" id="upload_file">
65 65 <p>${_('or')} <a id="file_enable" href="#">${_('Create New File')}</a></p>
66 66 </div>
67 67 </div>
68 68 </div>
69 69 <div class="table">
70 70 <div id="files_data">
71 71 <div id="codeblock" class="codeblock">
72 72 <div class="code-header form" id="set_mode_header">
73 73 <div class="fields">
74 74 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
75 75 <label for="line_wrap">${_('line wraps')}</label>
76 76 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
77 77
78 78 <div id="render_preview" class="btn btn-small preview hidden" >${_('Preview')}</div>
79 79 </div>
80 80 </div>
81 81 <div id="editor_container">
82 82 <pre id="editor_pre"></pre>
83 83 <textarea id="editor" name="content" ></textarea>
84 84 <div id="editor_preview"></div>
85 85 </div>
86 86 </div>
87 87 </div>
88 88 </div>
89 89
90 90 <div class="edit-file-fieldset">
91 91 <div class="fieldset">
92 92 <div id="commit-message-label" class="commit-message-label left-label">
93 93 ${_('Commit Message')}:
94 94 </div>
95 95 <div class="right-content">
96 96 <div class="message">
97 97 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
98 98 </div>
99 99 </div>
100 100 </div>
101 101 <div class="pull-right">
102 102 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
103 103 ${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-success")}
104 104 </div>
105 105 </div>
106 106 ${h.end_form()}
107 107 </div>
108 108 <script type="text/javascript">
109 109
110 110 $('#commit_btn').on('click', function() {
111 111 var button = $(this);
112 112 if (button.hasClass('clicked')) {
113 113 button.attr('disabled', true);
114 114 } else {
115 115 button.addClass('clicked');
116 116 }
117 117 });
118 118
119 119 $('#specify-custom-path').on('click', function(e){
120 120 e.preventDefault();
121 121 $('#specify-custom-path-container').hide();
122 122 $('#remove-custom-path-container').show();
123 123 $('#destination-label').css('margin-top', '13px');
124 124 });
125 125
126 126 $('#remove-custom-path').on('click', function(e){
127 127 e.preventDefault();
128 128 $('#specify-custom-path-container').show();
129 129 $('#remove-custom-path-container').hide();
130 130 $('#location').val('${c.f_path}');
131 131 $('#destination-label').css('margin-top', '0');
132 132 });
133 133
134 134 var hide_upload = function(){
135 135 $('#files_data').show();
136 136 $('#upload_file_container').hide();
137 137 $('#filename_container').show();
138 138 };
139 139
140 140 $('#file_enable').on('click', function(e){
141 141 e.preventDefault();
142 142 hide_upload();
143 143 });
144 144
145 145 $('#upload_file_enable').on('click', function(e){
146 146 e.preventDefault();
147 147 $('#files_data').hide();
148 148 $('#upload_file_container').show();
149 149 $('#filename_container').hide();
150 150 if (detectIE() && detectIE() <= 9) {
151 151 $('#upload_file_container .file-upload-input label').hide();
152 152 $('#upload_file_container .file-upload-input span').hide();
153 153 $('#upload_file_container .file-upload-input input').show();
154 154 }
155 155 });
156 156
157 157 $('#upload_file').on('change', function() {
158 158 if (detectIE() && detectIE() <= 9) {
159 159 if (this.files && this.files[0]) {
160 160 $('#selected-file').html(this.files[0].name);
161 161 }
162 162 }
163 163 });
164 164
165 165 hide_upload();
166 166
167 167 var renderer = "";
168 168 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}";
169 169 var myCodeMirror = initCodeMirror('editor', reset_url, false);
170 170
171 171 var modes_select = $('#set_mode');
172 172 fillCodeMirrorOptions(modes_select);
173 173
174 174 var filename_selector = '#filename';
175 175 var callback = function(filename, mimetype, mode){
176 176 CodeMirrorPreviewEnable(mode);
177 177 };
178 178 // on change of select field set mode
179 179 setCodeMirrorModeFromSelect(
180 180 modes_select, filename_selector, myCodeMirror, callback);
181 181
182 182 // on entering the new filename set mode, from given extension
183 183 setCodeMirrorModeFromInput(
184 184 modes_select, filename_selector, myCodeMirror, callback);
185 185
186 186 // if the file is renderable set line wraps automatically
187 187 if (renderer !== ""){
188 188 var line_wrap = 'on';
189 189 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
190 190 setCodeMirrorLineWrap(myCodeMirror, true);
191 191 }
192 192
193 193 // on select line wraps change the editor
194 194 $('#line_wrap').on('change', function(e){
195 195 var selected = e.currentTarget;
196 196 var line_wraps = {'on': true, 'off': false}[selected.value];
197 197 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
198 198 });
199 199
200 200 // render preview/edit button
201 201 $('#render_preview').on('click', function(e){
202 202 if($(this).hasClass('preview')){
203 203 $(this).removeClass('preview');
204 204 $(this).html("${_('Edit')}");
205 205 $('#editor_preview').show();
206 206 $(myCodeMirror.getWrapperElement()).hide();
207 207
208 208 var possible_renderer = {
209 209 'rst':'rst',
210 210 'markdown':'markdown',
211 211 'gfm': 'markdown'}[myCodeMirror.getMode().name];
212 212 var _text = myCodeMirror.getValue();
213 213 var _renderer = possible_renderer || DEFAULT_RENDERER;
214 214 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
215 $('#editor_preview').html(_TM['Loading ...']);
215 $('#editor_preview').html(_gettext('Loading ...'));
216 216 var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
217 217
218 218 ajaxPOST(url, post_data, function(o){
219 219 $('#editor_preview').html(o);
220 220 })
221 221 }
222 222 else{
223 223 $(this).addClass('preview');
224 224 $(this).html("${_('Preview')}");
225 225 $('#editor_preview').hide();
226 226 $(myCodeMirror.getWrapperElement()).show();
227 227 }
228 228 });
229 229 $('#filename').focus();
230 230
231 231 </script>
232 232 </%def>
@@ -1,193 +1,193 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s File Edit') % c.repo_name}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="menu_bar_nav()">
11 11 ${self.menu_items(active='repositories')}
12 12 </%def>
13 13
14 14 <%def name="breadcrumbs_links()">
15 15 ${_('Edit file')} @ ${h.show_id(c.commit)}
16 16 </%def>
17 17
18 18 <%def name="menu_bar_subnav()">
19 19 ${self.repo_menu(active='files')}
20 20 </%def>
21 21
22 22 <%def name="main()">
23 23 <% renderer = h.renderer_from_filename(c.f_path)%>
24 24 <div class="box">
25 25 <div class="title">
26 26 ${self.repo_page_title(c.rhodecode_db_repo)}
27 27 </div>
28 28 <div class="edit-file-title">
29 29 ${self.breadcrumbs()}
30 30 </div>
31 31 <div class="edit-file-fieldset">
32 32 <div class="fieldset">
33 33 <div id="destination-label" class="left-label">
34 34 ${_('Path')}:
35 35 </div>
36 36 <div class="right-content">
37 37 <div id="specify-custom-path-container">
38 38 <span id="path-breadcrumbs">${h.files_breadcrumbs(c.repo_name,c.commit.raw_id,c.f_path)}</span>
39 39 </div>
40 40 </div>
41 41 </div>
42 42 </div>
43 43
44 44 <div class="table">
45 45 ${h.secure_form(h.url.current(),method='post',id='eform')}
46 46 <div id="codeblock" class="codeblock" >
47 47 <div class="code-header">
48 48 <div class="stats">
49 49 <i class="icon-file"></i>
50 50 <span class="item">${h.link_to("r%s:%s" % (c.file.commit.idx,h.short_id(c.file.commit.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.commit.raw_id))}</span>
51 51 <span class="item">${h.format_byte_size_binary(c.file.size)}</span>
52 52 <span class="item last">${c.file.mimetype}</span>
53 53 <div class="buttons">
54 54 <a class="btn btn-mini" href="${h.url('changelog_file_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path=c.f_path)}">
55 55 <i class="icon-time"></i> ${_('history')}
56 56 </a>
57 57
58 58 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
59 59 % if not c.file.is_binary:
60 60 %if True:
61 61 ${h.link_to(_('source'), h.url('files_home', repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
62 62 %else:
63 63 ${h.link_to(_('annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
64 64 %endif
65 65 ${h.link_to(_('raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path),class_="btn btn-mini")}
66 66 <a class="btn btn-mini" href="${h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.f_path)}">
67 67 <i class="icon-archive"></i> ${_('download')}
68 68 </a>
69 69 % endif
70 70 % endif
71 71 </div>
72 72 </div>
73 73 <div class="form">
74 74 <label for="set_mode">${_('Editing file')}:</label>
75 75 ${'%s /' % c.file.dir_path if c.file.dir_path else c.file.dir_path}
76 76 <input id="filename" type="text" name="filename" value="${c.file.name}">
77 77
78 78 ${h.dropdownmenu('set_mode','plain',[('plain',_('plain'))],enable_filter=True)}
79 79 <label for="line_wrap">${_('line wraps')}</label>
80 80 ${h.dropdownmenu('line_wrap', 'off', [('on', _('on')), ('off', _('off')),])}
81 81
82 82 <div id="render_preview" class="btn btn-small preview hidden">${_('Preview')}</div>
83 83 </div>
84 84 </div>
85 85 <div id="editor_container">
86 86 <pre id="editor_pre"></pre>
87 87 <textarea id="editor" name="content" >${h.escape(c.file.content)|n}</textarea>
88 88 <div id="editor_preview" ></div>
89 89 </div>
90 90 </div>
91 91 </div>
92 92
93 93 <div class="edit-file-fieldset">
94 94 <div class="fieldset">
95 95 <div id="commit-message-label" class="commit-message-label left-label">
96 96 ${_('Commit Message')}:
97 97 </div>
98 98 <div class="right-content">
99 99 <div class="message">
100 100 <textarea id="commit" name="message" placeholder="${c.default_message}"></textarea>
101 101 </div>
102 102 </div>
103 103 </div>
104 104 <div class="pull-right">
105 105 ${h.reset('reset',_('Cancel'),class_="btn btn-small")}
106 106 ${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-success")}
107 107 </div>
108 108 </div>
109 109 ${h.end_form()}
110 110 </div>
111 111
112 112 <script type="text/javascript">
113 113 $(document).ready(function(){
114 114 var renderer = "${renderer}";
115 115 var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id,f_path=c.file.path)}";
116 116 var myCodeMirror = initCodeMirror('editor', reset_url);
117 117
118 118 var modes_select = $('#set_mode');
119 119 fillCodeMirrorOptions(modes_select);
120 120
121 121 // try to detect the mode based on the file we edit
122 122 var mimetype = "${c.file.mimetype}";
123 123 var detected_mode = detectCodeMirrorMode(
124 124 "${c.file.name}", mimetype);
125 125
126 126 if(detected_mode){
127 127 setCodeMirrorMode(myCodeMirror, detected_mode);
128 128 $(modes_select).select2("val", mimetype);
129 129 $(modes_select).change();
130 130 setCodeMirrorMode(myCodeMirror, detected_mode);
131 131 }
132 132
133 133 var filename_selector = '#filename';
134 134 var callback = function(filename, mimetype, mode){
135 135 CodeMirrorPreviewEnable(mode);
136 136 };
137 137 // on change of select field set mode
138 138 setCodeMirrorModeFromSelect(
139 139 modes_select, filename_selector, myCodeMirror, callback);
140 140
141 141 // on entering the new filename set mode, from given extension
142 142 setCodeMirrorModeFromInput(
143 143 modes_select, filename_selector, myCodeMirror, callback);
144 144
145 145 // if the file is renderable set line wraps automatically
146 146 if (renderer !== ""){
147 147 var line_wrap = 'on';
148 148 $($('#line_wrap option[value="'+line_wrap+'"]')[0]).attr("selected", "selected");
149 149 setCodeMirrorLineWrap(myCodeMirror, true);
150 150 }
151 151 // on select line wraps change the editor
152 152 $('#line_wrap').on('change', function(e){
153 153 var selected = e.currentTarget;
154 154 var line_wraps = {'on': true, 'off': false}[selected.value];
155 155 setCodeMirrorLineWrap(myCodeMirror, line_wraps)
156 156 });
157 157
158 158 // render preview/edit button
159 159 if (mimetype === 'text/x-rst' || mimetype === 'text/plain') {
160 160 $('#render_preview').removeClass('hidden');
161 161 }
162 162 $('#render_preview').on('click', function(e){
163 163 if($(this).hasClass('preview')){
164 164 $(this).removeClass('preview');
165 165 $(this).html("${_('Edit')}");
166 166 $('#editor_preview').show();
167 167 $(myCodeMirror.getWrapperElement()).hide();
168 168
169 169 var possible_renderer = {
170 170 'rst':'rst',
171 171 'markdown':'markdown',
172 172 'gfm': 'markdown'}[myCodeMirror.getMode().name];
173 173 var _text = myCodeMirror.getValue();
174 174 var _renderer = possible_renderer || DEFAULT_RENDERER;
175 175 var post_data = {'text': _text, 'renderer': _renderer, 'csrf_token': CSRF_TOKEN};
176 $('#editor_preview').html(_TM['Loading ...']);
176 $('#editor_preview').html(_gettext('Loading ...'));
177 177 var url = pyroutes.url('changeset_comment_preview', {'repo_name': '${c.repo_name}'});
178 178
179 179 ajaxPOST(url, post_data, function(o){
180 180 $('#editor_preview').html(o);
181 181 })
182 182 }
183 183 else{
184 184 $(this).addClass('preview');
185 185 $(this).html("${_('Preview')}");
186 186 $('#editor_preview').hide();
187 187 $(myCodeMirror.getWrapperElement()).show();
188 188 }
189 189 });
190 190
191 191 })
192 192 </script>
193 193 </%def>
@@ -1,568 +1,568 b''
1 1 <%inherit file="/base/base.html"/>
2 2
3 3 <%def name="title()">
4 4 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()">
11 11 <span id="pr-title">
12 12 ${c.pull_request.title}
13 13 %if c.pull_request.is_closed():
14 14 (${_('Closed')})
15 15 %endif
16 16 </span>
17 17 <div id="pr-title-edit" class="input" style="display: none;">
18 18 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
19 19 </div>
20 20 </%def>
21 21
22 22 <%def name="menu_bar_nav()">
23 23 ${self.menu_items(active='repositories')}
24 24 </%def>
25 25
26 26 <%def name="menu_bar_subnav()">
27 27 ${self.repo_menu(active='showpullrequest')}
28 28 </%def>
29 29
30 30 <%def name="main()">
31 31 <script type="text/javascript">
32 32 // TODO: marcink switch this to pyroutes
33 33 AJAX_COMMENT_DELETE_URL = "${url('pullrequest_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
34 34 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
35 35 </script>
36 36 <div class="box">
37 37 <div class="title">
38 38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 39 </div>
40 40
41 41 ${self.breadcrumbs()}
42 42
43 43
44 44 <div class="box pr-summary">
45 45 <div class="summary-details block-left">
46 46 <%summary = lambda n:{False:'summary-short'}.get(n)%>
47 47 <div class="pr-details-title">
48 48 ${_('Pull request #%s') % c.pull_request.pull_request_id} ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 49 %if c.allowed_to_update:
50 50 <span id="open_edit_pullrequest" class="block-right action_button">${_('Edit')}</span>
51 51 <span id="close_edit_pullrequest" class="block-right action_button" style="display: none;">${_('Close')}</span>
52 52 %endif
53 53 </div>
54 54
55 55 <div id="summary" class="fields pr-details-content">
56 56 <div class="field">
57 57 <div class="label-summary">
58 58 <label>${_('Origin')}:</label>
59 59 </div>
60 60 <div class="input">
61 61 <div class="pr-origininfo">
62 62 ## branch link is only valid if it is a branch
63 63 <span class="tag">
64 64 %if c.pull_request.source_ref_parts.type == 'branch':
65 65 <a href="${h.url('changelog_home', repo_name=c.pull_request.source_repo.repo_name, branch=c.pull_request.source_ref_parts.name)}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
66 66 %else:
67 67 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
68 68 %endif
69 69 </span>
70 70 <span class="clone-url">
71 71 <a href="${h.url('summary_home', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
72 72 </span>
73 73 </div>
74 74 <div class="pr-pullinfo">
75 75 %if h.is_hg(c.pull_request.source_repo):
76 76 <input type="text" value="hg pull -r ${h.short_id(c.source_ref)} ${c.pull_request.source_repo.clone_url()}" readonly="readonly">
77 77 %elif h.is_git(c.pull_request.source_repo):
78 78 <input type="text" value="git pull ${c.pull_request.source_repo.clone_url()} ${c.pull_request.source_ref_parts.name}" readonly="readonly">
79 79 %endif
80 80 </div>
81 81 </div>
82 82 </div>
83 83 <div class="field">
84 84 <div class="label-summary">
85 85 <label>${_('Target')}:</label>
86 86 </div>
87 87 <div class="input">
88 88 <div class="pr-targetinfo">
89 89 ## branch link is only valid if it is a branch
90 90 <span class="tag">
91 91 %if c.pull_request.target_ref_parts.type == 'branch':
92 92 <a href="${h.url('changelog_home', repo_name=c.pull_request.target_repo.repo_name, branch=c.pull_request.target_ref_parts.name)}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
93 93 %else:
94 94 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
95 95 %endif
96 96 </span>
97 97 <span class="clone-url">
98 98 <a href="${h.url('summary_home', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
99 99 </span>
100 100 </div>
101 101 </div>
102 102 </div>
103 103 <div class="field">
104 104 <div class="label-summary">
105 105 <label>${_('Review')}:</label>
106 106 </div>
107 107 <div class="input">
108 108 %if c.pull_request_review_status:
109 109 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
110 110 <span class="changeset-status-lbl tooltip">
111 111 %if c.pull_request.is_closed():
112 112 ${_('Closed')},
113 113 %endif
114 114 ${h.commit_status_lbl(c.pull_request_review_status)}
115 115 </span>
116 116 - ${ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
117 117 %endif
118 118 </div>
119 119 </div>
120 120 <div class="field">
121 121 <div class="pr-description-label label-summary">
122 122 <label>${_('Description')}:</label>
123 123 </div>
124 124 <div id="pr-desc" class="input">
125 125 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
126 126 </div>
127 127 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
128 128 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
129 129 </div>
130 130 </div>
131 131 <div class="field">
132 132 <div class="label-summary">
133 133 <label>${_('Comments')}:</label>
134 134 </div>
135 135 <div class="input">
136 136 <div>
137 137 <div class="comments-number">
138 138 %if c.comments:
139 139 <a href="#comments">${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}</a>,
140 140 %else:
141 141 ${ungettext("%d Pull request comment", "%d Pull request comments", len(c.comments)) % len(c.comments)}
142 142 %endif
143 143 %if c.inline_cnt:
144 144 ## this is replaced with a proper link to first comment via JS linkifyComments() func
145 145 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
146 146 %else:
147 147 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
148 148 %endif
149 149
150 150 % if c.outdated_cnt:
151 151 ,${ungettext("%d Outdated Comment", "%d Outdated Comments", c.outdated_cnt) % c.outdated_cnt} <span id="show-outdated-comments" class="btn btn-link">${_('(Show)')}</span>
152 152 % endif
153 153 </div>
154 154 </div>
155 155 </div>
156 156 </div>
157 157 <div id="pr-save" class="field" style="display: none;">
158 158 <div class="label-summary"></div>
159 159 <div class="input">
160 160 <span id="edit_pull_request" class="btn btn-small">${_('Save Changes')}</span>
161 161 </div>
162 162 </div>
163 163 </div>
164 164 </div>
165 165 <div>
166 166 ## AUTHOR
167 167 <div class="reviewers-title block-right">
168 168 <div class="pr-details-title">
169 169 ${_('Author')}
170 170 </div>
171 171 </div>
172 172 <div class="block-right pr-details-content reviewers">
173 173 <ul class="group_members">
174 174 <li>
175 175 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
176 176 </li>
177 177 </ul>
178 178 </div>
179 179 ## REVIEWERS
180 180 <div class="reviewers-title block-right">
181 181 <div class="pr-details-title">
182 182 ${_('Pull request reviewers')}
183 183 %if c.allowed_to_update:
184 184 <span id="open_edit_reviewers" class="block-right action_button">${_('Edit')}</span>
185 185 <span id="close_edit_reviewers" class="block-right action_button" style="display: none;">${_('Close')}</span>
186 186 %endif
187 187 </div>
188 188 </div>
189 189 <div id="reviewers" class="block-right pr-details-content reviewers">
190 190 ## members goes here !
191 191 <ul id="review_members" class="group_members">
192 192 %for member,status in c.pull_request_reviewers:
193 193 <li id="reviewer_${member.user_id}">
194 194 <div class="reviewers_member">
195 195 <div class="reviewer_status tooltip" title="${h.tooltip(h.commit_status_lbl(status[0][1].status if status else 'not_reviewed'))}">
196 196 <div class="${'flag_status %s' % (status[0][1].status if status else 'not_reviewed')} pull-left reviewer_member_status"></div>
197 197 </div>
198 198 <div id="reviewer_${member.user_id}_name" class="reviewer_name">
199 199 ${self.gravatar_with_user(member.email, 16)} <div class="reviewer">(${_('owner') if c.pull_request.user_id == member.user_id else _('reviewer')})</div>
200 200 </div>
201 201 <input id="reviewer_${member.user_id}_input" type="hidden" value="${member.user_id}" name="review_members" />
202 202 %if c.allowed_to_update:
203 203 <div class="reviewer_member_remove action_button" onclick="removeReviewMember(${member.user_id}, true)" style="visibility: hidden;">
204 204 <i class="icon-remove-sign" ></i>
205 205 </div>
206 206 %endif
207 207 </div>
208 208 </li>
209 209 %endfor
210 210 </ul>
211 211 %if not c.pull_request.is_closed():
212 212 <div id="add_reviewer_input" class='ac' style="display: none;">
213 213 %if c.allowed_to_update:
214 214 <div class="reviewer_ac">
215 215 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
216 216 <div id="reviewers_container"></div>
217 217 </div>
218 218 <div>
219 219 <span id="update_pull_request" class="btn btn-small">${_('Save Changes')}</span>
220 220 </div>
221 221 %endif
222 222 </div>
223 223 %endif
224 224 </div>
225 225 </div>
226 226 </div>
227 227 <div class="box">
228 228 ##DIFF
229 229 <div class="table" >
230 230 <div id="changeset_compare_view_content">
231 231 ##CS
232 232 % if c.missing_requirements:
233 233 <div class="box">
234 234 <div class="alert alert-warning">
235 235 <div>
236 236 <strong>${_('Missing requirements:')}</strong>
237 237 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
238 238 </div>
239 239 </div>
240 240 </div>
241 241 % elif c.missing_commits:
242 242 <div class="box">
243 243 <div class="alert alert-warning">
244 244 <div>
245 245 <strong>${_('Missing commits')}:</strong>
246 246 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
247 247 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
248 248 </div>
249 249 </div>
250 250 </div>
251 251 % endif
252 252 <div class="compare_view_commits_title">
253 253 % if c.allowed_to_update and not c.pull_request.is_closed():
254 254 <button id="update_commits" class="btn btn-small">${_('Update commits')}</button>
255 255 % endif
256 256 % if len(c.commit_ranges):
257 257 <h2>${ungettext('Compare View: %s commit','Compare View: %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}</h2>
258 258 % endif
259 259 </div>
260 260 % if not c.missing_commits:
261 261 <%include file="/compare/compare_commits.html" />
262 262 ## FILES
263 263 <div class="cs_files_title">
264 264 <span class="cs_files_expand">
265 265 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
266 266 </span>
267 267 <h2>
268 268 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
269 269 </h2>
270 270 </div>
271 271 % endif
272 272 <div class="cs_files">
273 273 %if not c.files and not c.missing_commits:
274 274 <span class="empty_data">${_('No files')}</span>
275 275 %endif
276 276 <table class="compare_view_files">
277 277 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
278 278 %for FID, change, path, stats in c.files:
279 279 <tr class="cs_${change} collapse_file" fid="${FID}">
280 280 <td class="cs_icon_td">
281 281 <span class="collapse_file_icon" fid="${FID}"></span>
282 282 </td>
283 283 <td class="cs_icon_td">
284 284 <div class="flag_status not_reviewed hidden"></div>
285 285 </td>
286 286 <td class="cs_${change}" id="a_${FID}">
287 287 <div class="node">
288 288 <a href="#a_${FID}">
289 289 <i class="icon-file-${change.lower()}"></i>
290 290 ${h.safe_unicode(path)}
291 291 </a>
292 292 </div>
293 293 </td>
294 294 <td>
295 295 <div class="changes pull-right">${h.fancy_file_stats(stats)}</div>
296 296 <div class="comment-bubble pull-right" data-path="${path}">
297 297 <i class="icon-comment"></i>
298 298 </div>
299 299 </td>
300 300 </tr>
301 301 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
302 302 <td></td>
303 303 <td></td>
304 304 <td class="cs_${change}">
305 305 %if c.target_repo.repo_name == c.repo_name:
306 306 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), c.target_ref, c.source_ref, change)}
307 307 %else:
308 308 ## this is slightly different case later, since the other repo can have this
309 309 ## file in other state than the origin repo
310 310 ${diff_block.diff_menu(c.target_repo.repo_name, h.safe_unicode(path), c.target_ref, c.source_ref, change)}
311 311 %endif
312 312 </td>
313 313 <td class="td-actions rc-form">
314 314 </td>
315 315 </tr>
316 316 <tr id="tr_${FID}">
317 317 <td></td>
318 318 <td></td>
319 319 <td class="injected_diff" colspan="2">
320 320 ${diff_block.diff_block_simple([c.changes[FID]])}
321 321 </td>
322 322 </tr>
323 323
324 324 ## Loop through inline comments
325 325 % if c.outdated_comments.get(path,False):
326 326 <tr class="outdated">
327 327 <td></td>
328 328 <td></td>
329 329 <td colspan="2">
330 330 <p>${_('Outdated Inline Comments')}:</p>
331 331 </td>
332 332 </tr>
333 333 <tr class="outdated">
334 334 <td></td>
335 335 <td></td>
336 336 <td colspan="2" class="outdated_comment_block">
337 337 % for line, comments in c.outdated_comments[path].iteritems():
338 338 <div class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
339 339 % for co in comments:
340 340 ${comment.comment_block_outdated(co)}
341 341 % endfor
342 342 </div>
343 343 % endfor
344 344 </td>
345 345 </tr>
346 346 % endif
347 347 %endfor
348 348 ## Loop through inline comments for deleted files
349 349 %for path in c.deleted_files:
350 350 <tr class="outdated deleted">
351 351 <td></td>
352 352 <td></td>
353 353 <td>${path}</td>
354 354 </tr>
355 355 <tr class="outdated deleted">
356 356 <td></td>
357 357 <td></td>
358 358 <td>(${_('Removed')})</td>
359 359 </tr>
360 360 % if path in c.outdated_comments:
361 361 <tr class="outdated deleted">
362 362 <td></td>
363 363 <td></td>
364 364 <td colspan="2">
365 365 <p>${_('Outdated Inline Comments')}:</p>
366 366 </td>
367 367 </tr>
368 368 <tr class="outdated">
369 369 <td></td>
370 370 <td></td>
371 371 <td colspan="2" class="outdated_comment_block">
372 372 % for line, comments in c.outdated_comments[path].iteritems():
373 373 <div class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
374 374 % for co in comments:
375 375 ${comment.comment_block_outdated(co)}
376 376 % endfor
377 377 </div>
378 378 % endfor
379 379 </td>
380 380 </tr>
381 381 % endif
382 382 %endfor
383 383 </table>
384 384 </div>
385 385 % if c.limited_diff:
386 386 <h5>${_('Commit was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></h5>
387 387 % endif
388 388 </div>
389 389 </div>
390 390
391 391 % if c.limited_diff:
392 392 <p>${_('Commit was too big and was cut off...')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a huge diff might take some time and resources")}')">${_('Show full diff')}</a></p>
393 393 % endif
394 394
395 395 ## template for inline comment form
396 396 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
397 397 ${comment.comment_inline_form()}
398 398
399 399 ## render comments and inlines
400 400 ${comment.generate_comments(include_pull_request=True, is_pull_request=True)}
401 401
402 402 % if not c.pull_request.is_closed():
403 403 ## main comment form and it status
404 404 ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name,
405 405 pull_request_id=c.pull_request.pull_request_id),
406 406 c.pull_request_review_status,
407 407 is_pull_request=True, change_status=c.allowed_to_change_status)}
408 408 %endif
409 409
410 410 <script type="text/javascript">
411 411 if (location.href.indexOf('#') != -1) {
412 412 var id = '#'+location.href.substring(location.href.indexOf('#') + 1).split('#');
413 413 var line = $('html').find(id);
414 414 offsetScroll(line, 70);
415 415 }
416 416 $(function(){
417 417 ReviewerAutoComplete('user');
418 418 // custom code mirror
419 419 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
420 420
421 421 var PRDetails = {
422 422 editButton: $('#open_edit_pullrequest'),
423 423 closeButton: $('#close_edit_pullrequest'),
424 424 viewFields: $('#pr-desc, #pr-title'),
425 425 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
426 426
427 427 init: function() {
428 428 var that = this;
429 429 this.editButton.on('click', function(e) { that.edit(); });
430 430 this.closeButton.on('click', function(e) { that.view(); });
431 431 },
432 432
433 433 edit: function(event) {
434 434 this.viewFields.hide();
435 435 this.editButton.hide();
436 436 this.editFields.show();
437 437 codeMirrorInstance.refresh();
438 438 },
439 439
440 440 view: function(event) {
441 441 this.editFields.hide();
442 442 this.closeButton.hide();
443 443 this.viewFields.show();
444 444 }
445 445 };
446 446
447 447 var ReviewersPanel = {
448 448 editButton: $('#open_edit_reviewers'),
449 449 closeButton: $('#close_edit_reviewers'),
450 450 addButton: $('#add_reviewer_input'),
451 451 removeButtons: $('.reviewer_member_remove'),
452 452
453 453 init: function() {
454 454 var that = this;
455 455 this.editButton.on('click', function(e) { that.edit(); });
456 456 this.closeButton.on('click', function(e) { that.close(); });
457 457 },
458 458
459 459 edit: function(event) {
460 460 this.editButton.hide();
461 461 this.closeButton.show();
462 462 this.addButton.show();
463 463 this.removeButtons.css('visibility', 'visible');
464 464 },
465 465
466 466 close: function(event) {
467 467 this.editButton.show();
468 468 this.closeButton.hide();
469 469 this.addButton.hide();
470 470 this.removeButtons.css('visibility', 'hidden');
471 471 }
472 472 };
473 473
474 474 PRDetails.init();
475 475 ReviewersPanel.init();
476 476
477 477 $('#show-outdated-comments').on('click', function(e){
478 478 var button = $(this);
479 479 var outdated = $('.outdated');
480 480 if (button.html() === "(Show)") {
481 481 button.html("(Hide)");
482 482 outdated.show();
483 483 } else {
484 484 button.html("(Show)");
485 485 outdated.hide();
486 486 }
487 487 });
488 488
489 489 $('.show-inline-comments').on('change', function(e){
490 490 var show = 'none';
491 491 var target = e.currentTarget;
492 492 if(target.checked){
493 493 show = ''
494 494 }
495 495 var boxid = $(target).attr('id_for');
496 496 var comments = $('#{0} .inline-comments'.format(boxid));
497 497 var fn_display = function(idx){
498 498 $(this).css('display', show);
499 499 };
500 500 $(comments).each(fn_display);
501 501 var btns = $('#{0} .inline-comments-button'.format(boxid));
502 502 $(btns).each(fn_display);
503 503 });
504 504
505 505 // inject comments into their proper positions
506 506 var file_comments = $('.inline-comment-placeholder');
507 507 %if c.pull_request.is_closed():
508 508 renderInlineComments(file_comments, false);
509 509 %else:
510 510 renderInlineComments(file_comments, true);
511 511 %endif
512 512 var commentTotals = {};
513 513 $.each(file_comments, function(i, comment) {
514 514 var path = $(comment).attr('path');
515 515 var comms = $(comment).children().length;
516 516 if (path in commentTotals) {
517 517 commentTotals[path] += comms;
518 518 } else {
519 519 commentTotals[path] = comms;
520 520 }
521 521 });
522 522 $.each(commentTotals, function(path, total) {
523 523 var elem = $('.comment-bubble[data-path="'+ path +'"]');
524 524 elem.css('visibility', 'visible');
525 525 elem.html(elem.html() + ' ' + total );
526 526 });
527 527
528 528 $('#merge_pull_request_form').submit(function() {
529 529 if (!$('#merge_pull_request').attr('disabled')) {
530 530 $('#merge_pull_request').attr('disabled', 'disabled');
531 531 }
532 532 return true;
533 533 });
534 534
535 535 $('#edit_pull_request').on('click', function(e){
536 536 var title = $('#pr-title-input').val();
537 537 var description = codeMirrorInstance.getValue();
538 538 editPullRequest(
539 539 "${c.repo_name}", "${c.pull_request.pull_request_id}",
540 540 title, description);
541 541 });
542 542
543 543 $('#update_pull_request').on('click', function(e){
544 544 updateReviewers(undefined, "${c.repo_name}", "${c.pull_request.pull_request_id}");
545 545 });
546 546
547 547 $('#update_commits').on('click', function(e){
548 548 var isDisabled = !$(e.currentTarget).attr('disabled');
549 $(e.currentTarget).text(_TM['Updating...']);
549 $(e.currentTarget).text(_gettext('Updating...'));
550 550 $(e.currentTarget).attr('disabled', 'disabled');
551 551 if(isDisabled){
552 552 updateCommits("${c.repo_name}", "${c.pull_request.pull_request_id}");
553 553 }
554 554
555 555 });
556 556 // fixing issue with caches on firefox
557 557 $('#update_commits').removeAttr("disabled");
558 558
559 559 $('#close_pull_request').on('click', function(e){
560 560 closePullRequest("${c.repo_name}", "${c.pull_request.pull_request_id}");
561 561 });
562 562 })
563 563 </script>
564 564
565 565 </div>
566 566 </div>
567 567
568 568 </%def>
@@ -1,39 +1,42 b''
1 1 [aliases]
2 2 test = pytest
3 3
4 4 [egg_info]
5 5 tag_build =
6 6 tag_svn_revision = false
7 7
8 8 # Babel configuration
9 9 [compile_catalog]
10 10 domain = rhodecode
11 11 directory = rhodecode/i18n
12 12 statistics = true
13 13
14 14 [extract_messages]
15 15 add_comments = TRANSLATORS:
16 16 output_file = rhodecode/i18n/rhodecode.pot
17 17 msgid-bugs-address = marcin@rhodecode.com
18 18 copyright-holder = RhodeCode GmbH
19 19 no-wrap = true
20 keywords = lazy_ugettext
20 keywords =
21 lazy_ugettext
22 _ngettext
23 _gettext
21 24
22 25 [init_catalog]
23 26 domain = rhodecode
24 27 input_file = rhodecode/i18n/rhodecode.pot
25 28 output_dir = rhodecode/i18n
26 29
27 30 [update_catalog]
28 31 domain = rhodecode
29 32 input_file = rhodecode/i18n/rhodecode.pot
30 33 output_dir = rhodecode/i18n
31 34 previous = true
32 35
33 36 [build_sphinx]
34 37 source-dir = docs/
35 38 build-dir = docs/_build
36 39 all_files = 1
37 40
38 41 [upload_sphinx]
39 42 upload-dir = docs/_build/html
@@ -1,248 +1,249 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Import early to make sure things are patched up properly
4 4 from setuptools import setup, find_packages
5 5
6 6 import os
7 7 import sys
8 8 import platform
9 9
10 10 if sys.version_info < (2, 7):
11 11 raise Exception('RhodeCode requires Python 2.7 or later')
12 12
13 13
14 14 here = os.path.abspath(os.path.dirname(__file__))
15 15
16 16
17 17 def _get_meta_var(name, data, callback_handler=None):
18 18 import re
19 19 matches = re.compile(r'(?:%s)\s*=\s*(.*)' % name).search(data)
20 20 if matches:
21 21 if not callable(callback_handler):
22 22 callback_handler = lambda v: v
23 23
24 24 return callback_handler(eval(matches.groups()[0]))
25 25
26 26 _meta = open(os.path.join(here, 'rhodecode', '__init__.py'), 'rb')
27 27 _metadata = _meta.read()
28 28 _meta.close()
29 29
30 30 callback = lambda V: ('.'.join(map(str, V[:3])) + '.'.join(V[3:]))
31 31 __version__ = open(os.path.join('rhodecode', 'VERSION')).read().strip()
32 32 __license__ = _get_meta_var('__license__', _metadata)
33 33 __author__ = _get_meta_var('__author__', _metadata)
34 34 __url__ = _get_meta_var('__url__', _metadata)
35 35 # defines current platform
36 36 __platform__ = platform.system()
37 37
38 38 # Cygwin has different platform identifiers, but they all contain the
39 39 # term "CYGWIN"
40 40 is_windows = __platform__ == 'Windows' or 'CYGWIN' in __platform__
41 41
42 42 requirements = [
43 43 'Babel',
44 44 'Beaker',
45 45 'FormEncode',
46 46 'Mako',
47 47 'Markdown',
48 48 'MarkupSafe',
49 49 'MySQL-python',
50 50 'Paste',
51 51 'PasteDeploy',
52 52 'PasteScript',
53 53 'Pygments',
54 54 'Pylons',
55 55 'Pyro4',
56 56 'Routes',
57 57 'SQLAlchemy',
58 58 'Tempita',
59 59 'URLObject',
60 60 'WebError',
61 61 'WebHelpers',
62 62 'WebHelpers2',
63 63 'WebOb',
64 64 'WebTest',
65 65 'Whoosh',
66 66 'alembic',
67 67 'amqplib',
68 68 'anyjson',
69 69 'appenlight-client',
70 70 'authomatic',
71 71 'backport_ipaddress',
72 72 'celery',
73 73 'colander',
74 74 'decorator',
75 75 'docutils',
76 76 'gunicorn',
77 77 'infrae.cache',
78 78 'ipython',
79 79 'iso8601',
80 80 'kombu',
81 81 'msgpack-python',
82 82 'packaging',
83 83 'psycopg2',
84 84 'py-gfm',
85 85 'pycrypto',
86 86 'pycurl',
87 87 'pyparsing',
88 88 'pyramid',
89 89 'pyramid-debugtoolbar',
90 90 'pyramid-mako',
91 91 'pyramid-beaker',
92 92 'pysqlite',
93 93 'python-dateutil',
94 94 'python-ldap',
95 95 'python-memcached',
96 96 'python-pam',
97 97 'recaptcha-client',
98 98 'repoze.lru',
99 99 'requests',
100 100 'simplejson',
101 101 'waitress',
102 102 'zope.cachedescriptors',
103 103 'dogpile.cache',
104 104 'dogpile.core'
105 105 ]
106 106
107 107 if is_windows:
108 108 pass
109 109 else:
110 110 requirements.append('psutil')
111 111 requirements.append('py-bcrypt')
112 112
113 113 test_requirements = [
114 114 'WebTest',
115 115 'configobj',
116 116 'cssselect',
117 117 'flake8',
118 118 'lxml',
119 119 'mock',
120 120 'pytest',
121 121 'pytest-cov',
122 122 'pytest-runner',
123 123 ]
124 124
125 125 setup_requirements = [
126 126 'PasteScript',
127 127 'pytest-runner',
128 128 ]
129 129
130 130 dependency_links = [
131 131 ]
132 132
133 133 classifiers = [
134 134 'Development Status :: 6 - Mature',
135 135 'Environment :: Web Environment',
136 136 'Framework :: Pylons',
137 137 'Intended Audience :: Developers',
138 138 'Operating System :: OS Independent',
139 139 'Programming Language :: Python',
140 140 'Programming Language :: Python :: 2.7',
141 141 ]
142 142
143 143
144 144 # additional files from project that goes somewhere in the filesystem
145 145 # relative to sys.prefix
146 146 data_files = []
147 147
148 148 # additional files that goes into package itself
149 149 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
150 150
151 151 description = ('RhodeCode is a fast and powerful management tool '
152 152 'for Mercurial and GIT with a built in push/pull server, '
153 153 'full text search and code-review.')
154 154
155 155 keywords = ' '.join([
156 156 'rhodecode', 'rhodiumcode', 'mercurial', 'git', 'code review',
157 157 'repo groups', 'ldap', 'repository management', 'hgweb replacement',
158 158 'hgwebdir', 'gitweb replacement', 'serving hgweb',
159 159 ])
160 160
161 161 # long description
162 162 README_FILE = 'README.rst'
163 163 CHANGELOG_FILE = 'CHANGES.rst'
164 164 try:
165 165 long_description = open(README_FILE).read() + '\n\n' + \
166 166 open(CHANGELOG_FILE).read()
167 167
168 168 except IOError, err:
169 169 sys.stderr.write(
170 170 '[WARNING] Cannot find file specified as long_description (%s)\n or '
171 171 'changelog (%s) skipping that file' % (README_FILE, CHANGELOG_FILE)
172 172 )
173 173 long_description = description
174 174
175 175 # packages
176 176 packages = find_packages()
177 177
178 178 paster_commands = [
179 179 'make-config=rhodecode.lib.paster_commands.make_config:Command',
180 180 'setup-rhodecode=rhodecode.lib.paster_commands.setup_rhodecode:Command',
181 181 'update-repoinfo=rhodecode.lib.paster_commands.update_repoinfo:Command',
182 182 'cache-keys=rhodecode.lib.paster_commands.cache_keys:Command',
183 183 'ishell=rhodecode.lib.paster_commands.ishell:Command',
184 184 'upgrade-db=rhodecode.lib.dbmigrate:UpgradeDb',
185 185 'celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand',
186 186 ]
187 187
188 188 setup(
189 189 name='rhodecode-enterprise-ce',
190 190 version=__version__,
191 191 description=description,
192 192 long_description=long_description,
193 193 keywords=keywords,
194 194 license=__license__,
195 195 author=__author__,
196 196 author_email='marcin@rhodecode.com',
197 197 dependency_links=dependency_links,
198 198 url=__url__,
199 199 install_requires=requirements,
200 200 tests_require=test_requirements,
201 201 classifiers=classifiers,
202 202 setup_requires=setup_requirements,
203 203 data_files=data_files,
204 204 packages=packages,
205 205 include_package_data=True,
206 206 package_data=package_data,
207 207 message_extractors={
208 208 'rhodecode': [
209 209 ('**.py', 'python', None),
210 ('**.js', 'javascript', None),
210 211 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
211 212 ('templates/**.html', 'mako', {'input_encoding': 'utf-8'}),
212 213 ('public/**', 'ignore', None),
213 214 ]
214 215 },
215 216 zip_safe=False,
216 217 paster_plugins=['PasteScript', 'Pylons'],
217 218 entry_points={
218 219 'enterprise.plugins1': [
219 220 'crowd=rhodecode.authentication.plugins.auth_crowd:plugin_factory',
220 221 'headers=rhodecode.authentication.plugins.auth_headers:plugin_factory',
221 222 'jasig_cas=rhodecode.authentication.plugins.auth_jasig_cas:plugin_factory',
222 223 'ldap=rhodecode.authentication.plugins.auth_ldap:plugin_factory',
223 224 'pam=rhodecode.authentication.plugins.auth_pam:plugin_factory',
224 225 'rhodecode=rhodecode.authentication.plugins.auth_rhodecode:plugin_factory',
225 226 'token=rhodecode.authentication.plugins.auth_token:plugin_factory',
226 227 ],
227 228 'paste.app_factory': [
228 229 'main=rhodecode.config.middleware:make_pyramid_app',
229 230 'pylons=rhodecode.config.middleware:make_app',
230 231 ],
231 232 'paste.app_install': [
232 233 'main=pylons.util:PylonsInstaller',
233 234 'pylons=pylons.util:PylonsInstaller',
234 235 ],
235 236 'paste.global_paster_command': paster_commands,
236 237 'pytest11': [
237 238 'pylons=rhodecode.tests.pylons_plugin',
238 239 'enterprise=rhodecode.tests.plugin',
239 240 ],
240 241 'console_scripts': [
241 242 'rcserver=rhodecode.rcserver:main',
242 243 ],
243 244 'beaker.backends': [
244 245 'memorylru_base=rhodecode.lib.memory_lru_debug:MemoryLRUNamespaceManagerBase',
245 246 'memorylru_debug=rhodecode.lib.memory_lru_debug:MemoryLRUNamespaceManagerDebug'
246 247 ]
247 248 },
248 249 )
General Comments 0
You need to be logged in to leave comments. Login now