##// END OF EJS Templates
js: reduce duplicate code - make autocompleteHighlightMatch handle the non-match case too
domruf -
r6784:b9e10022 default
parent child Browse files
Show More
@@ -1,1529 +1,1512 b''
1 /**
1 /**
2 Kallithea JS Files
2 Kallithea JS Files
3 **/
3 **/
4 'use strict';
4 'use strict';
5
5
6 if (typeof console == "undefined" || typeof console.log == "undefined"){
6 if (typeof console == "undefined" || typeof console.log == "undefined"){
7 console = { log: function() {} }
7 console = { log: function() {} }
8 }
8 }
9
9
10 /**
10 /**
11 * INJECT .format function into String
11 * INJECT .format function into String
12 * Usage: "My name is {0} {1}".format("Johny","Bravo")
12 * Usage: "My name is {0} {1}".format("Johny","Bravo")
13 * Return "My name is Johny Bravo"
13 * Return "My name is Johny Bravo"
14 * Inspired by https://gist.github.com/1049426
14 * Inspired by https://gist.github.com/1049426
15 */
15 */
16 String.prototype.format = function() {
16 String.prototype.format = function() {
17 function format() {
17 function format() {
18 var str = this;
18 var str = this;
19 var len = arguments.length+1;
19 var len = arguments.length+1;
20 var safe = undefined;
20 var safe = undefined;
21 var arg = undefined;
21 var arg = undefined;
22
22
23 // For each {0} {1} {n...} replace with the argument in that position. If
23 // For each {0} {1} {n...} replace with the argument in that position. If
24 // the argument is an object or an array it will be stringified to JSON.
24 // the argument is an object or an array it will be stringified to JSON.
25 for (var i=0; i < len; arg = arguments[i++]) {
25 for (var i=0; i < len; arg = arguments[i++]) {
26 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
26 safe = typeof arg === 'object' ? JSON.stringify(arg) : arg;
27 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
27 str = str.replace(RegExp('\\{'+(i-1)+'\\}', 'g'), safe);
28 }
28 }
29 return str;
29 return str;
30 }
30 }
31
31
32 // Save a reference of what may already exist under the property native.
32 // Save a reference of what may already exist under the property native.
33 // Allows for doing something like: if("".format.native) { /* use native */ }
33 // Allows for doing something like: if("".format.native) { /* use native */ }
34 format.native = String.prototype.format;
34 format.native = String.prototype.format;
35
35
36 // Replace the prototype property
36 // Replace the prototype property
37 return format;
37 return format;
38
38
39 }();
39 }();
40
40
41 String.prototype.strip = function(char) {
41 String.prototype.strip = function(char) {
42 if(char === undefined){
42 if(char === undefined){
43 char = '\\s';
43 char = '\\s';
44 }
44 }
45 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
45 return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), '');
46 }
46 }
47
47
48 String.prototype.lstrip = function(char) {
48 String.prototype.lstrip = function(char) {
49 if(char === undefined){
49 if(char === undefined){
50 char = '\\s';
50 char = '\\s';
51 }
51 }
52 return this.replace(new RegExp('^'+char+'+'),'');
52 return this.replace(new RegExp('^'+char+'+'),'');
53 }
53 }
54
54
55 String.prototype.rstrip = function(char) {
55 String.prototype.rstrip = function(char) {
56 if(char === undefined){
56 if(char === undefined){
57 char = '\\s';
57 char = '\\s';
58 }
58 }
59 return this.replace(new RegExp(''+char+'+$'),'');
59 return this.replace(new RegExp(''+char+'+$'),'');
60 }
60 }
61
61
62 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill
62 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf#Polyfill
63 under MIT license / public domain, see
63 under MIT license / public domain, see
64 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
64 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
65 if(!Array.prototype.indexOf) {
65 if(!Array.prototype.indexOf) {
66 Array.prototype.indexOf = function (searchElement, fromIndex) {
66 Array.prototype.indexOf = function (searchElement, fromIndex) {
67 if ( this === undefined || this === null ) {
67 if ( this === undefined || this === null ) {
68 throw new TypeError( '"this" is null or not defined' );
68 throw new TypeError( '"this" is null or not defined' );
69 }
69 }
70
70
71 var length = this.length >>> 0; // Hack to convert object.length to a UInt32
71 var length = this.length >>> 0; // Hack to convert object.length to a UInt32
72
72
73 fromIndex = +fromIndex || 0;
73 fromIndex = +fromIndex || 0;
74
74
75 if (Math.abs(fromIndex) === Infinity) {
75 if (Math.abs(fromIndex) === Infinity) {
76 fromIndex = 0;
76 fromIndex = 0;
77 }
77 }
78
78
79 if (fromIndex < 0) {
79 if (fromIndex < 0) {
80 fromIndex += length;
80 fromIndex += length;
81 if (fromIndex < 0) {
81 if (fromIndex < 0) {
82 fromIndex = 0;
82 fromIndex = 0;
83 }
83 }
84 }
84 }
85
85
86 for (;fromIndex < length; fromIndex++) {
86 for (;fromIndex < length; fromIndex++) {
87 if (this[fromIndex] === searchElement) {
87 if (this[fromIndex] === searchElement) {
88 return fromIndex;
88 return fromIndex;
89 }
89 }
90 }
90 }
91
91
92 return -1;
92 return -1;
93 };
93 };
94 }
94 }
95
95
96 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility
96 /* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#Compatibility
97 under MIT license / public domain, see
97 under MIT license / public domain, see
98 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
98 https://developer.mozilla.org/en-US/docs/MDN/About#Copyrights_and_licenses */
99 if (!Array.prototype.filter)
99 if (!Array.prototype.filter)
100 {
100 {
101 Array.prototype.filter = function(fun /*, thisArg */)
101 Array.prototype.filter = function(fun /*, thisArg */)
102 {
102 {
103 if (this === void 0 || this === null)
103 if (this === void 0 || this === null)
104 throw new TypeError();
104 throw new TypeError();
105
105
106 var t = Object(this);
106 var t = Object(this);
107 var len = t.length >>> 0;
107 var len = t.length >>> 0;
108 if (typeof fun !== "function")
108 if (typeof fun !== "function")
109 throw new TypeError();
109 throw new TypeError();
110
110
111 var res = [];
111 var res = [];
112 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
112 var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
113 for (var i = 0; i < len; i++)
113 for (var i = 0; i < len; i++)
114 {
114 {
115 if (i in t)
115 if (i in t)
116 {
116 {
117 var val = t[i];
117 var val = t[i];
118
118
119 // NOTE: Technically this should Object.defineProperty at
119 // NOTE: Technically this should Object.defineProperty at
120 // the next index, as push can be affected by
120 // the next index, as push can be affected by
121 // properties on Object.prototype and Array.prototype.
121 // properties on Object.prototype and Array.prototype.
122 // But that method's new, and collisions should be
122 // But that method's new, and collisions should be
123 // rare, so use the more-compatible alternative.
123 // rare, so use the more-compatible alternative.
124 if (fun.call(thisArg, val, i, t))
124 if (fun.call(thisArg, val, i, t))
125 res.push(val);
125 res.push(val);
126 }
126 }
127 }
127 }
128
128
129 return res;
129 return res;
130 };
130 };
131 }
131 }
132
132
133 /**
133 /**
134 * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/
134 * A customized version of PyRoutes.JS from https://pypi.python.org/pypi/pyroutes.js/
135 * which is copyright Stephane Klein and was made available under the BSD License.
135 * which is copyright Stephane Klein and was made available under the BSD License.
136 *
136 *
137 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
137 * Usage pyroutes.url('mark_error_fixed',{"error_id":error_id}) // /mark_error_fixed/<error_id>
138 */
138 */
139 var pyroutes = (function() {
139 var pyroutes = (function() {
140 var matchlist = {};
140 var matchlist = {};
141 var sprintf = (function() {
141 var sprintf = (function() {
142 function get_type(variable) {
142 function get_type(variable) {
143 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
143 return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase();
144 }
144 }
145 function str_repeat(input, multiplier) {
145 function str_repeat(input, multiplier) {
146 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
146 for (var output = []; multiplier > 0; output[--multiplier] = input) {/* do nothing */}
147 return output.join('');
147 return output.join('');
148 }
148 }
149
149
150 var str_format = function() {
150 var str_format = function() {
151 if (!str_format.cache.hasOwnProperty(arguments[0])) {
151 if (!str_format.cache.hasOwnProperty(arguments[0])) {
152 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
152 str_format.cache[arguments[0]] = str_format.parse(arguments[0]);
153 }
153 }
154 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
154 return str_format.format.call(null, str_format.cache[arguments[0]], arguments);
155 };
155 };
156
156
157 str_format.format = function(parse_tree, argv) {
157 str_format.format = function(parse_tree, argv) {
158 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
158 var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length;
159 for (i = 0; i < tree_length; i++) {
159 for (i = 0; i < tree_length; i++) {
160 node_type = get_type(parse_tree[i]);
160 node_type = get_type(parse_tree[i]);
161 if (node_type === 'string') {
161 if (node_type === 'string') {
162 output.push(parse_tree[i]);
162 output.push(parse_tree[i]);
163 }
163 }
164 else if (node_type === 'array') {
164 else if (node_type === 'array') {
165 match = parse_tree[i]; // convenience purposes only
165 match = parse_tree[i]; // convenience purposes only
166 if (match[2]) { // keyword argument
166 if (match[2]) { // keyword argument
167 arg = argv[cursor];
167 arg = argv[cursor];
168 for (k = 0; k < match[2].length; k++) {
168 for (k = 0; k < match[2].length; k++) {
169 if (!arg.hasOwnProperty(match[2][k])) {
169 if (!arg.hasOwnProperty(match[2][k])) {
170 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
170 throw(sprintf('[sprintf] property "%s" does not exist', match[2][k]));
171 }
171 }
172 arg = arg[match[2][k]];
172 arg = arg[match[2][k]];
173 }
173 }
174 }
174 }
175 else if (match[1]) { // positional argument (explicit)
175 else if (match[1]) { // positional argument (explicit)
176 arg = argv[match[1]];
176 arg = argv[match[1]];
177 }
177 }
178 else { // positional argument (implicit)
178 else { // positional argument (implicit)
179 arg = argv[cursor++];
179 arg = argv[cursor++];
180 }
180 }
181
181
182 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
182 if (/[^s]/.test(match[8]) && (get_type(arg) != 'number')) {
183 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
183 throw(sprintf('[sprintf] expecting number but found %s', get_type(arg)));
184 }
184 }
185 switch (match[8]) {
185 switch (match[8]) {
186 case 'b': arg = arg.toString(2); break;
186 case 'b': arg = arg.toString(2); break;
187 case 'c': arg = String.fromCharCode(arg); break;
187 case 'c': arg = String.fromCharCode(arg); break;
188 case 'd': arg = parseInt(arg, 10); break;
188 case 'd': arg = parseInt(arg, 10); break;
189 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
189 case 'e': arg = match[7] ? arg.toExponential(match[7]) : arg.toExponential(); break;
190 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
190 case 'f': arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg); break;
191 case 'o': arg = arg.toString(8); break;
191 case 'o': arg = arg.toString(8); break;
192 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
192 case 's': arg = ((arg = String(arg)) && match[7] ? arg.substring(0, match[7]) : arg); break;
193 case 'u': arg = Math.abs(arg); break;
193 case 'u': arg = Math.abs(arg); break;
194 case 'x': arg = arg.toString(16); break;
194 case 'x': arg = arg.toString(16); break;
195 case 'X': arg = arg.toString(16).toUpperCase(); break;
195 case 'X': arg = arg.toString(16).toUpperCase(); break;
196 }
196 }
197 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
197 arg = (/[def]/.test(match[8]) && match[3] && arg >= 0 ? '+'+ arg : arg);
198 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
198 pad_character = match[4] ? match[4] == '0' ? '0' : match[4].charAt(1) : ' ';
199 pad_length = match[6] - String(arg).length;
199 pad_length = match[6] - String(arg).length;
200 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
200 pad = match[6] ? str_repeat(pad_character, pad_length) : '';
201 output.push(match[5] ? arg + pad : pad + arg);
201 output.push(match[5] ? arg + pad : pad + arg);
202 }
202 }
203 }
203 }
204 return output.join('');
204 return output.join('');
205 };
205 };
206
206
207 str_format.cache = {};
207 str_format.cache = {};
208
208
209 str_format.parse = function(fmt) {
209 str_format.parse = function(fmt) {
210 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
210 var _fmt = fmt, match = [], parse_tree = [], arg_names = 0;
211 while (_fmt) {
211 while (_fmt) {
212 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
212 if ((match = /^[^\x25]+/.exec(_fmt)) !== null) {
213 parse_tree.push(match[0]);
213 parse_tree.push(match[0]);
214 }
214 }
215 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
215 else if ((match = /^\x25{2}/.exec(_fmt)) !== null) {
216 parse_tree.push('%');
216 parse_tree.push('%');
217 }
217 }
218 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
218 else if ((match = /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(_fmt)) !== null) {
219 if (match[2]) {
219 if (match[2]) {
220 arg_names |= 1;
220 arg_names |= 1;
221 var field_list = [], replacement_field = match[2], field_match = [];
221 var field_list = [], replacement_field = match[2], field_match = [];
222 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
222 if ((field_match = /^([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
223 field_list.push(field_match[1]);
223 field_list.push(field_match[1]);
224 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
224 while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
225 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
225 if ((field_match = /^\.([a-z_][a-z_\d]*)/i.exec(replacement_field)) !== null) {
226 field_list.push(field_match[1]);
226 field_list.push(field_match[1]);
227 }
227 }
228 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
228 else if ((field_match = /^\[(\d+)\]/.exec(replacement_field)) !== null) {
229 field_list.push(field_match[1]);
229 field_list.push(field_match[1]);
230 }
230 }
231 else {
231 else {
232 throw('[sprintf] huh?');
232 throw('[sprintf] huh?');
233 }
233 }
234 }
234 }
235 }
235 }
236 else {
236 else {
237 throw('[sprintf] huh?');
237 throw('[sprintf] huh?');
238 }
238 }
239 match[2] = field_list;
239 match[2] = field_list;
240 }
240 }
241 else {
241 else {
242 arg_names |= 2;
242 arg_names |= 2;
243 }
243 }
244 if (arg_names === 3) {
244 if (arg_names === 3) {
245 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
245 throw('[sprintf] mixing positional and named placeholders is not (yet) supported');
246 }
246 }
247 parse_tree.push(match);
247 parse_tree.push(match);
248 }
248 }
249 else {
249 else {
250 throw('[sprintf] huh?');
250 throw('[sprintf] huh?');
251 }
251 }
252 _fmt = _fmt.substring(match[0].length);
252 _fmt = _fmt.substring(match[0].length);
253 }
253 }
254 return parse_tree;
254 return parse_tree;
255 };
255 };
256
256
257 return str_format;
257 return str_format;
258 })();
258 })();
259
259
260 var vsprintf = function(fmt, argv) {
260 var vsprintf = function(fmt, argv) {
261 argv.unshift(fmt);
261 argv.unshift(fmt);
262 return sprintf.apply(null, argv);
262 return sprintf.apply(null, argv);
263 };
263 };
264 return {
264 return {
265 'url': function(route_name, params) {
265 'url': function(route_name, params) {
266 var result = route_name;
266 var result = route_name;
267 if (typeof(params) != 'object'){
267 if (typeof(params) != 'object'){
268 params = {};
268 params = {};
269 }
269 }
270 if (matchlist.hasOwnProperty(route_name)) {
270 if (matchlist.hasOwnProperty(route_name)) {
271 var route = matchlist[route_name];
271 var route = matchlist[route_name];
272 // param substitution
272 // param substitution
273 for(var i=0; i < route[1].length; i++) {
273 for(var i=0; i < route[1].length; i++) {
274 if (!params.hasOwnProperty(route[1][i]))
274 if (!params.hasOwnProperty(route[1][i]))
275 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
275 throw new Error(route[1][i] + ' missing in "' + route_name + '" route generation');
276 }
276 }
277 result = sprintf(route[0], params);
277 result = sprintf(route[0], params);
278
278
279 var ret = [];
279 var ret = [];
280 //extra params => GET
280 //extra params => GET
281 for(var param in params){
281 for(var param in params){
282 if (route[1].indexOf(param) == -1){
282 if (route[1].indexOf(param) == -1){
283 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
283 ret.push(encodeURIComponent(param) + "=" + encodeURIComponent(params[param]));
284 }
284 }
285 }
285 }
286 var _parts = ret.join("&");
286 var _parts = ret.join("&");
287 if(_parts){
287 if(_parts){
288 result = result +'?'+ _parts
288 result = result +'?'+ _parts
289 }
289 }
290 }
290 }
291
291
292 return result;
292 return result;
293 },
293 },
294 'register': function(route_name, route_tmpl, req_params) {
294 'register': function(route_name, route_tmpl, req_params) {
295 if (typeof(req_params) != 'object') {
295 if (typeof(req_params) != 'object') {
296 req_params = [];
296 req_params = [];
297 }
297 }
298 var keys = [];
298 var keys = [];
299 for (var i=0; i < req_params.length; i++) {
299 for (var i=0; i < req_params.length; i++) {
300 keys.push(req_params[i]);
300 keys.push(req_params[i]);
301 }
301 }
302 matchlist[route_name] = [
302 matchlist[route_name] = [
303 unescape(route_tmpl),
303 unescape(route_tmpl),
304 keys
304 keys
305 ]
305 ]
306 },
306 },
307 '_routes': function(){
307 '_routes': function(){
308 return matchlist;
308 return matchlist;
309 }
309 }
310 }
310 }
311 })();
311 })();
312
312
313
313
314 /* Invoke all functions in callbacks */
314 /* Invoke all functions in callbacks */
315 var _run_callbacks = function(callbacks){
315 var _run_callbacks = function(callbacks){
316 if (callbacks !== undefined){
316 if (callbacks !== undefined){
317 var _l = callbacks.length;
317 var _l = callbacks.length;
318 for (var i=0;i<_l;i++){
318 for (var i=0;i<_l;i++){
319 var func = callbacks[i];
319 var func = callbacks[i];
320 if(typeof(func)=='function'){
320 if(typeof(func)=='function'){
321 try{
321 try{
322 func();
322 func();
323 }catch (err){};
323 }catch (err){};
324 }
324 }
325 }
325 }
326 }
326 }
327 }
327 }
328
328
329 /**
329 /**
330 * turns objects into GET query string
330 * turns objects into GET query string
331 */
331 */
332 var _toQueryString = function(o) {
332 var _toQueryString = function(o) {
333 if(typeof o !== 'object') {
333 if(typeof o !== 'object') {
334 return false;
334 return false;
335 }
335 }
336 var _p, _qs = [];
336 var _p, _qs = [];
337 for(_p in o) {
337 for(_p in o) {
338 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
338 _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
339 }
339 }
340 return _qs.join('&');
340 return _qs.join('&');
341 };
341 };
342
342
343 /**
343 /**
344 * Load HTML into DOM using Ajax
344 * Load HTML into DOM using Ajax
345 *
345 *
346 * @param $target: load html async and place it (or an error message) here
346 * @param $target: load html async and place it (or an error message) here
347 * @param success: success callback function
347 * @param success: success callback function
348 * @param args: query parameters to pass to url
348 * @param args: query parameters to pass to url
349 */
349 */
350 function asynchtml(url, $target, success, args){
350 function asynchtml(url, $target, success, args){
351 if(args===undefined){
351 if(args===undefined){
352 args=null;
352 args=null;
353 }
353 }
354 $target.html(_TM['Loading ...']).css('opacity','0.3');
354 $target.html(_TM['Loading ...']).css('opacity','0.3');
355
355
356 return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
356 return $.ajax({url: url, data: args, headers: {'X-PARTIAL-XHR': '1'}, cache: false, dataType: 'html'})
357 .done(function(html) {
357 .done(function(html) {
358 $target.html(html);
358 $target.html(html);
359 $target.css('opacity','1.0');
359 $target.css('opacity','1.0');
360 //execute the given original callback
360 //execute the given original callback
361 if (success !== undefined && success) {
361 if (success !== undefined && success) {
362 success();
362 success();
363 }
363 }
364 })
364 })
365 .fail(function(jqXHR, textStatus, errorThrown) {
365 .fail(function(jqXHR, textStatus, errorThrown) {
366 if (textStatus == "abort")
366 if (textStatus == "abort")
367 return;
367 return;
368 $target.html('<span class="bg-danger">ERROR: {0}</span>'.format(textStatus));
368 $target.html('<span class="bg-danger">ERROR: {0}</span>'.format(textStatus));
369 $target.css('opacity','1.0');
369 $target.css('opacity','1.0');
370 })
370 })
371 ;
371 ;
372 };
372 };
373
373
374 var ajaxGET = function(url, success, failure) {
374 var ajaxGET = function(url, success, failure) {
375 if(failure === undefined) {
375 if(failure === undefined) {
376 failure = function(jqXHR, textStatus, errorThrown) {
376 failure = function(jqXHR, textStatus, errorThrown) {
377 if (textStatus != "abort")
377 if (textStatus != "abort")
378 alert("Ajax GET error: " + textStatus);
378 alert("Ajax GET error: " + textStatus);
379 };
379 };
380 }
380 }
381 return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
381 return $.ajax({url: url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
382 .done(success)
382 .done(success)
383 .fail(failure);
383 .fail(failure);
384 };
384 };
385
385
386 var ajaxPOST = function(url, postData, success, failure) {
386 var ajaxPOST = function(url, postData, success, failure) {
387 postData['_authentication_token'] = _authentication_token;
387 postData['_authentication_token'] = _authentication_token;
388 var postData = _toQueryString(postData);
388 var postData = _toQueryString(postData);
389 if(failure === undefined) {
389 if(failure === undefined) {
390 failure = function(jqXHR, textStatus, errorThrown) {
390 failure = function(jqXHR, textStatus, errorThrown) {
391 if (textStatus != "abort")
391 if (textStatus != "abort")
392 alert("Error posting to server: " + textStatus);
392 alert("Error posting to server: " + textStatus);
393 };
393 };
394 }
394 }
395 return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
395 return $.ajax({url: url, data: postData, type: 'POST', headers: {'X-PARTIAL-XHR': '1'}, cache: false})
396 .done(success)
396 .done(success)
397 .fail(failure);
397 .fail(failure);
398 };
398 };
399
399
400
400
401 /**
401 /**
402 * activate .show_more links
402 * activate .show_more links
403 * the .show_more must have an id that is the the id of an element to hide prefixed with _
403 * the .show_more must have an id that is the the id of an element to hide prefixed with _
404 * the parentnode will be displayed
404 * the parentnode will be displayed
405 */
405 */
406 var show_more_event = function(){
406 var show_more_event = function(){
407 $('.show_more').click(function(e){
407 $('.show_more').click(function(e){
408 var el = e.currentTarget;
408 var el = e.currentTarget;
409 $('#' + el.id.substring(1)).hide();
409 $('#' + el.id.substring(1)).hide();
410 $(el.parentNode).show();
410 $(el.parentNode).show();
411 });
411 });
412 };
412 };
413
413
414
414
415 var _onSuccessFollow = function(target){
415 var _onSuccessFollow = function(target){
416 var $target = $(target);
416 var $target = $(target);
417 var $f_cnt = $('#current_followers_count');
417 var $f_cnt = $('#current_followers_count');
418 if ($target.hasClass('follow')) {
418 if ($target.hasClass('follow')) {
419 $target.removeClass('follow').addClass('following');
419 $target.removeClass('follow').addClass('following');
420 $target.prop('title', _TM['Stop following this repository']);
420 $target.prop('title', _TM['Stop following this repository']);
421 if ($f_cnt.html()) {
421 if ($f_cnt.html()) {
422 var cnt = Number($f_cnt.html())+1;
422 var cnt = Number($f_cnt.html())+1;
423 $f_cnt.html(cnt);
423 $f_cnt.html(cnt);
424 }
424 }
425 } else {
425 } else {
426 $target.removeClass('following').addClass('follow');
426 $target.removeClass('following').addClass('follow');
427 $target.prop('title', _TM['Start following this repository']);
427 $target.prop('title', _TM['Start following this repository']);
428 if ($f_cnt.html()) {
428 if ($f_cnt.html()) {
429 var cnt = Number($f_cnt.html())-1;
429 var cnt = Number($f_cnt.html())-1;
430 $f_cnt.html(cnt);
430 $f_cnt.html(cnt);
431 }
431 }
432 }
432 }
433 }
433 }
434
434
435 var toggleFollowingRepo = function(target, follows_repository_id){
435 var toggleFollowingRepo = function(target, follows_repository_id){
436 var args = 'follows_repository_id=' + follows_repository_id;
436 var args = 'follows_repository_id=' + follows_repository_id;
437 args += '&amp;_authentication_token=' + _authentication_token;
437 args += '&amp;_authentication_token=' + _authentication_token;
438 $.post(TOGGLE_FOLLOW_URL, args, function(data){
438 $.post(TOGGLE_FOLLOW_URL, args, function(data){
439 _onSuccessFollow(target);
439 _onSuccessFollow(target);
440 });
440 });
441 return false;
441 return false;
442 };
442 };
443
443
444 var showRepoSize = function(target, repo_name){
444 var showRepoSize = function(target, repo_name){
445 var args = '_authentication_token=' + _authentication_token;
445 var args = '_authentication_token=' + _authentication_token;
446
446
447 if(!$("#" + target).hasClass('loaded')){
447 if(!$("#" + target).hasClass('loaded')){
448 $("#" + target).html(_TM['Loading ...']);
448 $("#" + target).html(_TM['Loading ...']);
449 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
449 var url = pyroutes.url('repo_size', {"repo_name":repo_name});
450 $.post(url, args, function(data) {
450 $.post(url, args, function(data) {
451 $("#" + target).html(data);
451 $("#" + target).html(data);
452 $("#" + target).addClass('loaded');
452 $("#" + target).addClass('loaded');
453 });
453 });
454 }
454 }
455 return false;
455 return false;
456 };
456 };
457
457
458 /**
458 /**
459 * load tooltips dynamically based on data attributes, used for .lazy-cs changeset links
459 * load tooltips dynamically based on data attributes, used for .lazy-cs changeset links
460 */
460 */
461 var get_changeset_tooltip = function() {
461 var get_changeset_tooltip = function() {
462 var $target = $(this);
462 var $target = $(this);
463 var tooltip = $target.data('tooltip');
463 var tooltip = $target.data('tooltip');
464 if (!tooltip) {
464 if (!tooltip) {
465 var raw_id = $target.data('raw_id');
465 var raw_id = $target.data('raw_id');
466 var repo_name = $target.data('repo_name');
466 var repo_name = $target.data('repo_name');
467 var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": raw_id});
467 var url = pyroutes.url('changeset_info', {"repo_name": repo_name, "revision": raw_id});
468
468
469 $.ajax(url, {
469 $.ajax(url, {
470 async: false,
470 async: false,
471 success: function(data) {
471 success: function(data) {
472 tooltip = data["message"];
472 tooltip = data["message"];
473 }
473 }
474 });
474 });
475 $target.data('tooltip', tooltip);
475 $target.data('tooltip', tooltip);
476 }
476 }
477 return tooltip;
477 return tooltip;
478 };
478 };
479
479
480 /**
480 /**
481 * activate tooltips and popups
481 * activate tooltips and popups
482 */
482 */
483 var tooltip_activate = function(){
483 var tooltip_activate = function(){
484 function placement(p, e){
484 function placement(p, e){
485 if(e.getBoundingClientRect().top > 2*$(window).height()/3){
485 if(e.getBoundingClientRect().top > 2*$(window).height()/3){
486 return 'top';
486 return 'top';
487 }else{
487 }else{
488 return 'bottom';
488 return 'bottom';
489 }
489 }
490 }
490 }
491 $(document).ready(function(){
491 $(document).ready(function(){
492 $('[data-toggle="tooltip"]').tooltip({
492 $('[data-toggle="tooltip"]').tooltip({
493 container: 'body',
493 container: 'body',
494 placement: placement
494 placement: placement
495 });
495 });
496 $('[data-toggle="popover"]').popover({
496 $('[data-toggle="popover"]').popover({
497 html: true,
497 html: true,
498 container: 'body',
498 container: 'body',
499 placement: placement,
499 placement: placement,
500 trigger: 'hover',
500 trigger: 'hover',
501 template: '<div class="popover cs-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
501 template: '<div class="popover cs-popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
502 });
502 });
503 $('.lazy-cs').tooltip({
503 $('.lazy-cs').tooltip({
504 title: get_changeset_tooltip,
504 title: get_changeset_tooltip,
505 placement: placement
505 placement: placement
506 });
506 });
507 });
507 });
508 };
508 };
509
509
510
510
511 /**
511 /**
512 * Quick filter widget
512 * Quick filter widget
513 *
513 *
514 * @param target: filter input target
514 * @param target: filter input target
515 * @param nodes: list of nodes in html we want to filter.
515 * @param nodes: list of nodes in html we want to filter.
516 * @param display_element function that takes current node from nodes and
516 * @param display_element function that takes current node from nodes and
517 * does hide or show based on the node
517 * does hide or show based on the node
518 */
518 */
519 var q_filter = (function() {
519 var q_filter = (function() {
520 var _namespace = {};
520 var _namespace = {};
521 var namespace = function (target) {
521 var namespace = function (target) {
522 if (!(target in _namespace)) {
522 if (!(target in _namespace)) {
523 _namespace[target] = {};
523 _namespace[target] = {};
524 }
524 }
525 return _namespace[target];
525 return _namespace[target];
526 };
526 };
527 return function (target, $nodes, display_element) {
527 return function (target, $nodes, display_element) {
528 var $nodes = $nodes;
528 var $nodes = $nodes;
529 var $q_filter_field = $('#' + target);
529 var $q_filter_field = $('#' + target);
530 var F = namespace(target);
530 var F = namespace(target);
531
531
532 $q_filter_field.keyup(function (e) {
532 $q_filter_field.keyup(function (e) {
533 clearTimeout(F.filterTimeout);
533 clearTimeout(F.filterTimeout);
534 F.filterTimeout = setTimeout(F.updateFilter, 600);
534 F.filterTimeout = setTimeout(F.updateFilter, 600);
535 });
535 });
536
536
537 F.filterTimeout = null;
537 F.filterTimeout = null;
538
538
539 F.updateFilter = function () {
539 F.updateFilter = function () {
540 // Reset timeout
540 // Reset timeout
541 F.filterTimeout = null;
541 F.filterTimeout = null;
542
542
543 var obsolete = [];
543 var obsolete = [];
544
544
545 var req = $q_filter_field.val().toLowerCase();
545 var req = $q_filter_field.val().toLowerCase();
546
546
547 var showing = 0;
547 var showing = 0;
548 $nodes.each(function () {
548 $nodes.each(function () {
549 var n = this;
549 var n = this;
550 var target_element = display_element(n);
550 var target_element = display_element(n);
551 if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) {
551 if (req && n.innerHTML.toLowerCase().indexOf(req) == -1) {
552 $(target_element).hide();
552 $(target_element).hide();
553 }
553 }
554 else {
554 else {
555 $(target_element).show();
555 $(target_element).show();
556 showing += 1;
556 showing += 1;
557 }
557 }
558 });
558 });
559
559
560 $('#repo_count').html(showing);
560 $('#repo_count').html(showing);
561 /* FIXME: don't hardcode */
561 /* FIXME: don't hardcode */
562 }
562 }
563 }
563 }
564 })();
564 })();
565
565
566
566
567 /**
567 /**
568 * Comment handling
568 * Comment handling
569 */
569 */
570
570
571 // move comments to their right location, inside new trs
571 // move comments to their right location, inside new trs
572 function move_comments($anchorcomments) {
572 function move_comments($anchorcomments) {
573 $anchorcomments.each(function(i, anchorcomment) {
573 $anchorcomments.each(function(i, anchorcomment) {
574 var $anchorcomment = $(anchorcomment);
574 var $anchorcomment = $(anchorcomment);
575 var target_id = $anchorcomment.data('target-id');
575 var target_id = $anchorcomment.data('target-id');
576 var $comment_div = _get_add_comment_div(target_id);
576 var $comment_div = _get_add_comment_div(target_id);
577 var f_path = $anchorcomment.data('f_path');
577 var f_path = $anchorcomment.data('f_path');
578 var line_no = $anchorcomment.data('line_no');
578 var line_no = $anchorcomment.data('line_no');
579 if ($comment_div[0]) {
579 if ($comment_div[0]) {
580 $comment_div.append($anchorcomment.children());
580 $comment_div.append($anchorcomment.children());
581 if (f_path && line_no) {
581 if (f_path && line_no) {
582 _comment_div_append_add($comment_div, f_path, line_no);
582 _comment_div_append_add($comment_div, f_path, line_no);
583 } else {
583 } else {
584 _comment_div_append_form($comment_div, f_path, line_no);
584 _comment_div_append_form($comment_div, f_path, line_no);
585 }
585 }
586 } else {
586 } else {
587 $anchorcomment.before("Comment to {0} line {1} which is outside the diff context:".format(f_path || '?', line_no || '?'));
587 $anchorcomment.before("Comment to {0} line {1} which is outside the diff context:".format(f_path || '?', line_no || '?'));
588 }
588 }
589 });
589 });
590 linkInlineComments($('.firstlink'), $('.comment:first-child'));
590 linkInlineComments($('.firstlink'), $('.comment:first-child'));
591 }
591 }
592
592
593 // comment bubble was clicked - insert new tr and show form
593 // comment bubble was clicked - insert new tr and show form
594 function show_comment_form($bubble) {
594 function show_comment_form($bubble) {
595 var children = $bubble.closest('tr.line').children('[id]');
595 var children = $bubble.closest('tr.line').children('[id]');
596 var line_td_id = children[children.length - 1].id;
596 var line_td_id = children[children.length - 1].id;
597 var $comment_div = _get_add_comment_div(line_td_id);
597 var $comment_div = _get_add_comment_div(line_td_id);
598 var f_path = $bubble.closest('[data-f_path]').data('f_path');
598 var f_path = $bubble.closest('[data-f_path]').data('f_path');
599 var parts = line_td_id.split('_');
599 var parts = line_td_id.split('_');
600 var line_no = parts[parts.length-1];
600 var line_no = parts[parts.length-1];
601 comment_div_state($comment_div, f_path, line_no, true);
601 comment_div_state($comment_div, f_path, line_no, true);
602 }
602 }
603
603
604 // return comment div for target_id - add it if it doesn't exist yet
604 // return comment div for target_id - add it if it doesn't exist yet
605 function _get_add_comment_div(target_id) {
605 function _get_add_comment_div(target_id) {
606 var comments_box_id = 'comments-' + target_id;
606 var comments_box_id = 'comments-' + target_id;
607 var $comments_box = $('#' + comments_box_id);
607 var $comments_box = $('#' + comments_box_id);
608 if (!$comments_box.length) {
608 if (!$comments_box.length) {
609 var html = '<tr><td id="{0}" colspan="3" class="inline-comments"></td></tr>'.format(comments_box_id);
609 var html = '<tr><td id="{0}" colspan="3" class="inline-comments"></td></tr>'.format(comments_box_id);
610 $('#' + target_id).closest('tr').after(html);
610 $('#' + target_id).closest('tr').after(html);
611 $comments_box = $('#' + comments_box_id);
611 $comments_box = $('#' + comments_box_id);
612 }
612 }
613 return $comments_box;
613 return $comments_box;
614 }
614 }
615
615
616 // Set $comment_div state - showing or not showing form and Add button.
616 // Set $comment_div state - showing or not showing form and Add button.
617 // An Add button is shown on non-empty forms when no form is shown.
617 // An Add button is shown on non-empty forms when no form is shown.
618 // The form is controlled by show_form_opt - if undefined, form is only shown for general comments.
618 // The form is controlled by show_form_opt - if undefined, form is only shown for general comments.
619 function comment_div_state($comment_div, f_path, line_no, show_form_opt) {
619 function comment_div_state($comment_div, f_path, line_no, show_form_opt) {
620 var show_form = show_form_opt !== undefined ? show_form_opt : !f_path && !line_no;
620 var show_form = show_form_opt !== undefined ? show_form_opt : !f_path && !line_no;
621 var $forms = $comment_div.children('.comment-inline-form');
621 var $forms = $comment_div.children('.comment-inline-form');
622 var $buttonrow = $comment_div.children('.add-button-row');
622 var $buttonrow = $comment_div.children('.add-button-row');
623 var $comments = $comment_div.children('.comment');
623 var $comments = $comment_div.children('.comment');
624 $forms.remove();
624 $forms.remove();
625 $buttonrow.remove();
625 $buttonrow.remove();
626 if (show_form) {
626 if (show_form) {
627 _comment_div_append_form($comment_div, f_path, line_no);
627 _comment_div_append_form($comment_div, f_path, line_no);
628 } else if ($comments.length) {
628 } else if ($comments.length) {
629 _comment_div_append_add($comment_div, f_path, line_no);
629 _comment_div_append_add($comment_div, f_path, line_no);
630 } else {
630 } else {
631 $comment_div.parent('tr').remove();
631 $comment_div.parent('tr').remove();
632 }
632 }
633 }
633 }
634
634
635 // append an Add button to $comment_div and hook it up to show form
635 // append an Add button to $comment_div and hook it up to show form
636 function _comment_div_append_add($comment_div, f_path, line_no) {
636 function _comment_div_append_add($comment_div, f_path, line_no) {
637 var addlabel = TRANSLATION_MAP['Add Another Comment'];
637 var addlabel = TRANSLATION_MAP['Add Another Comment'];
638 var $add = $('<div class="add-button-row"><span class="btn btn-default btn-xs add-button">{0}</span></div>'.format(addlabel));
638 var $add = $('<div class="add-button-row"><span class="btn btn-default btn-xs add-button">{0}</span></div>'.format(addlabel));
639 $comment_div.append($add);
639 $comment_div.append($add);
640 $add.children('.add-button').click(function(e) {
640 $add.children('.add-button').click(function(e) {
641 comment_div_state($comment_div, f_path, line_no, true);
641 comment_div_state($comment_div, f_path, line_no, true);
642 });
642 });
643 }
643 }
644
644
645 // append a comment form to $comment_div
645 // append a comment form to $comment_div
646 function _comment_div_append_form($comment_div, f_path, line_no) {
646 function _comment_div_append_form($comment_div, f_path, line_no) {
647 var $form_div = $('#comment-inline-form-template').children()
647 var $form_div = $('#comment-inline-form-template').children()
648 .clone()
648 .clone()
649 .addClass('comment-inline-form');
649 .addClass('comment-inline-form');
650 $comment_div.append($form_div);
650 $comment_div.append($form_div);
651 var $preview = $comment_div.find("div.comment-preview");
651 var $preview = $comment_div.find("div.comment-preview");
652 var $form = $comment_div.find("form");
652 var $form = $comment_div.find("form");
653 var $textarea = $form.find('textarea');
653 var $textarea = $form.find('textarea');
654
654
655 $form.submit(function(e) {
655 $form.submit(function(e) {
656 e.preventDefault();
656 e.preventDefault();
657
657
658 var text = $textarea.val();
658 var text = $textarea.val();
659 var review_status = $form.find('input:radio[name=changeset_status]:checked').val();
659 var review_status = $form.find('input:radio[name=changeset_status]:checked').val();
660 var pr_close = $form.find('input:checkbox[name=save_close]:checked').length ? 'on' : '';
660 var pr_close = $form.find('input:checkbox[name=save_close]:checked').length ? 'on' : '';
661 var pr_delete = $form.find('input:checkbox[name=save_delete]:checked').length ? 'delete' : '';
661 var pr_delete = $form.find('input:checkbox[name=save_delete]:checked').length ? 'delete' : '';
662
662
663 if (!text && !review_status && !pr_close && !pr_delete) {
663 if (!text && !review_status && !pr_close && !pr_delete) {
664 alert("Please provide a comment");
664 alert("Please provide a comment");
665 return false;
665 return false;
666 }
666 }
667
667
668 if (pr_delete) {
668 if (pr_delete) {
669 if (text || review_status || pr_close) {
669 if (text || review_status || pr_close) {
670 alert('Cannot delete pull request while making other changes');
670 alert('Cannot delete pull request while making other changes');
671 return false;
671 return false;
672 }
672 }
673 if (!confirm('Confirm to delete this pull request')) {
673 if (!confirm('Confirm to delete this pull request')) {
674 return false;
674 return false;
675 }
675 }
676 var comments = $('.comment').size();
676 var comments = $('.comment').size();
677 if (comments > 0 &&
677 if (comments > 0 &&
678 !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) {
678 !confirm('Confirm again to delete this pull request with {0} comments'.format(comments))) {
679 return false;
679 return false;
680 }
680 }
681 }
681 }
682
682
683 if (review_status) {
683 if (review_status) {
684 var $review_status = $preview.find('.automatic-comment');
684 var $review_status = $preview.find('.automatic-comment');
685 var review_status_lbl = $("#comment-inline-form-template input.status_change_radio[value='" + review_status + "']").parent().text().strip();
685 var review_status_lbl = $("#comment-inline-form-template input.status_change_radio[value='" + review_status + "']").parent().text().strip();
686 $review_status.find('.comment-status-label').text(review_status_lbl);
686 $review_status.find('.comment-status-label').text(review_status_lbl);
687 $review_status.show();
687 $review_status.show();
688 }
688 }
689 $preview.find('.comment-text div').text(text);
689 $preview.find('.comment-text div').text(text);
690 $preview.show();
690 $preview.show();
691 $textarea.val('');
691 $textarea.val('');
692 if (f_path && line_no) {
692 if (f_path && line_no) {
693 $form.hide();
693 $form.hide();
694 }
694 }
695
695
696 var postData = {
696 var postData = {
697 'text': text,
697 'text': text,
698 'f_path': f_path,
698 'f_path': f_path,
699 'line': line_no,
699 'line': line_no,
700 'changeset_status': review_status,
700 'changeset_status': review_status,
701 'save_close': pr_close,
701 'save_close': pr_close,
702 'save_delete': pr_delete
702 'save_delete': pr_delete
703 };
703 };
704 var success = function(json_data) {
704 var success = function(json_data) {
705 if (pr_delete) {
705 if (pr_delete) {
706 location = json_data['location'];
706 location = json_data['location'];
707 } else {
707 } else {
708 $comment_div.append(json_data['rendered_text']);
708 $comment_div.append(json_data['rendered_text']);
709 comment_div_state($comment_div, f_path, line_no);
709 comment_div_state($comment_div, f_path, line_no);
710 linkInlineComments($('.firstlink'), $('.comment:first-child'));
710 linkInlineComments($('.firstlink'), $('.comment:first-child'));
711 if ((review_status || pr_close) && !f_path && !line_no) {
711 if ((review_status || pr_close) && !f_path && !line_no) {
712 // Page changed a lot - reload it after closing the submitted form
712 // Page changed a lot - reload it after closing the submitted form
713 comment_div_state($comment_div, f_path, line_no, false);
713 comment_div_state($comment_div, f_path, line_no, false);
714 location.reload(true);
714 location.reload(true);
715 }
715 }
716 }
716 }
717 };
717 };
718 var failure = function(x, s, e) {
718 var failure = function(x, s, e) {
719 $preview.removeClass('submitting').addClass('failed');
719 $preview.removeClass('submitting').addClass('failed');
720 var $status = $preview.find('.comment-submission-status');
720 var $status = $preview.find('.comment-submission-status');
721 $('<span>', {
721 $('<span>', {
722 'title': e,
722 'title': e,
723 text: _TM['Unable to post']
723 text: _TM['Unable to post']
724 }).replaceAll($status.contents());
724 }).replaceAll($status.contents());
725 $('<div>', {
725 $('<div>', {
726 'class': 'btn-group'
726 'class': 'btn-group'
727 }).append(
727 }).append(
728 $('<button>', {
728 $('<button>', {
729 'class': 'btn btn-default btn-xs',
729 'class': 'btn btn-default btn-xs',
730 text: _TM['Retry']
730 text: _TM['Retry']
731 }).click(function() {
731 }).click(function() {
732 $status.text(_TM['Submitting ...']);
732 $status.text(_TM['Submitting ...']);
733 $preview.addClass('submitting').removeClass('failed');
733 $preview.addClass('submitting').removeClass('failed');
734 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
734 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
735 }),
735 }),
736 $('<button>', {
736 $('<button>', {
737 'class': 'btn btn-default btn-xs',
737 'class': 'btn btn-default btn-xs',
738 text: _TM['Cancel']
738 text: _TM['Cancel']
739 }).click(function() {
739 }).click(function() {
740 comment_div_state($comment_div, f_path, line_no);
740 comment_div_state($comment_div, f_path, line_no);
741 })
741 })
742 ).appendTo($status);
742 ).appendTo($status);
743 };
743 };
744 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
744 ajaxPOST(AJAX_COMMENT_URL, postData, success, failure);
745 });
745 });
746
746
747 // add event handler for hide/cancel buttons
747 // add event handler for hide/cancel buttons
748 $form.find('.hide-inline-form').click(function(e) {
748 $form.find('.hide-inline-form').click(function(e) {
749 comment_div_state($comment_div, f_path, line_no);
749 comment_div_state($comment_div, f_path, line_no);
750 });
750 });
751
751
752 tooltip_activate();
752 tooltip_activate();
753 if ($textarea.length > 0) {
753 if ($textarea.length > 0) {
754 MentionsAutoComplete($textarea, _USERS_AC_DATA);
754 MentionsAutoComplete($textarea, _USERS_AC_DATA);
755 }
755 }
756 if (f_path) {
756 if (f_path) {
757 $textarea.focus();
757 $textarea.focus();
758 }
758 }
759 }
759 }
760
760
761
761
762 function deleteComment(comment_id) {
762 function deleteComment(comment_id) {
763 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
763 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
764 var postData = {};
764 var postData = {};
765 var success = function(o) {
765 var success = function(o) {
766 $('#comment-'+comment_id).remove();
766 $('#comment-'+comment_id).remove();
767 // Ignore that this might leave a stray Add button (or have a pending form with another comment) ...
767 // Ignore that this might leave a stray Add button (or have a pending form with another comment) ...
768 }
768 }
769 ajaxPOST(url, postData, success);
769 ajaxPOST(url, postData, success);
770 }
770 }
771
771
772
772
773 /**
773 /**
774 * Double link comments
774 * Double link comments
775 */
775 */
776 var linkInlineComments = function($firstlinks, $comments){
776 var linkInlineComments = function($firstlinks, $comments){
777 if ($comments.length > 0) {
777 if ($comments.length > 0) {
778 $firstlinks.html('<a href="#{0}">First comment</a>'.format($comments.prop('id')));
778 $firstlinks.html('<a href="#{0}">First comment</a>'.format($comments.prop('id')));
779 }
779 }
780 if ($comments.length <= 1) {
780 if ($comments.length <= 1) {
781 return;
781 return;
782 }
782 }
783
783
784 $comments.each(function(i, e){
784 $comments.each(function(i, e){
785 var prev = '';
785 var prev = '';
786 if (i > 0){
786 if (i > 0){
787 var prev_anchor = $($comments.get(i-1)).prop('id');
787 var prev_anchor = $($comments.get(i-1)).prop('id');
788 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
788 prev = '<a href="#{0}">Previous comment</a>'.format(prev_anchor);
789 }
789 }
790 var next = '';
790 var next = '';
791 if (i+1 < $comments.length){
791 if (i+1 < $comments.length){
792 var next_anchor = $($comments.get(i+1)).prop('id');
792 var next_anchor = $($comments.get(i+1)).prop('id');
793 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
793 next = '<a href="#{0}">Next comment</a>'.format(next_anchor);
794 }
794 }
795 $(this).find('.comment-prev-next-links').html(
795 $(this).find('.comment-prev-next-links').html(
796 '<div class="prev-comment">{0}</div>'.format(prev) +
796 '<div class="prev-comment">{0}</div>'.format(prev) +
797 '<div class="next-comment">{0}</div>'.format(next));
797 '<div class="next-comment">{0}</div>'.format(next));
798 });
798 });
799 }
799 }
800
800
801 /* activate files.html stuff */
801 /* activate files.html stuff */
802 var fileBrowserListeners = function(current_url, node_list_url, url_base){
802 var fileBrowserListeners = function(current_url, node_list_url, url_base){
803 var current_url_branch = "?branch=__BRANCH__";
803 var current_url_branch = "?branch=__BRANCH__";
804
804
805 $('#stay_at_branch').on('click',function(e){
805 $('#stay_at_branch').on('click',function(e){
806 if(e.currentTarget.checked){
806 if(e.currentTarget.checked){
807 var uri = current_url_branch;
807 var uri = current_url_branch;
808 uri = uri.replace('__BRANCH__',e.currentTarget.value);
808 uri = uri.replace('__BRANCH__',e.currentTarget.value);
809 window.location = uri;
809 window.location = uri;
810 }
810 }
811 else{
811 else{
812 window.location = current_url;
812 window.location = current_url;
813 }
813 }
814 });
814 });
815
815
816 var $node_filter = $('#node_filter');
816 var $node_filter = $('#node_filter');
817
817
818 var filterTimeout = null;
818 var filterTimeout = null;
819 var nodes = null;
819 var nodes = null;
820
820
821 var initFilter = function(){
821 var initFilter = function(){
822 $('#node_filter_box_loading').show();
822 $('#node_filter_box_loading').show();
823 $('#search_activate_id').hide();
823 $('#search_activate_id').hide();
824 $('#add_node_id').hide();
824 $('#add_node_id').hide();
825 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
825 $.ajax({url: node_list_url, headers: {'X-PARTIAL-XHR': '1'}, cache: false})
826 .done(function(json) {
826 .done(function(json) {
827 nodes = json.nodes;
827 nodes = json.nodes;
828 $('#node_filter_box_loading').hide();
828 $('#node_filter_box_loading').hide();
829 $('#node_filter_box').show();
829 $('#node_filter_box').show();
830 $node_filter.focus();
830 $node_filter.focus();
831 if($node_filter.hasClass('init')){
831 if($node_filter.hasClass('init')){
832 $node_filter.val('');
832 $node_filter.val('');
833 $node_filter.removeClass('init');
833 $node_filter.removeClass('init');
834 }
834 }
835 })
835 })
836 .fail(function() {
836 .fail(function() {
837 console.log('fileBrowserListeners initFilter failed to load');
837 console.log('fileBrowserListeners initFilter failed to load');
838 })
838 })
839 ;
839 ;
840 }
840 }
841
841
842 var updateFilter = function(e) {
842 var updateFilter = function(e) {
843 return function(){
843 return function(){
844 // Reset timeout
844 // Reset timeout
845 filterTimeout = null;
845 filterTimeout = null;
846 var query = e.currentTarget.value.toLowerCase();
846 var query = e.currentTarget.value.toLowerCase();
847 var match = [];
847 var match = [];
848 var matches = 0;
848 var matches = 0;
849 var matches_max = 20;
849 var matches_max = 20;
850 if (query != ""){
850 if (query != ""){
851 for(var i=0;i<nodes.length;i++){
851 for(var i=0;i<nodes.length;i++){
852 var pos = nodes[i].name.toLowerCase().indexOf(query);
852 var pos = nodes[i].name.toLowerCase().indexOf(query);
853 if(query && pos != -1){
853 if(query && pos != -1){
854 matches++
854 matches++
855 //show only certain amount to not kill browser
855 //show only certain amount to not kill browser
856 if (matches > matches_max){
856 if (matches > matches_max){
857 break;
857 break;
858 }
858 }
859
859
860 var n = nodes[i].name;
860 var n = nodes[i].name;
861 var t = nodes[i].type;
861 var t = nodes[i].type;
862 var n_hl = n.substring(0,pos)
862 var n_hl = n.substring(0,pos)
863 + "<b>{0}</b>".format(n.substring(pos,pos+query.length))
863 + "<b>{0}</b>".format(n.substring(pos,pos+query.length))
864 + n.substring(pos+query.length);
864 + n.substring(pos+query.length);
865 var new_url = url_base.replace('__FPATH__',n);
865 var new_url = url_base.replace('__FPATH__',n);
866 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
866 match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,new_url,n_hl));
867 }
867 }
868 if(match.length >= matches_max){
868 if(match.length >= matches_max){
869 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
869 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['Search truncated']));
870 break;
870 break;
871 }
871 }
872 }
872 }
873 }
873 }
874 if(query != ""){
874 if(query != ""){
875 $('#tbody').hide();
875 $('#tbody').hide();
876 $('#tbody_filtered').show();
876 $('#tbody_filtered').show();
877
877
878 if (match.length==0){
878 if (match.length==0){
879 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
879 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(_TM['No matching files']));
880 }
880 }
881
881
882 $('#tbody_filtered').html(match.join(""));
882 $('#tbody_filtered').html(match.join(""));
883 }
883 }
884 else{
884 else{
885 $('#tbody').show();
885 $('#tbody').show();
886 $('#tbody_filtered').hide();
886 $('#tbody_filtered').hide();
887 }
887 }
888 }
888 }
889 };
889 };
890
890
891 $('#filter_activate').click(function(){
891 $('#filter_activate').click(function(){
892 initFilter();
892 initFilter();
893 });
893 });
894 $node_filter.click(function(){
894 $node_filter.click(function(){
895 if($node_filter.hasClass('init')){
895 if($node_filter.hasClass('init')){
896 $node_filter.val('');
896 $node_filter.val('');
897 $node_filter.removeClass('init');
897 $node_filter.removeClass('init');
898 }
898 }
899 });
899 });
900 $node_filter.keyup(function(e){
900 $node_filter.keyup(function(e){
901 clearTimeout(filterTimeout);
901 clearTimeout(filterTimeout);
902 filterTimeout = setTimeout(updateFilter(e),600);
902 filterTimeout = setTimeout(updateFilter(e),600);
903 });
903 });
904 };
904 };
905
905
906
906
907 var initCodeMirror = function(textarea_id, baseUrl, resetUrl){
907 var initCodeMirror = function(textarea_id, baseUrl, resetUrl){
908 var myCodeMirror = CodeMirror.fromTextArea($('#' + textarea_id)[0], {
908 var myCodeMirror = CodeMirror.fromTextArea($('#' + textarea_id)[0], {
909 mode: "null",
909 mode: "null",
910 lineNumbers: true,
910 lineNumbers: true,
911 indentUnit: 4,
911 indentUnit: 4,
912 autofocus: true
912 autofocus: true
913 });
913 });
914 CodeMirror.modeURL = baseUrl + "/codemirror/mode/%N/%N.js";
914 CodeMirror.modeURL = baseUrl + "/codemirror/mode/%N/%N.js";
915
915
916 $('#reset').click(function(e){
916 $('#reset').click(function(e){
917 window.location=resetUrl;
917 window.location=resetUrl;
918 });
918 });
919
919
920 $('#file_enable').click(function(){
920 $('#file_enable').click(function(){
921 $('#upload_file_container').hide();
921 $('#upload_file_container').hide();
922 $('#filename_container').show();
922 $('#filename_container').show();
923 $('#body').show();
923 $('#body').show();
924 });
924 });
925
925
926 $('#upload_file_enable').click(function(){
926 $('#upload_file_enable').click(function(){
927 $('#upload_file_container').show();
927 $('#upload_file_container').show();
928 $('#filename_container').hide();
928 $('#filename_container').hide();
929 $('#body').hide();
929 $('#body').hide();
930 });
930 });
931
931
932 return myCodeMirror
932 return myCodeMirror
933 };
933 };
934
934
935 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
935 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
936 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
936 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
937 }
937 }
938
938
939
939
940 var _getIdentNode = function(n){
940 var _getIdentNode = function(n){
941 //iterate thrugh nodes until matching interesting node
941 //iterate thrugh nodes until matching interesting node
942
942
943 if (typeof n == 'undefined'){
943 if (typeof n == 'undefined'){
944 return -1
944 return -1
945 }
945 }
946
946
947 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
947 if(typeof n.id != "undefined" && n.id.match('L[0-9]+')){
948 return n
948 return n
949 }
949 }
950 else{
950 else{
951 return _getIdentNode(n.parentNode);
951 return _getIdentNode(n.parentNode);
952 }
952 }
953 };
953 };
954
954
955 /* generate links for multi line selects that can be shown by files.html page_highlights.
955 /* generate links for multi line selects that can be shown by files.html page_highlights.
956 * This is a mouseup handler for hlcode from CodeHtmlFormatter and pygmentize */
956 * This is a mouseup handler for hlcode from CodeHtmlFormatter and pygmentize */
957 var getSelectionLink = function(e) {
957 var getSelectionLink = function(e) {
958 //get selection from start/to nodes
958 //get selection from start/to nodes
959 if (typeof window.getSelection != "undefined") {
959 if (typeof window.getSelection != "undefined") {
960 var s = window.getSelection();
960 var s = window.getSelection();
961
961
962 var from = _getIdentNode(s.anchorNode);
962 var from = _getIdentNode(s.anchorNode);
963 var till = _getIdentNode(s.focusNode);
963 var till = _getIdentNode(s.focusNode);
964
964
965 var f_int = parseInt(from.id.replace('L',''));
965 var f_int = parseInt(from.id.replace('L',''));
966 var t_int = parseInt(till.id.replace('L',''));
966 var t_int = parseInt(till.id.replace('L',''));
967
967
968 var yoffset = 35;
968 var yoffset = 35;
969 var ranges = [parseInt(from.id.replace('L','')), parseInt(till.id.replace('L',''))];
969 var ranges = [parseInt(from.id.replace('L','')), parseInt(till.id.replace('L',''))];
970 if (ranges[0] > ranges[1]){
970 if (ranges[0] > ranges[1]){
971 //highlight from bottom
971 //highlight from bottom
972 yoffset = -yoffset;
972 yoffset = -yoffset;
973 ranges = [ranges[1], ranges[0]];
973 ranges = [ranges[1], ranges[0]];
974 }
974 }
975 var $hl_div = $('div#linktt');
975 var $hl_div = $('div#linktt');
976 // if we select more than 2 lines
976 // if we select more than 2 lines
977 if (ranges[0] != ranges[1]){
977 if (ranges[0] != ranges[1]){
978 if ($hl_div.length) {
978 if ($hl_div.length) {
979 $hl_div.html('');
979 $hl_div.html('');
980 } else {
980 } else {
981 $hl_div = $('<div id="linktt" class="hl-tip-box">');
981 $hl_div = $('<div id="linktt" class="hl-tip-box">');
982 $('body').prepend($hl_div);
982 $('body').prepend($hl_div);
983 }
983 }
984
984
985 $hl_div.append($('<a>').html(_TM['Selection Link']).prop('href', location.href.substring(0, location.href.indexOf('#')) + '#L' + ranges[0] + '-'+ranges[1]));
985 $hl_div.append($('<a>').html(_TM['Selection Link']).prop('href', location.href.substring(0, location.href.indexOf('#')) + '#L' + ranges[0] + '-'+ranges[1]));
986 var xy = $(till).offset();
986 var xy = $(till).offset();
987 $hl_div.css('top', (xy.top + yoffset) + 'px').css('left', xy.left + 'px');
987 $hl_div.css('top', (xy.top + yoffset) + 'px').css('left', xy.left + 'px');
988 $hl_div.show();
988 $hl_div.show();
989 }
989 }
990 else{
990 else{
991 $hl_div.hide();
991 $hl_div.hide();
992 }
992 }
993 }
993 }
994 };
994 };
995
995
996 var deleteNotification = function(url, notification_id, callbacks){
996 var deleteNotification = function(url, notification_id, callbacks){
997 var success = function(o){
997 var success = function(o){
998 $("#notification_"+notification_id).remove();
998 $("#notification_"+notification_id).remove();
999 _run_callbacks(callbacks);
999 _run_callbacks(callbacks);
1000 };
1000 };
1001 var failure = function(o){
1001 var failure = function(o){
1002 alert("deleteNotification failure");
1002 alert("deleteNotification failure");
1003 };
1003 };
1004 var postData = {};
1004 var postData = {};
1005 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1005 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1006 ajaxPOST(sUrl, postData, success, failure);
1006 ajaxPOST(sUrl, postData, success, failure);
1007 };
1007 };
1008
1008
1009 var readNotification = function(url, notification_id, callbacks){
1009 var readNotification = function(url, notification_id, callbacks){
1010 var success = function(o){
1010 var success = function(o){
1011 var $obj = $("#notification_"+notification_id);
1011 var $obj = $("#notification_"+notification_id);
1012 $obj.removeClass('list-group-item-warning');
1012 $obj.removeClass('list-group-item-warning');
1013 $obj.find('.read-notification').remove();
1013 $obj.find('.read-notification').remove();
1014 _run_callbacks(callbacks);
1014 _run_callbacks(callbacks);
1015 };
1015 };
1016 var failure = function(o){
1016 var failure = function(o){
1017 alert("readNotification failure");
1017 alert("readNotification failure");
1018 };
1018 };
1019 var postData = {};
1019 var postData = {};
1020 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1020 var sUrl = url.replace('__NOTIFICATION_ID__',notification_id);
1021 ajaxPOST(sUrl, postData, success, failure);
1021 ajaxPOST(sUrl, postData, success, failure);
1022 };
1022 };
1023
1023
1024 /**
1024 /**
1025 * Autocomplete functionality
1025 * Autocomplete functionality
1026 */
1026 */
1027
1027
1028 // Custom search function for the DataSource of users
1028 // Custom search function for the DataSource of users
1029 var autocompleteMatchUsers = function (sQuery, myUsers) {
1029 var autocompleteMatchUsers = function (sQuery, myUsers) {
1030 // Case insensitive matching
1030 // Case insensitive matching
1031 var query = sQuery.toLowerCase();
1031 var query = sQuery.toLowerCase();
1032 var i = 0;
1032 var i = 0;
1033 var l = myUsers.length;
1033 var l = myUsers.length;
1034 var matches = [];
1034 var matches = [];
1035
1035
1036 // Match against each name of each contact
1036 // Match against each name of each contact
1037 for (; i < l; i++) {
1037 for (; i < l; i++) {
1038 var contact = myUsers[i];
1038 var contact = myUsers[i];
1039 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1039 if (((contact.fname+"").toLowerCase().indexOf(query) > -1) ||
1040 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1040 ((contact.lname+"").toLowerCase().indexOf(query) > -1) ||
1041 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1041 ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) {
1042 matches[matches.length] = contact;
1042 matches[matches.length] = contact;
1043 }
1043 }
1044 }
1044 }
1045 return matches;
1045 return matches;
1046 };
1046 };
1047
1047
1048 // Custom search function for the DataSource of userGroups
1048 // Custom search function for the DataSource of userGroups
1049 var autocompleteMatchGroups = function (sQuery, myGroups) {
1049 var autocompleteMatchGroups = function (sQuery, myGroups) {
1050 // Case insensitive matching
1050 // Case insensitive matching
1051 var query = sQuery.toLowerCase();
1051 var query = sQuery.toLowerCase();
1052 var i = 0;
1052 var i = 0;
1053 var l = myGroups.length;
1053 var l = myGroups.length;
1054 var matches = [];
1054 var matches = [];
1055
1055
1056 // Match against each name of each group
1056 // Match against each name of each group
1057 for (; i < l; i++) {
1057 for (; i < l; i++) {
1058 var matched_group = myGroups[i];
1058 var matched_group = myGroups[i];
1059 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1059 if (matched_group.grname.toLowerCase().indexOf(query) > -1) {
1060 matches[matches.length] = matched_group;
1060 matches[matches.length] = matched_group;
1061 }
1061 }
1062 }
1062 }
1063 return matches;
1063 return matches;
1064 };
1064 };
1065
1065
1066 // Helper highlight function for the formatter
1066 // Highlight the snippet if it is found in the full text.
1067 var autocompleteHighlightMatch = function (full, snippet, matchindex) {
1067 // Snippet must be lowercased already.
1068 var autocompleteHighlightMatch = function (full, snippet) {
1069 var matchindex = full.toLowerCase().indexOf(snippet);
1070 if (matchindex <0)
1071 return full;
1068 return full.substring(0, matchindex)
1072 return full.substring(0, matchindex)
1069 + "<span class='match'>"
1073 + "<span class='match'>"
1070 + full.substr(matchindex, snippet.length)
1074 + full.substr(matchindex, snippet.length)
1071 + "</span>" + full.substring(matchindex + snippet.length);
1075 + "</span>" + full.substring(matchindex + snippet.length);
1072 };
1076 };
1073
1077
1074 // Return html snippet for showing the provided gravatar url
1078 // Return html snippet for showing the provided gravatar url
1075 var gravatar = function(gravatar_lnk, size, cssclass) {
1079 var gravatar = function(gravatar_lnk, size, cssclass) {
1076 if (!gravatar_lnk) {
1080 if (!gravatar_lnk) {
1077 return '';
1081 return '';
1078 }
1082 }
1079 if (gravatar_lnk == 'default') {
1083 if (gravatar_lnk == 'default') {
1080 return '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
1084 return '<i class="icon-user {1}" style="font-size: {0}px;"></i>'.format(size, cssclass);
1081 }
1085 }
1082 return '<img alt="" class="{2}" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, gravatar_lnk, cssclass);
1086 return '<img alt="" class="{2}" style="width: {0}px; height: {0}px" src="{1}"/>'.format(size, gravatar_lnk, cssclass);
1083 }
1087 }
1084
1088
1085 var autocompleteGravatar = function(res, gravatar_lnk, size, group) {
1089 var autocompleteGravatar = function(res, gravatar_lnk, size, group) {
1086 var elem;
1090 var elem;
1087 if (group !== undefined) {
1091 if (group !== undefined) {
1088 elem = '<i class="perm-gravatar-ac icon-users"></i>';
1092 elem = '<i class="perm-gravatar-ac icon-users"></i>';
1089 } else {
1093 } else {
1090 elem = gravatar(gravatar_lnk, size, "perm-gravatar-ac");
1094 elem = gravatar(gravatar_lnk, size, "perm-gravatar-ac");
1091 }
1095 }
1092 return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
1096 return '<div class="ac-container-wrap">{0}{1}</div>'.format(elem, res);
1093 }
1097 }
1094
1098
1095 // Custom formatter to highlight the matching letters
1099 // Custom formatter to highlight the matching letters
1096 var autocompleteFormatter = function (oResultData, sQuery, sResultMatch) {
1100 var autocompleteFormatter = function (oResultData, sQuery, sResultMatch) {
1097 var query;
1101 var query;
1098 if (sQuery && sQuery.toLowerCase) // YAHOO AutoComplete
1102 if (sQuery && sQuery.toLowerCase) // YAHOO AutoComplete
1099 query = sQuery.toLowerCase();
1103 query = sQuery.toLowerCase();
1100 else if (sResultMatch && sResultMatch.term) // select2 - parameter names doesn't match
1104 else if (sResultMatch && sResultMatch.term) // select2 - parameter names doesn't match
1101 query = sResultMatch.term.toLowerCase();
1105 query = sResultMatch.term.toLowerCase();
1102
1106
1103 // group
1107 // group
1104 if (oResultData.grname != undefined) {
1108 if (oResultData.grname != undefined) {
1105 var grname = oResultData.grname;
1109 var grname = oResultData.grname;
1106 var grmembers = oResultData.grmembers;
1110 var grmembers = oResultData.grmembers;
1107 var grnameMatchIndex = grname.toLowerCase().indexOf(query);
1108 var grprefix = "{0}: ".format(_TM['Group']);
1111 var grprefix = "{0}: ".format(_TM['Group']);
1109 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1112 var grsuffix = " ({0} {1})".format(grmembers, _TM['members']);
1110
1113
1111 if (grnameMatchIndex > -1) {
1114 return autocompleteGravatar(grprefix + autocompleteHighlightMatch(grname, query) + grsuffix, null, null, true);
1112 return autocompleteGravatar(grprefix + autocompleteHighlightMatch(grname, query, grnameMatchIndex) + grsuffix, null, null, true);
1113 }
1114 return autocompleteGravatar(grprefix + oResultData.grname + grsuffix, null, null, true);
1115
1115
1116 // users
1116 // users
1117 } else if (oResultData.nname != undefined) {
1117 } else if (oResultData.nname != undefined) {
1118 var fname = oResultData.fname || "";
1118 var fname = oResultData.fname || "";
1119 var lname = oResultData.lname || "";
1119 var lname = oResultData.lname || "";
1120 var nname = oResultData.nname;
1120 var nname = oResultData.nname;
1121
1121
1122 // Guard against null value
1122 // Guard against null value
1123 var fnameMatchIndex = fname.toLowerCase().indexOf(query),
1123 var displayfname = autocompleteHighlightMatch(fname, query);
1124 lnameMatchIndex = lname.toLowerCase().indexOf(query),
1125 nnameMatchIndex = nname.toLowerCase().indexOf(query),
1126 displayfname, displaylname, displaynname, displayname;
1127
1124
1128 if (fnameMatchIndex > -1) {
1125 var displaylname = autocompleteHighlightMatch(lname, query);
1129 displayfname = autocompleteHighlightMatch(fname, query, fnameMatchIndex);
1130 } else {
1131 displayfname = fname;
1132 }
1133
1126
1134 if (lnameMatchIndex > -1) {
1127 var displaynname = autocompleteHighlightMatch(nname, query);
1135 displaylname = autocompleteHighlightMatch(lname, query, lnameMatchIndex);
1136 } else {
1137 displaylname = lname;
1138 }
1139
1128
1140 if (nnameMatchIndex > -1) {
1129 var displayname = displaynname;
1141 displaynname = autocompleteHighlightMatch(nname, query, nnameMatchIndex);
1142 } else {
1143 displaynname = nname;
1144 }
1145
1146 displayname = displaynname;
1147 if (displayfname && displaylname) {
1130 if (displayfname && displaylname) {
1148 displayname = "{0} {1} ({2})".format(displayfname, displaylname, displayname);
1131 displayname = "{0} {1} ({2})".format(displayfname, displaylname, displayname);
1149 }
1132 }
1150
1133
1151 return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1134 return autocompleteGravatar(displayname, oResultData.gravatar_lnk, oResultData.gravatar_size);
1152 } else {
1135 } else {
1153 return '';
1136 return '';
1154 }
1137 }
1155 };
1138 };
1156
1139
1157 // Generate a basic autocomplete instance that can be tweaked further by the caller
1140 // Generate a basic autocomplete instance that can be tweaked further by the caller
1158 var autocompleteCreate = function ($inputElement, matchFunc) {
1141 var autocompleteCreate = function ($inputElement, matchFunc) {
1159 var $container = $('<div/>').insertAfter($inputElement);
1142 var $container = $('<div/>').insertAfter($inputElement);
1160 var datasource = new YAHOO.util.FunctionDataSource(matchFunc);
1143 var datasource = new YAHOO.util.FunctionDataSource(matchFunc);
1161
1144
1162 var autocomplete = new YAHOO.widget.AutoComplete($inputElement[0], $container[0], datasource);
1145 var autocomplete = new YAHOO.widget.AutoComplete($inputElement[0], $container[0], datasource);
1163 autocomplete.useShadow = false;
1146 autocomplete.useShadow = false;
1164 autocomplete.resultTypeList = false;
1147 autocomplete.resultTypeList = false;
1165 autocomplete.animVert = false;
1148 autocomplete.animVert = false;
1166 autocomplete.animHoriz = false;
1149 autocomplete.animHoriz = false;
1167 autocomplete.animSpeed = 0.1;
1150 autocomplete.animSpeed = 0.1;
1168 autocomplete.formatResult = autocompleteFormatter;
1151 autocomplete.formatResult = autocompleteFormatter;
1169
1152
1170 return autocomplete;
1153 return autocomplete;
1171 }
1154 }
1172
1155
1173 var SimpleUserAutoComplete = function ($inputElement, users_list) {
1156 var SimpleUserAutoComplete = function ($inputElement, users_list) {
1174 $inputElement.select2(
1157 $inputElement.select2(
1175 {
1158 {
1176 formatInputTooShort: $inputElement.attr('placeholder'),
1159 formatInputTooShort: $inputElement.attr('placeholder'),
1177 initSelection : function (element, callback) {
1160 initSelection : function (element, callback) {
1178 var val = $inputElement.val();
1161 var val = $inputElement.val();
1179 $.each(users_list, function(i, user) {
1162 $.each(users_list, function(i, user) {
1180 if (user.nname == val)
1163 if (user.nname == val)
1181 callback(user);
1164 callback(user);
1182 });
1165 });
1183 },
1166 },
1184 minimumInputLength: 1,
1167 minimumInputLength: 1,
1185 query: function (query) {
1168 query: function (query) {
1186 query.callback({results: autocompleteMatchUsers(query.term, users_list)});
1169 query.callback({results: autocompleteMatchUsers(query.term, users_list)});
1187 },
1170 },
1188 formatSelection: autocompleteFormatter,
1171 formatSelection: autocompleteFormatter,
1189 formatResult: autocompleteFormatter,
1172 formatResult: autocompleteFormatter,
1190 escapeMarkup: function(m) { return m; },
1173 escapeMarkup: function(m) { return m; },
1191 id: function(item) { return item.nname; },
1174 id: function(item) { return item.nname; },
1192 });
1175 });
1193 }
1176 }
1194
1177
1195 var MembersAutoComplete = function ($inputElement, $typeElement, users_list, groups_list) {
1178 var MembersAutoComplete = function ($inputElement, $typeElement, users_list, groups_list) {
1196
1179
1197 var matchAll = function (sQuery) {
1180 var matchAll = function (sQuery) {
1198 var u = autocompleteMatchUsers(sQuery, users_list);
1181 var u = autocompleteMatchUsers(sQuery, users_list);
1199 var g = autocompleteMatchGroups(sQuery, groups_list);
1182 var g = autocompleteMatchGroups(sQuery, groups_list);
1200 return u.concat(g);
1183 return u.concat(g);
1201 };
1184 };
1202
1185
1203 $inputElement.select2(
1186 $inputElement.select2(
1204 {
1187 {
1205 placeholder: $inputElement.attr('placeholder'),
1188 placeholder: $inputElement.attr('placeholder'),
1206 minimumInputLength: 1,
1189 minimumInputLength: 1,
1207 query: function (query) {
1190 query: function (query) {
1208 query.callback({results: matchAll(query.term)});
1191 query.callback({results: matchAll(query.term)});
1209 },
1192 },
1210 formatSelection: autocompleteFormatter,
1193 formatSelection: autocompleteFormatter,
1211 formatResult: autocompleteFormatter,
1194 formatResult: autocompleteFormatter,
1212 escapeMarkup: function(m) { return m; },
1195 escapeMarkup: function(m) { return m; },
1213 }).on("select2-selecting", function(e) {
1196 }).on("select2-selecting", function(e) {
1214 // e.choice.id is automatically used as selection value - just set the type of the selection
1197 // e.choice.id is automatically used as selection value - just set the type of the selection
1215 if (e.choice.nname != undefined) {
1198 if (e.choice.nname != undefined) {
1216 $typeElement.val('user');
1199 $typeElement.val('user');
1217 } else {
1200 } else {
1218 $typeElement.val('users_group');
1201 $typeElement.val('users_group');
1219 }
1202 }
1220 });
1203 });
1221 }
1204 }
1222
1205
1223 var MentionsAutoComplete = function ($inputElement, users_list) {
1206 var MentionsAutoComplete = function ($inputElement, users_list) {
1224 var matchUsers = function (sQuery) {
1207 var matchUsers = function (sQuery) {
1225 var org_sQuery = sQuery;
1208 var org_sQuery = sQuery;
1226 if(this.mentionQuery == null){
1209 if(this.mentionQuery == null){
1227 return []
1210 return []
1228 }
1211 }
1229 sQuery = this.mentionQuery;
1212 sQuery = this.mentionQuery;
1230 return autocompleteMatchUsers(sQuery, users_list);
1213 return autocompleteMatchUsers(sQuery, users_list);
1231 }
1214 }
1232
1215
1233 var mentionsAC = autocompleteCreate($inputElement, matchUsers);
1216 var mentionsAC = autocompleteCreate($inputElement, matchUsers);
1234 mentionsAC.suppressInputUpdate = true;
1217 mentionsAC.suppressInputUpdate = true;
1235 // Overwrite formatResult to take into account mentionQuery
1218 // Overwrite formatResult to take into account mentionQuery
1236 mentionsAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1219 mentionsAC.formatResult = function (oResultData, sQuery, sResultMatch) {
1237 var org_sQuery = sQuery;
1220 var org_sQuery = sQuery;
1238 if (this.dataSource.mentionQuery != null) {
1221 if (this.dataSource.mentionQuery != null) {
1239 sQuery = this.dataSource.mentionQuery;
1222 sQuery = this.dataSource.mentionQuery;
1240 }
1223 }
1241 return autocompleteFormatter(oResultData, sQuery, sResultMatch);
1224 return autocompleteFormatter(oResultData, sQuery, sResultMatch);
1242 }
1225 }
1243
1226
1244 // Handler for selection of an entry
1227 // Handler for selection of an entry
1245 if(mentionsAC.itemSelectEvent){
1228 if(mentionsAC.itemSelectEvent){
1246 mentionsAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1229 mentionsAC.itemSelectEvent.subscribe(function (sType, aArgs) {
1247 var myAC = aArgs[0]; // reference back to the AC instance
1230 var myAC = aArgs[0]; // reference back to the AC instance
1248 var elLI = aArgs[1]; // reference to the selected LI element
1231 var elLI = aArgs[1]; // reference to the selected LI element
1249 var oData = aArgs[2]; // object literal of selected item's result data
1232 var oData = aArgs[2]; // object literal of selected item's result data
1250 //Replace the mention name with replaced
1233 //Replace the mention name with replaced
1251 var re = new RegExp();
1234 var re = new RegExp();
1252 var org = myAC.getInputEl().value;
1235 var org = myAC.getInputEl().value;
1253 var chunks = myAC.dataSource.chunks
1236 var chunks = myAC.dataSource.chunks
1254 // replace middle chunk(the search term) with actuall match
1237 // replace middle chunk(the search term) with actuall match
1255 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1238 chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery,
1256 '@'+oData.nname+' ');
1239 '@'+oData.nname+' ');
1257 myAC.getInputEl().value = chunks.join('');
1240 myAC.getInputEl().value = chunks.join('');
1258 myAC.getInputEl().focus(); // Y U NO WORK !?
1241 myAC.getInputEl().focus(); // Y U NO WORK !?
1259 });
1242 });
1260 }
1243 }
1261
1244
1262 // in this keybuffer we will gather current value of search !
1245 // in this keybuffer we will gather current value of search !
1263 // since we need to get this just when someone does `@` then we do the
1246 // since we need to get this just when someone does `@` then we do the
1264 // search
1247 // search
1265 mentionsAC.dataSource.chunks = [];
1248 mentionsAC.dataSource.chunks = [];
1266 mentionsAC.dataSource.mentionQuery = null;
1249 mentionsAC.dataSource.mentionQuery = null;
1267
1250
1268 mentionsAC.get_mention = function(msg, max_pos) {
1251 mentionsAC.get_mention = function(msg, max_pos) {
1269 var org = msg;
1252 var org = msg;
1270 // Must match utils2.py MENTIONS_REGEX.
1253 // Must match utils2.py MENTIONS_REGEX.
1271 // Only matching on string up to cursor, so it must end with $
1254 // Only matching on string up to cursor, so it must end with $
1272 var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$');
1255 var re = new RegExp('(?:^|[^a-zA-Z0-9])@([a-zA-Z0-9][-_.a-zA-Z0-9]*[a-zA-Z0-9])$');
1273 var chunks = [];
1256 var chunks = [];
1274
1257
1275 // cut first chunk until current pos
1258 // cut first chunk until current pos
1276 var to_max = msg.substr(0, max_pos);
1259 var to_max = msg.substr(0, max_pos);
1277 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1260 var at_pos = Math.max(0,to_max.lastIndexOf('@')-1);
1278 var msg2 = to_max.substr(at_pos);
1261 var msg2 = to_max.substr(at_pos);
1279
1262
1280 chunks.push(org.substr(0,at_pos)); // prefix chunk
1263 chunks.push(org.substr(0,at_pos)); // prefix chunk
1281 chunks.push(msg2); // search chunk
1264 chunks.push(msg2); // search chunk
1282 chunks.push(org.substr(max_pos)); // postfix chunk
1265 chunks.push(org.substr(max_pos)); // postfix chunk
1283
1266
1284 // clean up msg2 for filtering and regex match
1267 // clean up msg2 for filtering and regex match
1285 var msg2 = msg2.lstrip(' ').lstrip('\n');
1268 var msg2 = msg2.lstrip(' ').lstrip('\n');
1286
1269
1287 if(re.test(msg2)){
1270 if(re.test(msg2)){
1288 var unam = re.exec(msg2)[1];
1271 var unam = re.exec(msg2)[1];
1289 return [unam, chunks];
1272 return [unam, chunks];
1290 }
1273 }
1291 return [null, null];
1274 return [null, null];
1292 };
1275 };
1293
1276
1294 $inputElement.keyup(function(e){
1277 $inputElement.keyup(function(e){
1295 var currentMessage = $inputElement.val();
1278 var currentMessage = $inputElement.val();
1296 var currentCaretPosition = $inputElement[0].selectionStart;
1279 var currentCaretPosition = $inputElement[0].selectionStart;
1297
1280
1298 var unam = mentionsAC.get_mention(currentMessage, currentCaretPosition);
1281 var unam = mentionsAC.get_mention(currentMessage, currentCaretPosition);
1299 var curr_search = null;
1282 var curr_search = null;
1300 if(unam[0]){
1283 if(unam[0]){
1301 curr_search = unam[0];
1284 curr_search = unam[0];
1302 }
1285 }
1303
1286
1304 mentionsAC.dataSource.chunks = unam[1];
1287 mentionsAC.dataSource.chunks = unam[1];
1305 mentionsAC.dataSource.mentionQuery = curr_search;
1288 mentionsAC.dataSource.mentionQuery = curr_search;
1306 });
1289 });
1307 }
1290 }
1308
1291
1309 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
1292 var addReviewMember = function(id,fname,lname,nname,gravatar_link,gravatar_size){
1310 var displayname = nname;
1293 var displayname = nname;
1311 if ((fname != "") && (lname != "")) {
1294 if ((fname != "") && (lname != "")) {
1312 displayname = "{0} {1} ({2})".format(fname, lname, nname);
1295 displayname = "{0} {1} ({2})".format(fname, lname, nname);
1313 }
1296 }
1314 var gravatarelm = gravatar(gravatar_link, gravatar_size, "");
1297 var gravatarelm = gravatar(gravatar_link, gravatar_size, "");
1315 // WARNING: the HTML below is duplicate with
1298 // WARNING: the HTML below is duplicate with
1316 // kallithea/templates/pullrequests/pullrequest_show.html
1299 // kallithea/templates/pullrequests/pullrequest_show.html
1317 // If you change something here it should be reflected in the template too.
1300 // If you change something here it should be reflected in the template too.
1318 var element = (
1301 var element = (
1319 ' <li id="reviewer_{2}">\n'+
1302 ' <li id="reviewer_{2}">\n'+
1320 ' <span class="reviewers_member">\n'+
1303 ' <span class="reviewers_member">\n'+
1321 ' <span class="reviewer_status" data-toggle="tooltip" title="not_reviewed">\n'+
1304 ' <span class="reviewer_status" data-toggle="tooltip" title="not_reviewed">\n'+
1322 ' <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
1305 ' <i class="icon-circle changeset-status-not_reviewed"></i>\n'+
1323 ' </span>\n'+
1306 ' </span>\n'+
1324 (gravatarelm ?
1307 (gravatarelm ?
1325 ' {0}\n' :
1308 ' {0}\n' :
1326 '')+
1309 '')+
1327 ' <span>{1}</span>\n'+
1310 ' <span>{1}</span>\n'+
1328 ' <input type="hidden" value="{2}" name="review_members" />\n'+
1311 ' <input type="hidden" value="{2}" name="review_members" />\n'+
1329 ' <a href="#" class="reviewer_member_remove" onclick="removeReviewMember({2})">\n'+
1312 ' <a href="#" class="reviewer_member_remove" onclick="removeReviewMember({2})">\n'+
1330 ' <i class="icon-minus-circled"></i>\n'+
1313 ' <i class="icon-minus-circled"></i>\n'+
1331 ' </a> (add not saved)\n'+
1314 ' </a> (add not saved)\n'+
1332 ' </span>\n'+
1315 ' </span>\n'+
1333 ' </li>\n'
1316 ' </li>\n'
1334 ).format(gravatarelm, displayname, id);
1317 ).format(gravatarelm, displayname, id);
1335 // check if we don't have this ID already in
1318 // check if we don't have this ID already in
1336 var ids = [];
1319 var ids = [];
1337 $('#review_members').find('li').each(function() {
1320 $('#review_members').find('li').each(function() {
1338 ids.push(this.id);
1321 ids.push(this.id);
1339 });
1322 });
1340 if(ids.indexOf('reviewer_'+id) == -1){
1323 if(ids.indexOf('reviewer_'+id) == -1){
1341 //only add if it's not there
1324 //only add if it's not there
1342 $('#review_members').append(element);
1325 $('#review_members').append(element);
1343 }
1326 }
1344 }
1327 }
1345
1328
1346 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1329 var removeReviewMember = function(reviewer_id, repo_name, pull_request_id){
1347 var $li = $('#reviewer_{0}'.format(reviewer_id));
1330 var $li = $('#reviewer_{0}'.format(reviewer_id));
1348 $li.find('div div').css("text-decoration", "line-through");
1331 $li.find('div div').css("text-decoration", "line-through");
1349 $li.find('input').prop('name', 'review_members_removed');
1332 $li.find('input').prop('name', 'review_members_removed');
1350 $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
1333 $li.find('.reviewer_member_remove').replaceWith('&nbsp;(remove not saved)');
1351 }
1334 }
1352
1335
1353 /* activate auto completion of users as PR reviewers */
1336 /* activate auto completion of users as PR reviewers */
1354 var PullRequestAutoComplete = function ($inputElement, users_list) {
1337 var PullRequestAutoComplete = function ($inputElement, users_list) {
1355 $inputElement.select2(
1338 $inputElement.select2(
1356 {
1339 {
1357 placeholder: $inputElement.attr('placeholder'),
1340 placeholder: $inputElement.attr('placeholder'),
1358 minimumInputLength: 1,
1341 minimumInputLength: 1,
1359 query: function (query) {
1342 query: function (query) {
1360 query.callback({results: autocompleteMatchUsers(query.term, users_list)});
1343 query.callback({results: autocompleteMatchUsers(query.term, users_list)});
1361 },
1344 },
1362 formatSelection: autocompleteFormatter,
1345 formatSelection: autocompleteFormatter,
1363 formatResult: autocompleteFormatter,
1346 formatResult: autocompleteFormatter,
1364 escapeMarkup: function(m) { return m; },
1347 escapeMarkup: function(m) { return m; },
1365 }).on("select2-selecting", function(e) {
1348 }).on("select2-selecting", function(e) {
1366 addReviewMember(e.choice.id, e.choice.fname, e.choice.lname, e.choice.nname,
1349 addReviewMember(e.choice.id, e.choice.fname, e.choice.lname, e.choice.nname,
1367 e.choice.gravatar_lnk, e.choice.gravatar_size);
1350 e.choice.gravatar_lnk, e.choice.gravatar_size);
1368 $inputElement.select2("close");
1351 $inputElement.select2("close");
1369 e.preventDefault();
1352 e.preventDefault();
1370 });
1353 });
1371 }
1354 }
1372
1355
1373
1356
1374 function addPermAction(perm_type, users_list, groups_list) {
1357 function addPermAction(perm_type, users_list, groups_list) {
1375 var template =
1358 var template =
1376 '<td><input type="radio" value="{1}.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1359 '<td><input type="radio" value="{1}.none" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1377 '<td><input type="radio" value="{1}.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1360 '<td><input type="radio" value="{1}.read" checked="checked" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1378 '<td><input type="radio" value="{1}.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1361 '<td><input type="radio" value="{1}.write" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1379 '<td><input type="radio" value="{1}.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1362 '<td><input type="radio" value="{1}.admin" name="perm_new_member_{0}" id="perm_new_member_{0}"></td>' +
1380 '<td>' +
1363 '<td>' +
1381 '<input class="form-control" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text" placeholder="{2}">' +
1364 '<input class="form-control" id="perm_new_member_name_{0}" name="perm_new_member_name_{0}" value="" type="text" placeholder="{2}">' +
1382 '<input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">' +
1365 '<input id="perm_new_member_type_{0}" name="perm_new_member_type_{0}" value="" type="hidden">' +
1383 '</td>' +
1366 '</td>' +
1384 '<td></td>';
1367 '<td></td>';
1385 var $last_node = $('.last_new_member').last(); // empty tr between last and add
1368 var $last_node = $('.last_new_member').last(); // empty tr between last and add
1386 var next_id = $('.new_members').length;
1369 var next_id = $('.new_members').length;
1387 $last_node.before($('<tr class="new_members">').append(template.format(next_id, perm_type, _TM['Type name of user or member to grant permission'])));
1370 $last_node.before($('<tr class="new_members">').append(template.format(next_id, perm_type, _TM['Type name of user or member to grant permission'])));
1388 MembersAutoComplete($("#perm_new_member_name_"+next_id), $("#perm_new_member_type_"+next_id), users_list, groups_list);
1371 MembersAutoComplete($("#perm_new_member_name_"+next_id), $("#perm_new_member_type_"+next_id), users_list, groups_list);
1389 }
1372 }
1390
1373
1391 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
1374 function ajaxActionRevokePermission(url, obj_id, obj_type, field_id, extra_data) {
1392 var success = function (o) {
1375 var success = function (o) {
1393 $('#' + field_id).remove();
1376 $('#' + field_id).remove();
1394 };
1377 };
1395 var failure = function (o) {
1378 var failure = function (o) {
1396 alert(_TM['Failed to revoke permission'] + ": " + o.status);
1379 alert(_TM['Failed to revoke permission'] + ": " + o.status);
1397 };
1380 };
1398 var query_params = {};
1381 var query_params = {};
1399 // put extra data into POST
1382 // put extra data into POST
1400 if (extra_data !== undefined && (typeof extra_data === 'object')){
1383 if (extra_data !== undefined && (typeof extra_data === 'object')){
1401 for(var k in extra_data){
1384 for(var k in extra_data){
1402 query_params[k] = extra_data[k];
1385 query_params[k] = extra_data[k];
1403 }
1386 }
1404 }
1387 }
1405
1388
1406 if (obj_type=='user'){
1389 if (obj_type=='user'){
1407 query_params['user_id'] = obj_id;
1390 query_params['user_id'] = obj_id;
1408 query_params['obj_type'] = 'user';
1391 query_params['obj_type'] = 'user';
1409 }
1392 }
1410 else if (obj_type=='user_group'){
1393 else if (obj_type=='user_group'){
1411 query_params['user_group_id'] = obj_id;
1394 query_params['user_group_id'] = obj_id;
1412 query_params['obj_type'] = 'user_group';
1395 query_params['obj_type'] = 'user_group';
1413 }
1396 }
1414
1397
1415 ajaxPOST(url, query_params, success, failure);
1398 ajaxPOST(url, query_params, success, failure);
1416 };
1399 };
1417
1400
1418 /* Multi selectors */
1401 /* Multi selectors */
1419
1402
1420 var MultiSelectWidget = function(selected_id, available_id, form_id){
1403 var MultiSelectWidget = function(selected_id, available_id, form_id){
1421 var $availableselect = $('#' + available_id);
1404 var $availableselect = $('#' + available_id);
1422 var $selectedselect = $('#' + selected_id);
1405 var $selectedselect = $('#' + selected_id);
1423
1406
1424 //fill available only with those not in selected
1407 //fill available only with those not in selected
1425 var $selectedoptions = $selectedselect.children('option');
1408 var $selectedoptions = $selectedselect.children('option');
1426 $availableselect.children('option').filter(function(i, e){
1409 $availableselect.children('option').filter(function(i, e){
1427 for(var j = 0, node; node = $selectedoptions[j]; j++){
1410 for(var j = 0, node; node = $selectedoptions[j]; j++){
1428 if(node.value == e.value){
1411 if(node.value == e.value){
1429 return true;
1412 return true;
1430 }
1413 }
1431 }
1414 }
1432 return false;
1415 return false;
1433 }).remove();
1416 }).remove();
1434
1417
1435 $('#add_element').click(function(e){
1418 $('#add_element').click(function(e){
1436 $selectedselect.append($availableselect.children('option:selected'));
1419 $selectedselect.append($availableselect.children('option:selected'));
1437 });
1420 });
1438 $('#remove_element').click(function(e){
1421 $('#remove_element').click(function(e){
1439 $availableselect.append($selectedselect.children('option:selected'));
1422 $availableselect.append($selectedselect.children('option:selected'));
1440 });
1423 });
1441
1424
1442 $('#'+form_id).submit(function(){
1425 $('#'+form_id).submit(function(){
1443 $selectedselect.children('option').each(function(i, e){
1426 $selectedselect.children('option').each(function(i, e){
1444 e.selected = 'selected';
1427 e.selected = 'selected';
1445 });
1428 });
1446 });
1429 });
1447 }
1430 }
1448
1431
1449
1432
1450 /**
1433 /**
1451 Branch Sorting callback for select2, modifying the filtered result so prefix
1434 Branch Sorting callback for select2, modifying the filtered result so prefix
1452 matches come before matches in the line.
1435 matches come before matches in the line.
1453 **/
1436 **/
1454 var branchSort = function(results, container, query) {
1437 var branchSort = function(results, container, query) {
1455 if (query.term) {
1438 if (query.term) {
1456 return results.sort(function (a, b) {
1439 return results.sort(function (a, b) {
1457 // Put closed branches after open ones (a bit of a hack ...)
1440 // Put closed branches after open ones (a bit of a hack ...)
1458 var aClosed = a.text.indexOf("(closed)") > -1,
1441 var aClosed = a.text.indexOf("(closed)") > -1,
1459 bClosed = b.text.indexOf("(closed)") > -1;
1442 bClosed = b.text.indexOf("(closed)") > -1;
1460 if (aClosed && !bClosed) {
1443 if (aClosed && !bClosed) {
1461 return 1;
1444 return 1;
1462 }
1445 }
1463 if (bClosed && !aClosed) {
1446 if (bClosed && !aClosed) {
1464 return -1;
1447 return -1;
1465 }
1448 }
1466
1449
1467 // Put early (especially prefix) matches before later matches
1450 // Put early (especially prefix) matches before later matches
1468 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1451 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1469 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1452 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1470 if (aPos < bPos) {
1453 if (aPos < bPos) {
1471 return -1;
1454 return -1;
1472 }
1455 }
1473 if (bPos < aPos) {
1456 if (bPos < aPos) {
1474 return 1;
1457 return 1;
1475 }
1458 }
1476
1459
1477 // Default sorting
1460 // Default sorting
1478 if (a.text > b.text) {
1461 if (a.text > b.text) {
1479 return 1;
1462 return 1;
1480 }
1463 }
1481 if (a.text < b.text) {
1464 if (a.text < b.text) {
1482 return -1;
1465 return -1;
1483 }
1466 }
1484 return 0;
1467 return 0;
1485 });
1468 });
1486 }
1469 }
1487 return results;
1470 return results;
1488 };
1471 };
1489
1472
1490 var prefixFirstSort = function(results, container, query) {
1473 var prefixFirstSort = function(results, container, query) {
1491 if (query.term) {
1474 if (query.term) {
1492 return results.sort(function (a, b) {
1475 return results.sort(function (a, b) {
1493 // if parent node, no sorting
1476 // if parent node, no sorting
1494 if (a.children != undefined || b.children != undefined) {
1477 if (a.children != undefined || b.children != undefined) {
1495 return 0;
1478 return 0;
1496 }
1479 }
1497
1480
1498 // Put prefix matches before matches in the line
1481 // Put prefix matches before matches in the line
1499 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1482 var aPos = a.text.toLowerCase().indexOf(query.term.toLowerCase()),
1500 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1483 bPos = b.text.toLowerCase().indexOf(query.term.toLowerCase());
1501 if (aPos === 0 && bPos !== 0) {
1484 if (aPos === 0 && bPos !== 0) {
1502 return -1;
1485 return -1;
1503 }
1486 }
1504 if (bPos === 0 && aPos !== 0) {
1487 if (bPos === 0 && aPos !== 0) {
1505 return 1;
1488 return 1;
1506 }
1489 }
1507
1490
1508 // Default sorting
1491 // Default sorting
1509 if (a.text > b.text) {
1492 if (a.text > b.text) {
1510 return 1;
1493 return 1;
1511 }
1494 }
1512 if (a.text < b.text) {
1495 if (a.text < b.text) {
1513 return -1;
1496 return -1;
1514 }
1497 }
1515 return 0;
1498 return 0;
1516 });
1499 });
1517 }
1500 }
1518 return results;
1501 return results;
1519 };
1502 };
1520
1503
1521 /* Helper for jQuery DataTables */
1504 /* Helper for jQuery DataTables */
1522
1505
1523 var updateRowCountCallback = function updateRowCountCallback($elem, onlyDisplayed) {
1506 var updateRowCountCallback = function updateRowCountCallback($elem, onlyDisplayed) {
1524 return function drawCallback() {
1507 return function drawCallback() {
1525 var info = this.api().page.info(),
1508 var info = this.api().page.info(),
1526 count = onlyDisplayed === true ? info.recordsDisplay : info.recordsTotal;
1509 count = onlyDisplayed === true ? info.recordsDisplay : info.recordsTotal;
1527 $elem.html(count);
1510 $elem.html(count);
1528 }
1511 }
1529 };
1512 };
General Comments 0
You need to be logged in to leave comments. Login now