##// END OF EJS Templates
quick-search: added ability to search for pull-requests using `pr:` prefix....
marcink -
r4329:4543620f default
parent child Browse files
Show More
@@ -1,830 +1,896 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound
26 26 from pyramid.view import view_config
27 27
28 28 from rhodecode.apps._base import BaseAppView, DataGridAppView
29 29 from rhodecode.lib import helpers as h
30 30 from rhodecode.lib.auth import (
31 31 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator, CSRFRequired,
32 32 HasRepoGroupPermissionAny, AuthUser)
33 33 from rhodecode.lib.codeblocks import filenode_as_lines_tokens
34 34 from rhodecode.lib.index import searcher_from_config
35 35 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
36 36 from rhodecode.lib.vcs.nodes import FileNode
37 37 from rhodecode.model.db import (
38 func, true, or_, case, in_filter_generator, Session,
39 Repository, RepoGroup, User, UserGroup)
38 func, true, or_, case, cast, in_filter_generator, String, Session,
39 Repository, RepoGroup, User, UserGroup, PullRequest)
40 40 from rhodecode.model.repo import RepoModel
41 41 from rhodecode.model.repo_group import RepoGroupModel
42 42 from rhodecode.model.user import UserModel
43 43 from rhodecode.model.user_group import UserGroupModel
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class HomeView(BaseAppView, DataGridAppView):
49 49
50 50 def load_default_context(self):
51 51 c = self._get_local_tmpl_context()
52 52 c.user = c.auth_user.get_instance()
53 53
54 54 return c
55 55
56 56 @LoginRequired()
57 57 @view_config(
58 58 route_name='user_autocomplete_data', request_method='GET',
59 59 renderer='json_ext', xhr=True)
60 60 def user_autocomplete_data(self):
61 61 self.load_default_context()
62 62 query = self.request.GET.get('query')
63 63 active = str2bool(self.request.GET.get('active') or True)
64 64 include_groups = str2bool(self.request.GET.get('user_groups'))
65 65 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
66 66 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
67 67
68 68 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
69 69 query, active, include_groups)
70 70
71 71 _users = UserModel().get_users(
72 72 name_contains=query, only_active=active)
73 73
74 74 def maybe_skip_default_user(usr):
75 75 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
76 76 return False
77 77 return True
78 78 _users = filter(maybe_skip_default_user, _users)
79 79
80 80 if include_groups:
81 81 # extend with user groups
82 82 _user_groups = UserGroupModel().get_user_groups(
83 83 name_contains=query, only_active=active,
84 84 expand_groups=expand_groups)
85 85 _users = _users + _user_groups
86 86
87 87 return {'suggestions': _users}
88 88
89 89 @LoginRequired()
90 90 @NotAnonymous()
91 91 @view_config(
92 92 route_name='user_group_autocomplete_data', request_method='GET',
93 93 renderer='json_ext', xhr=True)
94 94 def user_group_autocomplete_data(self):
95 95 self.load_default_context()
96 96 query = self.request.GET.get('query')
97 97 active = str2bool(self.request.GET.get('active') or True)
98 98 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
99 99
100 100 log.debug('generating user group list, query:%s, active:%s',
101 101 query, active)
102 102
103 103 _user_groups = UserGroupModel().get_user_groups(
104 104 name_contains=query, only_active=active,
105 105 expand_groups=expand_groups)
106 106 _user_groups = _user_groups
107 107
108 108 return {'suggestions': _user_groups}
109 109
110 110 def _get_repo_list(self, name_contains=None, repo_type=None, repo_group_name='', limit=20):
111 111 org_query = name_contains
112 112 allowed_ids = self._rhodecode_user.repo_acl_ids(
113 113 ['repository.read', 'repository.write', 'repository.admin'],
114 cache=False, name_filter=name_contains) or [-1]
114 cache=True, name_filter=name_contains) or [-1]
115 115
116 116 query = Session().query(
117 117 Repository.repo_name,
118 118 Repository.repo_id,
119 119 Repository.repo_type,
120 120 Repository.private,
121 121 )\
122 122 .filter(Repository.archived.isnot(true()))\
123 123 .filter(or_(
124 124 # generate multiple IN to fix limitation problems
125 125 *in_filter_generator(Repository.repo_id, allowed_ids)
126 126 ))
127 127
128 128 query = query.order_by(case(
129 129 [
130 130 (Repository.repo_name.startswith(repo_group_name), repo_group_name+'/'),
131 131 ],
132 132 ))
133 133 query = query.order_by(func.length(Repository.repo_name))
134 134 query = query.order_by(Repository.repo_name)
135 135
136 136 if repo_type:
137 137 query = query.filter(Repository.repo_type == repo_type)
138 138
139 139 if name_contains:
140 140 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
141 141 query = query.filter(
142 142 Repository.repo_name.ilike(ilike_expression))
143 143 query = query.limit(limit)
144 144
145 145 acl_iter = query
146 146
147 147 return [
148 148 {
149 149 'id': obj.repo_name,
150 150 'value': org_query,
151 151 'value_display': obj.repo_name,
152 152 'text': obj.repo_name,
153 153 'type': 'repo',
154 154 'repo_id': obj.repo_id,
155 155 'repo_type': obj.repo_type,
156 156 'private': obj.private,
157 157 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
158 158 }
159 159 for obj in acl_iter]
160 160
161 161 def _get_repo_group_list(self, name_contains=None, repo_group_name='', limit=20):
162 162 org_query = name_contains
163 163 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
164 164 ['group.read', 'group.write', 'group.admin'],
165 cache=False, name_filter=name_contains) or [-1]
165 cache=True, name_filter=name_contains) or [-1]
166 166
167 167 query = Session().query(
168 168 RepoGroup.group_id,
169 169 RepoGroup.group_name,
170 170 )\
171 171 .filter(or_(
172 172 # generate multiple IN to fix limitation problems
173 173 *in_filter_generator(RepoGroup.group_id, allowed_ids)
174 174 ))
175 175
176 176 query = query.order_by(case(
177 177 [
178 178 (RepoGroup.group_name.startswith(repo_group_name), repo_group_name+'/'),
179 179 ],
180 180 ))
181 181 query = query.order_by(func.length(RepoGroup.group_name))
182 182 query = query.order_by(RepoGroup.group_name)
183 183
184 184 if name_contains:
185 185 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
186 186 query = query.filter(
187 187 RepoGroup.group_name.ilike(ilike_expression))
188 188 query = query.limit(limit)
189 189
190 190 acl_iter = query
191 191
192 192 return [
193 193 {
194 194 'id': obj.group_name,
195 195 'value': org_query,
196 196 'value_display': obj.group_name,
197 197 'text': obj.group_name,
198 198 'type': 'repo_group',
199 199 'repo_group_id': obj.group_id,
200 200 'url': h.route_path(
201 201 'repo_group_home', repo_group_name=obj.group_name)
202 202 }
203 203 for obj in acl_iter]
204 204
205 205 def _get_user_list(self, name_contains=None, limit=20):
206 206 org_query = name_contains
207 207 if not name_contains:
208 208 return [], False
209 209
210 210 # TODO(marcink): should all logged in users be allowed to search others?
211 211 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
212 212 if not allowed_user_search:
213 213 return [], False
214 214
215 215 name_contains = re.compile('(?:user:[ ]?)(.+)').findall(name_contains)
216 216 if len(name_contains) != 1:
217 217 return [], False
218 218
219 219 name_contains = name_contains[0]
220 220
221 221 query = User.query()\
222 222 .order_by(func.length(User.username))\
223 223 .order_by(User.username) \
224 224 .filter(User.username != User.DEFAULT_USER)
225 225
226 226 if name_contains:
227 227 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
228 228 query = query.filter(
229 229 User.username.ilike(ilike_expression))
230 230 query = query.limit(limit)
231 231
232 232 acl_iter = query
233 233
234 234 return [
235 235 {
236 236 'id': obj.user_id,
237 237 'value': org_query,
238 238 'value_display': 'user: `{}`'.format(obj.username),
239 239 'type': 'user',
240 240 'icon_link': h.gravatar_url(obj.email, 30),
241 241 'url': h.route_path(
242 242 'user_profile', username=obj.username)
243 243 }
244 244 for obj in acl_iter], True
245 245
246 246 def _get_user_groups_list(self, name_contains=None, limit=20):
247 247 org_query = name_contains
248 248 if not name_contains:
249 249 return [], False
250 250
251 251 # TODO(marcink): should all logged in users be allowed to search others?
252 252 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
253 253 if not allowed_user_search:
254 254 return [], False
255 255
256 256 name_contains = re.compile('(?:user_group:[ ]?)(.+)').findall(name_contains)
257 257 if len(name_contains) != 1:
258 258 return [], False
259 259
260 260 name_contains = name_contains[0]
261 261
262 262 query = UserGroup.query()\
263 263 .order_by(func.length(UserGroup.users_group_name))\
264 264 .order_by(UserGroup.users_group_name)
265 265
266 266 if name_contains:
267 267 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
268 268 query = query.filter(
269 269 UserGroup.users_group_name.ilike(ilike_expression))
270 270 query = query.limit(limit)
271 271
272 272 acl_iter = query
273 273
274 274 return [
275 275 {
276 276 'id': obj.users_group_id,
277 277 'value': org_query,
278 278 'value_display': 'user_group: `{}`'.format(obj.users_group_name),
279 279 'type': 'user_group',
280 280 'url': h.route_path(
281 281 'user_group_profile', user_group_name=obj.users_group_name)
282 282 }
283 283 for obj in acl_iter], True
284 284
285 def _get_pull_request_list(self, name_contains=None, limit=20):
286 org_query = name_contains
287 if not name_contains:
288 return [], False
289
290 # TODO(marcink): should all logged in users be allowed to search others?
291 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
292 if not allowed_user_search:
293 return [], False
294
295 name_contains = re.compile('(?:pr:[ ]?)(.+)').findall(name_contains)
296 if len(name_contains) != 1:
297 return [], False
298
299 name_contains = name_contains[0]
300
301 allowed_ids = self._rhodecode_user.repo_acl_ids(
302 ['repository.read', 'repository.write', 'repository.admin'],
303 cache=True) or [-1]
304
305 query = Session().query(
306 PullRequest.pull_request_id,
307 PullRequest.title,
308 )
309 query = query.join(Repository, Repository.repo_id == PullRequest.target_repo_id)
310
311 query = query.filter(or_(
312 # generate multiple IN to fix limitation problems
313 *in_filter_generator(Repository.repo_id, allowed_ids)
314 ))
315
316 query = query.order_by(PullRequest.pull_request_id)
317
318 if name_contains:
319 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
320 query = query.filter(or_(
321 cast(PullRequest.pull_request_id, String).ilike(ilike_expression),
322 PullRequest.title.ilike(ilike_expression),
323 PullRequest.description.ilike(ilike_expression),
324 ))
325
326 query = query.limit(limit)
327
328 acl_iter = query
329
330 return [
331 {
332 'id': obj.pull_request_id,
333 'value': org_query,
334 'value_display': 'pull request: `!{} - {}`'.format(obj.pull_request_id, obj.title[:50]),
335 'type': 'pull_request',
336 'url': h.route_path('pull_requests_global', pull_request_id=obj.pull_request_id)
337 }
338 for obj in acl_iter], True
339
285 340 def _get_hash_commit_list(self, auth_user, searcher, query, repo=None, repo_group=None):
286 341 repo_name = repo_group_name = None
287 342 if repo:
288 343 repo_name = repo.repo_name
289 344 if repo_group:
290 345 repo_group_name = repo_group.group_name
291 346
292 347 org_query = query
293 348 if not query or len(query) < 3 or not searcher:
294 349 return [], False
295 350
296 351 commit_hashes = re.compile('(?:commit:[ ]?)([0-9a-f]{2,40})').findall(query)
297 352
298 353 if len(commit_hashes) != 1:
299 354 return [], False
300 355
301 356 commit_hash = commit_hashes[0]
302 357
303 358 result = searcher.search(
304 359 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
305 360 repo_name, repo_group_name, raise_on_exc=False)
306 361
307 362 commits = []
308 363 for entry in result['results']:
309 364 repo_data = {
310 365 'repository_id': entry.get('repository_id'),
311 366 'repository_type': entry.get('repo_type'),
312 367 'repository_name': entry.get('repository'),
313 368 }
314 369
315 370 commit_entry = {
316 371 'id': entry['commit_id'],
317 372 'value': org_query,
318 373 'value_display': '`{}` commit: {}'.format(
319 374 entry['repository'], entry['commit_id']),
320 375 'type': 'commit',
321 376 'repo': entry['repository'],
322 377 'repo_data': repo_data,
323 378
324 379 'url': h.route_path(
325 380 'repo_commit',
326 381 repo_name=entry['repository'], commit_id=entry['commit_id'])
327 382 }
328 383
329 384 commits.append(commit_entry)
330 385 return commits, True
331 386
332 387 def _get_path_list(self, auth_user, searcher, query, repo=None, repo_group=None):
333 388 repo_name = repo_group_name = None
334 389 if repo:
335 390 repo_name = repo.repo_name
336 391 if repo_group:
337 392 repo_group_name = repo_group.group_name
338 393
339 394 org_query = query
340 395 if not query or len(query) < 3 or not searcher:
341 396 return [], False
342 397
343 398 paths_re = re.compile('(?:file:[ ]?)(.+)').findall(query)
344 399 if len(paths_re) != 1:
345 400 return [], False
346 401
347 402 file_path = paths_re[0]
348 403
349 404 search_path = searcher.escape_specials(file_path)
350 405 result = searcher.search(
351 406 'file.raw:*{}*'.format(search_path), 'path', auth_user,
352 407 repo_name, repo_group_name, raise_on_exc=False)
353 408
354 409 files = []
355 410 for entry in result['results']:
356 411 repo_data = {
357 412 'repository_id': entry.get('repository_id'),
358 413 'repository_type': entry.get('repo_type'),
359 414 'repository_name': entry.get('repository'),
360 415 }
361 416
362 417 file_entry = {
363 418 'id': entry['commit_id'],
364 419 'value': org_query,
365 420 'value_display': '`{}` file: {}'.format(
366 421 entry['repository'], entry['file']),
367 422 'type': 'file',
368 423 'repo': entry['repository'],
369 424 'repo_data': repo_data,
370 425
371 426 'url': h.route_path(
372 427 'repo_files',
373 428 repo_name=entry['repository'], commit_id=entry['commit_id'],
374 429 f_path=entry['file'])
375 430 }
376 431
377 432 files.append(file_entry)
378 433 return files, True
379 434
380 435 @LoginRequired()
381 436 @view_config(
382 437 route_name='repo_list_data', request_method='GET',
383 438 renderer='json_ext', xhr=True)
384 439 def repo_list_data(self):
385 440 _ = self.request.translate
386 441 self.load_default_context()
387 442
388 443 query = self.request.GET.get('query')
389 444 repo_type = self.request.GET.get('repo_type')
390 445 log.debug('generating repo list, query:%s, repo_type:%s',
391 446 query, repo_type)
392 447
393 448 res = []
394 449 repos = self._get_repo_list(query, repo_type=repo_type)
395 450 if repos:
396 451 res.append({
397 452 'text': _('Repositories'),
398 453 'children': repos
399 454 })
400 455
401 456 data = {
402 457 'more': False,
403 458 'results': res
404 459 }
405 460 return data
406 461
407 462 @LoginRequired()
408 463 @view_config(
409 464 route_name='repo_group_list_data', request_method='GET',
410 465 renderer='json_ext', xhr=True)
411 466 def repo_group_list_data(self):
412 467 _ = self.request.translate
413 468 self.load_default_context()
414 469
415 470 query = self.request.GET.get('query')
416 471
417 472 log.debug('generating repo group list, query:%s',
418 473 query)
419 474
420 475 res = []
421 476 repo_groups = self._get_repo_group_list(query)
422 477 if repo_groups:
423 478 res.append({
424 479 'text': _('Repository Groups'),
425 480 'children': repo_groups
426 481 })
427 482
428 483 data = {
429 484 'more': False,
430 485 'results': res
431 486 }
432 487 return data
433 488
434 489 def _get_default_search_queries(self, search_context, searcher, query):
435 490 if not searcher:
436 491 return []
437 492
438 493 is_es_6 = searcher.is_es_6
439 494
440 495 queries = []
441 496 repo_group_name, repo_name, repo_context = None, None, None
442 497
443 498 # repo group context
444 499 if search_context.get('search_context[repo_group_name]'):
445 500 repo_group_name = search_context.get('search_context[repo_group_name]')
446 501 if search_context.get('search_context[repo_name]'):
447 502 repo_name = search_context.get('search_context[repo_name]')
448 503 repo_context = search_context.get('search_context[repo_view_type]')
449 504
450 505 if is_es_6 and repo_name:
451 506 # files
452 507 def query_modifier():
453 508 qry = query
454 509 return {'q': qry, 'type': 'content'}
455 510
456 511 label = u'File content search for `{}`'.format(h.escape(query))
457 512 file_qry = {
458 513 'id': -10,
459 514 'value': query,
460 515 'value_display': label,
461 516 'value_icon': '<i class="icon-code"></i>',
462 517 'type': 'search',
463 518 'subtype': 'repo',
464 519 'url': h.route_path('search_repo',
465 520 repo_name=repo_name,
466 521 _query=query_modifier())
467 522 }
468 523
469 524 # commits
470 525 def query_modifier():
471 526 qry = query
472 527 return {'q': qry, 'type': 'commit'}
473 528
474 529 label = u'Commit search for `{}`'.format(h.escape(query))
475 530 commit_qry = {
476 531 'id': -20,
477 532 'value': query,
478 533 'value_display': label,
479 534 'value_icon': '<i class="icon-history"></i>',
480 535 'type': 'search',
481 536 'subtype': 'repo',
482 537 'url': h.route_path('search_repo',
483 538 repo_name=repo_name,
484 539 _query=query_modifier())
485 540 }
486 541
487 542 if repo_context in ['commit', 'commits']:
488 543 queries.extend([commit_qry, file_qry])
489 544 elif repo_context in ['files', 'summary']:
490 545 queries.extend([file_qry, commit_qry])
491 546 else:
492 547 queries.extend([commit_qry, file_qry])
493 548
494 549 elif is_es_6 and repo_group_name:
495 550 # files
496 551 def query_modifier():
497 552 qry = query
498 553 return {'q': qry, 'type': 'content'}
499 554
500 555 label = u'File content search for `{}`'.format(query)
501 556 file_qry = {
502 557 'id': -30,
503 558 'value': query,
504 559 'value_display': label,
505 560 'value_icon': '<i class="icon-code"></i>',
506 561 'type': 'search',
507 562 'subtype': 'repo_group',
508 563 'url': h.route_path('search_repo_group',
509 564 repo_group_name=repo_group_name,
510 565 _query=query_modifier())
511 566 }
512 567
513 568 # commits
514 569 def query_modifier():
515 570 qry = query
516 571 return {'q': qry, 'type': 'commit'}
517 572
518 573 label = u'Commit search for `{}`'.format(query)
519 574 commit_qry = {
520 575 'id': -40,
521 576 'value': query,
522 577 'value_display': label,
523 578 'value_icon': '<i class="icon-history"></i>',
524 579 'type': 'search',
525 580 'subtype': 'repo_group',
526 581 'url': h.route_path('search_repo_group',
527 582 repo_group_name=repo_group_name,
528 583 _query=query_modifier())
529 584 }
530 585
531 586 if repo_context in ['commit', 'commits']:
532 587 queries.extend([commit_qry, file_qry])
533 588 elif repo_context in ['files', 'summary']:
534 589 queries.extend([file_qry, commit_qry])
535 590 else:
536 591 queries.extend([commit_qry, file_qry])
537 592
538 593 # Global, not scoped
539 594 if not queries:
540 595 queries.append(
541 596 {
542 597 'id': -1,
543 598 'value': query,
544 599 'value_display': u'File content search for: `{}`'.format(query),
545 600 'value_icon': '<i class="icon-code"></i>',
546 601 'type': 'search',
547 602 'subtype': 'global',
548 603 'url': h.route_path('search',
549 604 _query={'q': query, 'type': 'content'})
550 605 })
551 606 queries.append(
552 607 {
553 608 'id': -2,
554 609 'value': query,
555 610 'value_display': u'Commit search for: `{}`'.format(query),
556 611 'value_icon': '<i class="icon-history"></i>',
557 612 'type': 'search',
558 613 'subtype': 'global',
559 614 'url': h.route_path('search',
560 615 _query={'q': query, 'type': 'commit'})
561 616 })
562 617
563 618 return queries
564 619
565 620 @LoginRequired()
566 621 @view_config(
567 622 route_name='goto_switcher_data', request_method='GET',
568 623 renderer='json_ext', xhr=True)
569 624 def goto_switcher_data(self):
570 625 c = self.load_default_context()
571 626
572 627 _ = self.request.translate
573 628
574 629 query = self.request.GET.get('query')
575 630 log.debug('generating main filter data, query %s', query)
576 631
577 632 res = []
578 633 if not query:
579 634 return {'suggestions': res}
580 635
581 636 def no_match(name):
582 637 return {
583 638 'id': -1,
584 639 'value': "",
585 640 'value_display': name,
586 641 'type': 'text',
587 642 'url': ""
588 643 }
589 644 searcher = searcher_from_config(self.request.registry.settings)
590 645 has_specialized_search = False
591 646
592 647 # set repo context
593 648 repo = None
594 649 repo_id = safe_int(self.request.GET.get('search_context[repo_id]'))
595 650 if repo_id:
596 651 repo = Repository.get(repo_id)
597 652
598 653 # set group context
599 654 repo_group = None
600 655 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
601 656 if repo_group_id:
602 657 repo_group = RepoGroup.get(repo_group_id)
603 658 prefix_match = False
604 659
605 660 # user: type search
606 661 if not prefix_match:
607 662 users, prefix_match = self._get_user_list(query)
608 663 if users:
609 664 has_specialized_search = True
610 665 for serialized_user in users:
611 666 res.append(serialized_user)
612 667 elif prefix_match:
613 668 has_specialized_search = True
614 669 res.append(no_match('No matching users found'))
615 670
616 671 # user_group: type search
617 672 if not prefix_match:
618 673 user_groups, prefix_match = self._get_user_groups_list(query)
619 674 if user_groups:
620 675 has_specialized_search = True
621 676 for serialized_user_group in user_groups:
622 677 res.append(serialized_user_group)
623 678 elif prefix_match:
624 679 has_specialized_search = True
625 680 res.append(no_match('No matching user groups found'))
626 681
682 # pr: type search
683 if not prefix_match:
684 pull_requests, prefix_match = self._get_pull_request_list(query)
685 if pull_requests:
686 has_specialized_search = True
687 for serialized_pull_request in pull_requests:
688 res.append(serialized_pull_request)
689 elif prefix_match:
690 has_specialized_search = True
691 res.append(no_match('No matching pull requests found'))
692
627 693 # FTS commit: type search
628 694 if not prefix_match:
629 695 commits, prefix_match = self._get_hash_commit_list(
630 696 c.auth_user, searcher, query, repo, repo_group)
631 697 if commits:
632 698 has_specialized_search = True
633 699 unique_repos = collections.OrderedDict()
634 700 for commit in commits:
635 701 repo_name = commit['repo']
636 702 unique_repos.setdefault(repo_name, []).append(commit)
637 703
638 704 for _repo, commits in unique_repos.items():
639 705 for commit in commits:
640 706 res.append(commit)
641 707 elif prefix_match:
642 708 has_specialized_search = True
643 709 res.append(no_match('No matching commits found'))
644 710
645 711 # FTS file: type search
646 712 if not prefix_match:
647 713 paths, prefix_match = self._get_path_list(
648 714 c.auth_user, searcher, query, repo, repo_group)
649 715 if paths:
650 716 has_specialized_search = True
651 717 unique_repos = collections.OrderedDict()
652 718 for path in paths:
653 719 repo_name = path['repo']
654 720 unique_repos.setdefault(repo_name, []).append(path)
655 721
656 722 for repo, paths in unique_repos.items():
657 723 for path in paths:
658 724 res.append(path)
659 725 elif prefix_match:
660 726 has_specialized_search = True
661 727 res.append(no_match('No matching files found'))
662 728
663 729 # main suggestions
664 730 if not has_specialized_search:
665 731 repo_group_name = ''
666 732 if repo_group:
667 733 repo_group_name = repo_group.group_name
668 734
669 735 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
670 736 res.append(_q)
671 737
672 738 repo_groups = self._get_repo_group_list(query, repo_group_name=repo_group_name)
673 739 for serialized_repo_group in repo_groups:
674 740 res.append(serialized_repo_group)
675 741
676 742 repos = self._get_repo_list(query, repo_group_name=repo_group_name)
677 743 for serialized_repo in repos:
678 744 res.append(serialized_repo)
679 745
680 746 if not repos and not repo_groups:
681 747 res.append(no_match('No matches found'))
682 748
683 749 return {'suggestions': res}
684 750
685 751 @LoginRequired()
686 752 @view_config(
687 753 route_name='home', request_method='GET',
688 754 renderer='rhodecode:templates/index.mako')
689 755 def main_page(self):
690 756 c = self.load_default_context()
691 757 c.repo_group = None
692 758 return self._get_template_context(c)
693 759
694 760 def _main_page_repo_groups_data(self, repo_group_id):
695 761 column_map = {
696 762 'name': 'group_name_hash',
697 763 'desc': 'group_description',
698 764 'last_change': 'updated_on',
699 765 'owner': 'user_username',
700 766 }
701 767 draw, start, limit = self._extract_chunk(self.request)
702 768 search_q, order_by, order_dir = self._extract_ordering(
703 769 self.request, column_map=column_map)
704 770 return RepoGroupModel().get_repo_groups_data_table(
705 771 draw, start, limit,
706 772 search_q, order_by, order_dir,
707 773 self._rhodecode_user, repo_group_id)
708 774
709 775 def _main_page_repos_data(self, repo_group_id):
710 776 column_map = {
711 777 'name': 'repo_name',
712 778 'desc': 'description',
713 779 'last_change': 'updated_on',
714 780 'owner': 'user_username',
715 781 }
716 782 draw, start, limit = self._extract_chunk(self.request)
717 783 search_q, order_by, order_dir = self._extract_ordering(
718 784 self.request, column_map=column_map)
719 785 return RepoModel().get_repos_data_table(
720 786 draw, start, limit,
721 787 search_q, order_by, order_dir,
722 788 self._rhodecode_user, repo_group_id)
723 789
724 790 @LoginRequired()
725 791 @view_config(
726 792 route_name='main_page_repo_groups_data',
727 793 request_method='GET', renderer='json_ext', xhr=True)
728 794 def main_page_repo_groups_data(self):
729 795 self.load_default_context()
730 796 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
731 797
732 798 if repo_group_id:
733 799 group = RepoGroup.get_or_404(repo_group_id)
734 800 _perms = AuthUser.repo_group_read_perms
735 801 if not HasRepoGroupPermissionAny(*_perms)(
736 802 group.group_name, 'user is allowed to list repo group children'):
737 803 raise HTTPNotFound()
738 804
739 805 return self._main_page_repo_groups_data(repo_group_id)
740 806
741 807 @LoginRequired()
742 808 @view_config(
743 809 route_name='main_page_repos_data',
744 810 request_method='GET', renderer='json_ext', xhr=True)
745 811 def main_page_repos_data(self):
746 812 self.load_default_context()
747 813 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
748 814
749 815 if repo_group_id:
750 816 group = RepoGroup.get_or_404(repo_group_id)
751 817 _perms = AuthUser.repo_group_read_perms
752 818 if not HasRepoGroupPermissionAny(*_perms)(
753 819 group.group_name, 'user is allowed to list repo group children'):
754 820 raise HTTPNotFound()
755 821
756 822 return self._main_page_repos_data(repo_group_id)
757 823
758 824 @LoginRequired()
759 825 @HasRepoGroupPermissionAnyDecorator(*AuthUser.repo_group_read_perms)
760 826 @view_config(
761 827 route_name='repo_group_home', request_method='GET',
762 828 renderer='rhodecode:templates/index_repo_group.mako')
763 829 @view_config(
764 830 route_name='repo_group_home_slash', request_method='GET',
765 831 renderer='rhodecode:templates/index_repo_group.mako')
766 832 def repo_group_main_page(self):
767 833 c = self.load_default_context()
768 834 c.repo_group = self.request.db_repo_group
769 835 return self._get_template_context(c)
770 836
771 837 @LoginRequired()
772 838 @CSRFRequired()
773 839 @view_config(
774 840 route_name='markup_preview', request_method='POST',
775 841 renderer='string', xhr=True)
776 842 def markup_preview(self):
777 843 # Technically a CSRF token is not needed as no state changes with this
778 844 # call. However, as this is a POST is better to have it, so automated
779 845 # tools don't flag it as potential CSRF.
780 846 # Post is required because the payload could be bigger than the maximum
781 847 # allowed by GET.
782 848
783 849 text = self.request.POST.get('text')
784 850 renderer = self.request.POST.get('renderer') or 'rst'
785 851 if text:
786 852 return h.render(text, renderer=renderer, mentions=True)
787 853 return ''
788 854
789 855 @LoginRequired()
790 856 @CSRFRequired()
791 857 @view_config(
792 858 route_name='file_preview', request_method='POST',
793 859 renderer='string', xhr=True)
794 860 def file_preview(self):
795 861 # Technically a CSRF token is not needed as no state changes with this
796 862 # call. However, as this is a POST is better to have it, so automated
797 863 # tools don't flag it as potential CSRF.
798 864 # Post is required because the payload could be bigger than the maximum
799 865 # allowed by GET.
800 866
801 867 text = self.request.POST.get('text')
802 868 file_path = self.request.POST.get('file_path')
803 869
804 870 renderer = h.renderer_from_filename(file_path)
805 871
806 872 if renderer:
807 873 return h.render(text, renderer=renderer, mentions=True)
808 874 else:
809 875 self.load_default_context()
810 876 _render = self.request.get_partial_renderer(
811 877 'rhodecode:templates/files/file_content.mako')
812 878
813 879 lines = filenode_as_lines_tokens(FileNode(file_path, text))
814 880
815 881 return _render('render_lines', lines)
816 882
817 883 @LoginRequired()
818 884 @CSRFRequired()
819 885 @view_config(
820 886 route_name='store_user_session_value', request_method='POST',
821 887 renderer='string', xhr=True)
822 888 def store_user_session_attr(self):
823 889 key = self.request.POST.get('key')
824 890 val = self.request.POST.get('val')
825 891
826 892 existing_value = self.request.session.get(key)
827 893 if existing_value != val:
828 894 self.request.session[key] = val
829 895
830 896 return 'stored:{}:{}'.format(key, val)
@@ -1,1189 +1,1195 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%!
4 4 ## base64 filter e.g ${ example | base64 }
5 5 def base64(text):
6 6 import base64
7 7 from rhodecode.lib.helpers import safe_str
8 8 return base64.encodestring(safe_str(text))
9 9 %>
10 10
11 11 <%inherit file="root.mako"/>
12 12
13 13 <%include file="/ejs_templates/templates.html"/>
14 14
15 15 <div class="outerwrapper">
16 16 <!-- HEADER -->
17 17 <div class="header">
18 18 <div id="header-inner" class="wrapper">
19 19 <div id="logo">
20 20 <div class="logo-wrapper">
21 21 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
22 22 </div>
23 23 % if c.rhodecode_name:
24 24 <div class="branding">
25 25 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
26 26 </div>
27 27 % endif
28 28 </div>
29 29 <!-- MENU BAR NAV -->
30 30 ${self.menu_bar_nav()}
31 31 <!-- END MENU BAR NAV -->
32 32 </div>
33 33 </div>
34 34 ${self.menu_bar_subnav()}
35 35 <!-- END HEADER -->
36 36
37 37 <!-- CONTENT -->
38 38 <div id="content" class="wrapper">
39 39
40 40 <rhodecode-toast id="notifications"></rhodecode-toast>
41 41
42 42 <div class="main">
43 43 ${next.main()}
44 44 </div>
45 45 </div>
46 46 <!-- END CONTENT -->
47 47
48 48 </div>
49 49 <!-- FOOTER -->
50 50 <div id="footer">
51 51 <div id="footer-inner" class="title wrapper">
52 52 <div>
53 53 <p class="footer-link-right">
54 54 % if c.visual.show_version:
55 55 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
56 56 % endif
57 57 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
58 58 % if c.visual.rhodecode_support_url:
59 59 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
60 60 % endif
61 61 </p>
62 62 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
63 63 <p class="server-instance" style="display:${sid}">
64 64 ## display hidden instance ID if specially defined
65 65 % if c.rhodecode_instanceid:
66 66 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
67 67 % endif
68 68 </p>
69 69 </div>
70 70 </div>
71 71 </div>
72 72
73 73 <!-- END FOOTER -->
74 74
75 75 ### MAKO DEFS ###
76 76
77 77 <%def name="menu_bar_subnav()">
78 78 </%def>
79 79
80 80 <%def name="breadcrumbs(class_='breadcrumbs')">
81 81 <div class="${class_}">
82 82 ${self.breadcrumbs_links()}
83 83 </div>
84 84 </%def>
85 85
86 86 <%def name="admin_menu(active=None)">
87 87
88 88 <div id="context-bar">
89 89 <div class="wrapper">
90 90 <div class="title">
91 91 <div class="title-content">
92 92 <div class="title-main">
93 93 % if c.is_super_admin:
94 94 ${_('Super-admin Panel')}
95 95 % else:
96 96 ${_('Delegated Admin Panel')}
97 97 % endif
98 98 </div>
99 99 </div>
100 100 </div>
101 101
102 102 <ul id="context-pages" class="navigation horizontal-list">
103 103
104 104 ## super-admin case
105 105 % if c.is_super_admin:
106 106 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
107 107 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
108 108 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
109 109 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
110 110 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
111 111 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
112 112 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
113 113 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
114 114 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
115 115 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
116 116
117 117 ## delegated admin
118 118 % elif c.is_delegated_admin:
119 119 <%
120 120 repositories=c.auth_user.repositories_admin or c.can_create_repo
121 121 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
122 122 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
123 123 %>
124 124
125 125 %if repositories:
126 126 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
127 127 %endif
128 128 %if repository_groups:
129 129 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
130 130 %endif
131 131 %if user_groups:
132 132 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
133 133 %endif
134 134 % endif
135 135 </ul>
136 136
137 137 </div>
138 138 <div class="clear"></div>
139 139 </div>
140 140 </%def>
141 141
142 142 <%def name="dt_info_panel(elements)">
143 143 <dl class="dl-horizontal">
144 144 %for dt, dd, title, show_items in elements:
145 145 <dt>${dt}:</dt>
146 146 <dd title="${h.tooltip(title)}">
147 147 %if callable(dd):
148 148 ## allow lazy evaluation of elements
149 149 ${dd()}
150 150 %else:
151 151 ${dd}
152 152 %endif
153 153 %if show_items:
154 154 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
155 155 %endif
156 156 </dd>
157 157
158 158 %if show_items:
159 159 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
160 160 %for item in show_items:
161 161 <dt></dt>
162 162 <dd>${item}</dd>
163 163 %endfor
164 164 </div>
165 165 %endif
166 166
167 167 %endfor
168 168 </dl>
169 169 </%def>
170 170
171 171 <%def name="tr_info_entry(element)">
172 172 <% key, val, title, show_items = element %>
173 173
174 174 <tr>
175 175 <td style="vertical-align: top">${key}</td>
176 176 <td title="${h.tooltip(title)}">
177 177 %if callable(val):
178 178 ## allow lazy evaluation of elements
179 179 ${val()}
180 180 %else:
181 181 ${val}
182 182 %endif
183 183 %if show_items:
184 184 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
185 185 % for item in show_items:
186 186 <dt></dt>
187 187 <dd>${item}</dd>
188 188 % endfor
189 189 </div>
190 190 %endif
191 191 </td>
192 192 <td style="vertical-align: top">
193 193 %if show_items:
194 194 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
195 195 %endif
196 196 </td>
197 197 </tr>
198 198
199 199 </%def>
200 200
201 201 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
202 202 <%
203 203 if size > 16:
204 204 gravatar_class = ['gravatar','gravatar-large']
205 205 else:
206 206 gravatar_class = ['gravatar']
207 207
208 208 data_hovercard_url = ''
209 209 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
210 210
211 211 if tooltip:
212 212 gravatar_class += ['tooltip-hovercard']
213 213 if extra_class:
214 214 gravatar_class += extra_class
215 215 if tooltip and user:
216 216 if user.username == h.DEFAULT_USER:
217 217 gravatar_class.pop(-1)
218 218 else:
219 219 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
220 220 gravatar_class = ' '.join(gravatar_class)
221 221
222 222 %>
223 223 <%doc>
224 224 TODO: johbo: For now we serve double size images to make it smooth
225 225 for retina. This is how it worked until now. Should be replaced
226 226 with a better solution at some point.
227 227 </%doc>
228 228
229 229 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
230 230 </%def>
231 231
232 232
233 233 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
234 234 <%
235 235 email = h.email_or_none(contact)
236 236 rc_user = h.discover_user(contact)
237 237 %>
238 238
239 239 <div class="${_class}">
240 240 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
241 241 <span class="${('user user-disabled' if show_disabled else 'user')}"> ${h.link_to_user(rc_user or contact)}</span>
242 242 </div>
243 243 </%def>
244 244
245 245
246 246 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
247 247 <%
248 248 if (size > 16):
249 249 gravatar_class = 'icon-user-group-alt'
250 250 else:
251 251 gravatar_class = 'icon-user-group-alt'
252 252
253 253 if tooltip:
254 254 gravatar_class += ' tooltip-hovercard'
255 255
256 256 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
257 257 %>
258 258 <%doc>
259 259 TODO: johbo: For now we serve double size images to make it smooth
260 260 for retina. This is how it worked until now. Should be replaced
261 261 with a better solution at some point.
262 262 </%doc>
263 263
264 264 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
265 265 </%def>
266 266
267 267 <%def name="repo_page_title(repo_instance)">
268 268 <div class="title-content repo-title">
269 269
270 270 <div class="title-main">
271 271 ## SVN/HG/GIT icons
272 272 %if h.is_hg(repo_instance):
273 273 <i class="icon-hg"></i>
274 274 %endif
275 275 %if h.is_git(repo_instance):
276 276 <i class="icon-git"></i>
277 277 %endif
278 278 %if h.is_svn(repo_instance):
279 279 <i class="icon-svn"></i>
280 280 %endif
281 281
282 282 ## public/private
283 283 %if repo_instance.private:
284 284 <i class="icon-repo-private"></i>
285 285 %else:
286 286 <i class="icon-repo-public"></i>
287 287 %endif
288 288
289 289 ## repo name with group name
290 290 ${h.breadcrumb_repo_link(repo_instance)}
291 291
292 292 ## Context Actions
293 293 <div class="pull-right">
294 294 %if c.rhodecode_user.username != h.DEFAULT_USER:
295 295 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
296 296
297 297 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
298 298 % if c.repository_is_user_following:
299 299 <i class="icon-eye-off"></i>${_('Unwatch')}
300 300 % else:
301 301 <i class="icon-eye"></i>${_('Watch')}
302 302 % endif
303 303
304 304 </a>
305 305 %else:
306 306 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
307 307 %endif
308 308 </div>
309 309
310 310 </div>
311 311
312 312 ## FORKED
313 313 %if repo_instance.fork:
314 314 <p class="discreet">
315 315 <i class="icon-code-fork"></i> ${_('Fork of')}
316 316 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
317 317 </p>
318 318 %endif
319 319
320 320 ## IMPORTED FROM REMOTE
321 321 %if repo_instance.clone_uri:
322 322 <p class="discreet">
323 323 <i class="icon-code-fork"></i> ${_('Clone from')}
324 324 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
325 325 </p>
326 326 %endif
327 327
328 328 ## LOCKING STATUS
329 329 %if repo_instance.locked[0]:
330 330 <p class="locking_locked discreet">
331 331 <i class="icon-repo-lock"></i>
332 332 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
333 333 </p>
334 334 %elif repo_instance.enable_locking:
335 335 <p class="locking_unlocked discreet">
336 336 <i class="icon-repo-unlock"></i>
337 337 ${_('Repository not locked. Pull repository to lock it.')}
338 338 </p>
339 339 %endif
340 340
341 341 </div>
342 342 </%def>
343 343
344 344 <%def name="repo_menu(active=None)">
345 345 <%
346 346 ## determine if we have "any" option available
347 347 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
348 348 has_actions = can_lock
349 349
350 350 %>
351 351 % if c.rhodecode_db_repo.archived:
352 352 <div class="alert alert-warning text-center">
353 353 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
354 354 </div>
355 355 % endif
356 356
357 357 <!--- REPO CONTEXT BAR -->
358 358 <div id="context-bar">
359 359 <div class="wrapper">
360 360
361 361 <div class="title">
362 362 ${self.repo_page_title(c.rhodecode_db_repo)}
363 363 </div>
364 364
365 365 <ul id="context-pages" class="navigation horizontal-list">
366 366 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
367 367 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
368 368 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
369 369 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
370 370
371 371 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
372 372 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
373 373 <li class="${h.is_active('showpullrequest', active)}">
374 374 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
375 375 <div class="menulabel">
376 376 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
377 377 </div>
378 378 </a>
379 379 </li>
380 380 %endif
381 381
382 382 <li class="${h.is_active('artifacts', active)}">
383 383 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
384 384 <div class="menulabel">
385 385 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
386 386 </div>
387 387 </a>
388 388 </li>
389 389
390 390 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
391 391 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
392 392 %endif
393 393
394 394 <li class="${h.is_active('options', active)}">
395 395 % if has_actions:
396 396 <a class="menulink dropdown">
397 397 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
398 398 </a>
399 399 <ul class="submenu">
400 400 %if can_lock:
401 401 %if c.rhodecode_db_repo.locked[0]:
402 402 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
403 403 %else:
404 404 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
405 405 %endif
406 406 %endif
407 407 </ul>
408 408 % endif
409 409 </li>
410 410
411 411 </ul>
412 412 </div>
413 413 <div class="clear"></div>
414 414 </div>
415 415
416 416 <!--- REPO END CONTEXT BAR -->
417 417
418 418 </%def>
419 419
420 420 <%def name="repo_group_page_title(repo_group_instance)">
421 421 <div class="title-content">
422 422 <div class="title-main">
423 423 ## Repository Group icon
424 424 <i class="icon-repo-group"></i>
425 425
426 426 ## repo name with group name
427 427 ${h.breadcrumb_repo_group_link(repo_group_instance)}
428 428 </div>
429 429
430 430 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
431 431 <div class="repo-group-desc discreet">
432 432 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
433 433 </div>
434 434
435 435 </div>
436 436 </%def>
437 437
438 438
439 439 <%def name="repo_group_menu(active=None)">
440 440 <%
441 441 gr_name = c.repo_group.group_name if c.repo_group else None
442 442 # create repositories with write permission on group is set to true
443 443 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
444 444
445 445 %>
446 446
447 447
448 448 <!--- REPO GROUP CONTEXT BAR -->
449 449 <div id="context-bar">
450 450 <div class="wrapper">
451 451 <div class="title">
452 452 ${self.repo_group_page_title(c.repo_group)}
453 453 </div>
454 454
455 455 <ul id="context-pages" class="navigation horizontal-list">
456 456 <li class="${h.is_active('home', active)}">
457 457 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
458 458 </li>
459 459 % if c.is_super_admin or group_admin:
460 460 <li class="${h.is_active('settings', active)}">
461 461 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
462 462 </li>
463 463 % endif
464 464
465 465 </ul>
466 466 </div>
467 467 <div class="clear"></div>
468 468 </div>
469 469
470 470 <!--- REPO GROUP CONTEXT BAR -->
471 471
472 472 </%def>
473 473
474 474
475 475 <%def name="usermenu(active=False)">
476 476 <%
477 477 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
478 478
479 479 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
480 480 # create repositories with write permission on group is set to true
481 481
482 482 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
483 483 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
484 484 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
485 485 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
486 486
487 487 can_create_repos = c.is_super_admin or c.can_create_repo
488 488 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
489 489
490 490 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
491 491 can_create_repo_groups_in_group = c.is_super_admin or group_admin
492 492 %>
493 493
494 494 % if not_anonymous:
495 495 <%
496 496 default_target_group = dict()
497 497 if c.rhodecode_user.personal_repo_group:
498 498 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
499 499 %>
500 500
501 501 ## create action
502 502 <li>
503 503 <a href="#create-actions" onclick="return false;" class="menulink childs">
504 504 <i class="tooltip icon-plus-circled" title="${_('Create')}"></i>
505 505 </a>
506 506
507 507 <div class="action-menu submenu">
508 508
509 509 <ol>
510 510 ## scope of within a repository
511 511 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
512 512 <li class="submenu-title">${_('This Repository')}</li>
513 513 <li>
514 514 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
515 515 </li>
516 516 % if can_fork:
517 517 <li>
518 518 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
519 519 </li>
520 520 % endif
521 521 % endif
522 522
523 523 ## scope of within repository groups
524 524 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
525 525 <li class="submenu-title">${_('This Repository Group')}</li>
526 526
527 527 % if can_create_repos_in_group:
528 528 <li>
529 529 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
530 530 </li>
531 531 % endif
532 532
533 533 % if can_create_repo_groups_in_group:
534 534 <li>
535 535 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
536 536 </li>
537 537 % endif
538 538 % endif
539 539
540 540 ## personal group
541 541 % if c.rhodecode_user.personal_repo_group:
542 542 <li class="submenu-title">Personal Group</li>
543 543
544 544 <li>
545 545 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
546 546 </li>
547 547
548 548 <li>
549 549 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
550 550 </li>
551 551 % endif
552 552
553 553 ## Global actions
554 554 <li class="submenu-title">RhodeCode</li>
555 555 % if can_create_repos:
556 556 <li>
557 557 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
558 558 </li>
559 559 % endif
560 560
561 561 % if can_create_repo_groups:
562 562 <li>
563 563 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
564 564 </li>
565 565 % endif
566 566
567 567 <li>
568 568 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
569 569 </li>
570 570
571 571 </ol>
572 572
573 573 </div>
574 574 </li>
575 575
576 576 ## notifications
577 577 <li>
578 578 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
579 579 ${c.unread_notifications}
580 580 </a>
581 581 </li>
582 582 % endif
583 583
584 584 ## USER MENU
585 585 <li id="quick_login_li" class="${'active' if active else ''}">
586 586 % if c.rhodecode_user.username == h.DEFAULT_USER:
587 587 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
588 588 ${gravatar(c.rhodecode_user.email, 20)}
589 589 <span class="user">
590 590 <span>${_('Sign in')}</span>
591 591 </span>
592 592 </a>
593 593 % else:
594 594 ## logged in user
595 595 <a id="quick_login_link" class="menulink childs">
596 596 ${gravatar(c.rhodecode_user.email, 20)}
597 597 <span class="user">
598 598 <span class="menu_link_user">${c.rhodecode_user.username}</span>
599 599 <div class="show_more"></div>
600 600 </span>
601 601 </a>
602 602 ## subnav with menu for logged in user
603 603 <div class="user-menu submenu">
604 604 <div id="quick_login">
605 605 %if c.rhodecode_user.username != h.DEFAULT_USER:
606 606 <div class="">
607 607 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
608 608 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
609 609 <div class="email">${c.rhodecode_user.email}</div>
610 610 </div>
611 611 <div class="">
612 612 <ol class="links">
613 613 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
614 614 % if c.rhodecode_user.personal_repo_group:
615 615 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
616 616 % endif
617 617 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
618 618
619 619 % if c.debug_style:
620 620 <li>
621 621 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
622 622 <div class="menulabel">${_('[Style]')}</div>
623 623 </a>
624 624 </li>
625 625 % endif
626 626
627 627 ## bookmark-items
628 628 <li class="bookmark-items">
629 629 ${_('Bookmarks')}
630 630 <div class="pull-right">
631 631 <a href="${h.route_path('my_account_bookmarks')}">
632 632
633 633 <i class="icon-cog"></i>
634 634 </a>
635 635 </div>
636 636 </li>
637 637 % if not c.bookmark_items:
638 638 <li>
639 639 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
640 640 </li>
641 641 % endif
642 642 % for item in c.bookmark_items:
643 643 <li>
644 644 % if item.repository:
645 645 <div>
646 646 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
647 647 <code>${item.position}</code>
648 648 % if item.repository.repo_type == 'hg':
649 649 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
650 650 % elif item.repository.repo_type == 'git':
651 651 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
652 652 % elif item.repository.repo_type == 'svn':
653 653 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
654 654 % endif
655 655 ${(item.title or h.shorter(item.repository.repo_name, 30))}
656 656 </a>
657 657 </div>
658 658 % elif item.repository_group:
659 659 <div>
660 660 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
661 661 <code>${item.position}</code>
662 662 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
663 663 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
664 664 </a>
665 665 </div>
666 666 % else:
667 667 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
668 668 <code>${item.position}</code>
669 669 ${item.title}
670 670 </a>
671 671 % endif
672 672 </li>
673 673 % endfor
674 674
675 675 <li class="logout">
676 676 ${h.secure_form(h.route_path('logout'), request=request)}
677 677 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
678 678 ${h.end_form()}
679 679 </li>
680 680 </ol>
681 681 </div>
682 682 %endif
683 683 </div>
684 684 </div>
685 685
686 686 % endif
687 687 </li>
688 688 </%def>
689 689
690 690 <%def name="menu_items(active=None)">
691 691 <%
692 692 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
693 693 notice_display = 'none' if len(notice_messages) == 0 else ''
694 694 %>
695 695 <style>
696 696
697 697 </style>
698 698
699 699 <ul id="quick" class="main_nav navigation horizontal-list">
700 700 ## notice box for important system messages
701 701 <li style="display: ${notice_display}">
702 702 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
703 703 <div class="menulabel-notice ${notice_level}" >
704 704 ${len(notice_messages)}
705 705 </div>
706 706 </a>
707 707 </li>
708 708 <div class="notice-messages-container" style="display: none">
709 709 <div class="notice-messages">
710 710 <table class="rctable">
711 711 % for notice in notice_messages:
712 712 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
713 713 <td style="vertical-align: text-top; width: 20px">
714 714 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
715 715 </td>
716 716 <td>
717 717 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
718 718 ${notice['subject']}
719 719
720 720 <div id="notice-${notice['msg_id']}" style="display: none">
721 721 ${h.render(notice['body'], renderer='markdown')}
722 722 </div>
723 723 </td>
724 724 <td style="vertical-align: text-top; width: 35px;">
725 725 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
726 726 <i class="icon-remove icon-filled-red"></i>
727 727 </a>
728 728 </td>
729 729 </tr>
730 730
731 731 % endfor
732 732 </table>
733 733 </div>
734 734 </div>
735 735 ## Main filter
736 736 <li>
737 737 <div class="menulabel main_filter_box">
738 738 <div class="main_filter_input_box">
739 739 <ul class="searchItems">
740 740
741 741 <li class="searchTag searchTagIcon">
742 742 <i class="icon-search"></i>
743 743 </li>
744 744
745 745 % if c.template_context['search_context']['repo_id']:
746 746 <li class="searchTag searchTagFilter searchTagHidable" >
747 747 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
748 748 <span class="tag">
749 749 This repo
750 750 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
751 751 </span>
752 752 ##</a>
753 753 </li>
754 754 % elif c.template_context['search_context']['repo_group_id']:
755 755 <li class="searchTag searchTagFilter searchTagHidable">
756 756 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
757 757 <span class="tag">
758 758 This group
759 759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
760 760 </span>
761 761 ##</a>
762 762 </li>
763 763 % endif
764 764
765 765 <li class="searchTagInput">
766 766 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
767 767 </li>
768 768 <li class="searchTag searchTagHelp">
769 769 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
770 770 </li>
771 771 </ul>
772 772 </div>
773 773 </div>
774 774
775 775 <div id="main_filter_help" style="display: none">
776 776 - Use '/' key to quickly access this field.
777 777
778 778 - Enter a name of repository, or repository group for quick search.
779 779
780 780 - Prefix query to allow special search:
781 781
782 782 user:admin, to search for usernames, always global
783 783
784 784 user_group:devops, to search for user groups, always global
785 785
786 pr:303, to search for pull request number, title, or description, always global
787
786 788 commit:efced4, to search for commits, scoped to repositories or groups
787 789
788 790 file:models.py, to search for file paths, scoped to repositories or groups
789 791
790 792 % if c.template_context['search_context']['repo_id']:
791 793 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
792 794 % elif c.template_context['search_context']['repo_group_id']:
793 795 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
794 796 % else:
795 797 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
796 798 % endif
797 799 </div>
798 800 </li>
799 801
800 802 ## ROOT MENU
801 803 <li class="${h.is_active('home', active)}">
802 804 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
803 805 <div class="menulabel">${_('Home')}</div>
804 806 </a>
805 807 </li>
806 808
807 809 %if c.rhodecode_user.username != h.DEFAULT_USER:
808 810 <li class="${h.is_active('journal', active)}">
809 811 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
810 812 <div class="menulabel">${_('Journal')}</div>
811 813 </a>
812 814 </li>
813 815 %else:
814 816 <li class="${h.is_active('journal', active)}">
815 817 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
816 818 <div class="menulabel">${_('Public journal')}</div>
817 819 </a>
818 820 </li>
819 821 %endif
820 822
821 823 <li class="${h.is_active('gists', active)}">
822 824 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
823 825 <div class="menulabel">${_('Gists')}</div>
824 826 </a>
825 827 </li>
826 828
827 829 % if c.is_super_admin or c.is_delegated_admin:
828 830 <li class="${h.is_active('admin', active)}">
829 831 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
830 832 <div class="menulabel">${_('Admin')} </div>
831 833 </a>
832 834 </li>
833 835 % endif
834 836
835 837 ## render extra user menu
836 838 ${usermenu(active=(active=='my_account'))}
837 839
838 840 </ul>
839 841
840 842 <script type="text/javascript">
841 843 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
842 844
843 845 var formatRepoResult = function(result, container, query, escapeMarkup) {
844 846 return function(data, escapeMarkup) {
845 847 if (!data.repo_id){
846 848 return data.text; // optgroup text Repositories
847 849 }
848 850
849 851 var tmpl = '';
850 852 var repoType = data['repo_type'];
851 853 var repoName = data['text'];
852 854
853 855 if(data && data.type == 'repo'){
854 856 if(repoType === 'hg'){
855 857 tmpl += '<i class="icon-hg"></i> ';
856 858 }
857 859 else if(repoType === 'git'){
858 860 tmpl += '<i class="icon-git"></i> ';
859 861 }
860 862 else if(repoType === 'svn'){
861 863 tmpl += '<i class="icon-svn"></i> ';
862 864 }
863 865 if(data['private']){
864 866 tmpl += '<i class="icon-lock" ></i> ';
865 867 }
866 868 else if(visualShowPublicIcon){
867 869 tmpl += '<i class="icon-unlock-alt"></i> ';
868 870 }
869 871 }
870 872 tmpl += escapeMarkup(repoName);
871 873 return tmpl;
872 874
873 875 }(result, escapeMarkup);
874 876 };
875 877
876 878 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
877 879 return function(data, escapeMarkup) {
878 880 if (!data.repo_group_id){
879 881 return data.text; // optgroup text Repositories
880 882 }
881 883
882 884 var tmpl = '';
883 885 var repoGroupName = data['text'];
884 886
885 887 if(data){
886 888
887 889 tmpl += '<i class="icon-repo-group"></i> ';
888 890
889 891 }
890 892 tmpl += escapeMarkup(repoGroupName);
891 893 return tmpl;
892 894
893 895 }(result, escapeMarkup);
894 896 };
895 897
896 898 var escapeRegExChars = function (value) {
897 899 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
898 900 };
899 901
900 902 var getRepoIcon = function(repo_type) {
901 903 if (repo_type === 'hg') {
902 904 return '<i class="icon-hg"></i> ';
903 905 }
904 906 else if (repo_type === 'git') {
905 907 return '<i class="icon-git"></i> ';
906 908 }
907 909 else if (repo_type === 'svn') {
908 910 return '<i class="icon-svn"></i> ';
909 911 }
910 912 return ''
911 913 };
912 914
913 915 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
914 916
915 917 if (value.split(':').length === 2) {
916 918 value = value.split(':')[1]
917 919 }
918 920
919 921 var searchType = data['type'];
920 922 var searchSubType = data['subtype'];
921 923 var valueDisplay = data['value_display'];
922 924 var valueIcon = data['value_icon'];
923 925
924 926 var pattern = '(' + escapeRegExChars(value) + ')';
925 927
926 928 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
927 929
928 930 // highlight match
929 931 if (searchType != 'text') {
930 932 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
931 933 }
932 934
933 935 var icon = '';
934 936
935 937 if (searchType === 'hint') {
936 938 icon += '<i class="icon-repo-group"></i> ';
937 939 }
938 940 // full text search/hints
939 941 else if (searchType === 'search') {
940 942 if (valueIcon === undefined) {
941 943 icon += '<i class="icon-more"></i> ';
942 944 } else {
943 945 icon += valueIcon + ' ';
944 946 }
945 947
946 948 if (searchSubType !== undefined && searchSubType == 'repo') {
947 949 valueDisplay += '<div class="pull-right tag">repository</div>';
948 950 }
949 951 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
950 952 valueDisplay += '<div class="pull-right tag">repo group</div>';
951 953 }
952 954 }
953 955 // repository
954 956 else if (searchType === 'repo') {
955 957
956 958 var repoIcon = getRepoIcon(data['repo_type']);
957 959 icon += repoIcon;
958 960
959 961 if (data['private']) {
960 962 icon += '<i class="icon-lock" ></i> ';
961 963 }
962 964 else if (visualShowPublicIcon) {
963 965 icon += '<i class="icon-unlock-alt"></i> ';
964 966 }
965 967 }
966 968 // repository groups
967 969 else if (searchType === 'repo_group') {
968 970 icon += '<i class="icon-repo-group"></i> ';
969 971 }
970 972 // user group
971 973 else if (searchType === 'user_group') {
972 974 icon += '<i class="icon-group"></i> ';
973 975 }
974 976 // user
975 977 else if (searchType === 'user') {
976 978 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
977 979 }
980 // pull request
981 else if (searchType === 'pull_request') {
982 icon += '<i class="icon-merge"></i> ';
983 }
978 984 // commit
979 985 else if (searchType === 'commit') {
980 986 var repo_data = data['repo_data'];
981 987 var repoIcon = getRepoIcon(repo_data['repository_type']);
982 988 if (repoIcon) {
983 989 icon += repoIcon;
984 990 } else {
985 991 icon += '<i class="icon-tag"></i>';
986 992 }
987 993 }
988 994 // file
989 995 else if (searchType === 'file') {
990 996 var repo_data = data['repo_data'];
991 997 var repoIcon = getRepoIcon(repo_data['repository_type']);
992 998 if (repoIcon) {
993 999 icon += repoIcon;
994 1000 } else {
995 1001 icon += '<i class="icon-tag"></i>';
996 1002 }
997 1003 }
998 1004 // generic text
999 1005 else if (searchType === 'text') {
1000 1006 icon = '';
1001 1007 }
1002 1008
1003 1009 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1004 1010 return tmpl.format(icon, valueDisplay);
1005 1011 };
1006 1012
1007 1013 var handleSelect = function(element, suggestion) {
1008 1014 if (suggestion.type === "hint") {
1009 1015 // we skip action
1010 1016 $('#main_filter').focus();
1011 1017 }
1012 1018 else if (suggestion.type === "text") {
1013 1019 // we skip action
1014 1020 $('#main_filter').focus();
1015 1021
1016 1022 } else {
1017 1023 window.location = suggestion['url'];
1018 1024 }
1019 1025 };
1020 1026
1021 1027 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1022 1028 if (queryLowerCase.split(':').length === 2) {
1023 1029 queryLowerCase = queryLowerCase.split(':')[1]
1024 1030 }
1025 1031 if (suggestion.type === "text") {
1026 1032 // special case we don't want to "skip" display for
1027 1033 return true
1028 1034 }
1029 1035 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1030 1036 };
1031 1037
1032 1038 var cleanContext = {
1033 1039 repo_view_type: null,
1034 1040
1035 1041 repo_id: null,
1036 1042 repo_name: "",
1037 1043
1038 1044 repo_group_id: null,
1039 1045 repo_group_name: null
1040 1046 };
1041 1047 var removeGoToFilter = function () {
1042 1048 $('.searchTagHidable').hide();
1043 1049 $('#main_filter').autocomplete(
1044 1050 'setOptions', {params:{search_context: cleanContext}});
1045 1051 };
1046 1052
1047 1053 $('#main_filter').autocomplete({
1048 1054 serviceUrl: pyroutes.url('goto_switcher_data'),
1049 1055 params: {
1050 1056 "search_context": templateContext.search_context
1051 1057 },
1052 1058 minChars:2,
1053 1059 maxHeight:400,
1054 1060 deferRequestBy: 300, //miliseconds
1055 1061 tabDisabled: true,
1056 1062 autoSelectFirst: false,
1057 1063 containerClass: 'autocomplete-qfilter-suggestions',
1058 1064 formatResult: autocompleteMainFilterFormatResult,
1059 1065 lookupFilter: autocompleteMainFilterResult,
1060 1066 onSelect: function (element, suggestion) {
1061 1067 handleSelect(element, suggestion);
1062 1068 return false;
1063 1069 },
1064 1070 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1065 1071 if (jqXHR !== 'abort') {
1066 1072 alert("Error during search.\nError code: {0}".format(textStatus));
1067 1073 window.location = '';
1068 1074 }
1069 1075 },
1070 1076 onSearchStart: function (params) {
1071 1077 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1072 1078 },
1073 1079 onSearchComplete: function (query, suggestions) {
1074 1080 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1075 1081 },
1076 1082 });
1077 1083
1078 1084 showMainFilterBox = function () {
1079 1085 $('#main_filter_help').toggle();
1080 1086 };
1081 1087
1082 1088 $('#main_filter').on('keydown.autocomplete', function (e) {
1083 1089
1084 1090 var BACKSPACE = 8;
1085 1091 var el = $(e.currentTarget);
1086 1092 if(e.which === BACKSPACE){
1087 1093 var inputVal = el.val();
1088 1094 if (inputVal === ""){
1089 1095 removeGoToFilter()
1090 1096 }
1091 1097 }
1092 1098 });
1093 1099
1094 1100 var dismissNotice = function(noticeId) {
1095 1101
1096 1102 var url = pyroutes.url('user_notice_dismiss',
1097 1103 {"user_id": templateContext.rhodecode_user.user_id});
1098 1104
1099 1105 var postData = {
1100 1106 'csrf_token': CSRF_TOKEN,
1101 1107 'notice_id': noticeId,
1102 1108 };
1103 1109
1104 1110 var success = function(response) {
1105 1111 $('#notice-message-' + noticeId).remove();
1106 1112 return false;
1107 1113 };
1108 1114 var failure = function(data, textStatus, xhr) {
1109 1115 alert("error processing request: " + textStatus);
1110 1116 return false;
1111 1117 };
1112 1118 ajaxPOST(url, postData, success, failure);
1113 1119 }
1114 1120 </script>
1115 1121 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1116 1122 </%def>
1117 1123
1118 1124 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1119 1125 <div class="modal-dialog">
1120 1126 <div class="modal-content">
1121 1127 <div class="modal-header">
1122 1128 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1123 1129 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1124 1130 </div>
1125 1131 <div class="modal-body">
1126 1132 <div class="block-left">
1127 1133 <table class="keyboard-mappings">
1128 1134 <tbody>
1129 1135 <tr>
1130 1136 <th></th>
1131 1137 <th>${_('Site-wide shortcuts')}</th>
1132 1138 </tr>
1133 1139 <%
1134 1140 elems = [
1135 1141 ('/', 'Use quick search box'),
1136 1142 ('g h', 'Goto home page'),
1137 1143 ('g g', 'Goto my private gists page'),
1138 1144 ('g G', 'Goto my public gists page'),
1139 1145 ('g 0-9', 'Goto bookmarked items from 0-9'),
1140 1146 ('n r', 'New repository page'),
1141 1147 ('n g', 'New gist page'),
1142 1148 ]
1143 1149 %>
1144 1150 %for key, desc in elems:
1145 1151 <tr>
1146 1152 <td class="keys">
1147 1153 <span class="key tag">${key}</span>
1148 1154 </td>
1149 1155 <td>${desc}</td>
1150 1156 </tr>
1151 1157 %endfor
1152 1158 </tbody>
1153 1159 </table>
1154 1160 </div>
1155 1161 <div class="block-left">
1156 1162 <table class="keyboard-mappings">
1157 1163 <tbody>
1158 1164 <tr>
1159 1165 <th></th>
1160 1166 <th>${_('Repositories')}</th>
1161 1167 </tr>
1162 1168 <%
1163 1169 elems = [
1164 1170 ('g s', 'Goto summary page'),
1165 1171 ('g c', 'Goto changelog page'),
1166 1172 ('g f', 'Goto files page'),
1167 1173 ('g F', 'Goto files page with file search activated'),
1168 1174 ('g p', 'Goto pull requests page'),
1169 1175 ('g o', 'Goto repository settings'),
1170 1176 ('g O', 'Goto repository access permissions settings'),
1171 1177 ]
1172 1178 %>
1173 1179 %for key, desc in elems:
1174 1180 <tr>
1175 1181 <td class="keys">
1176 1182 <span class="key tag">${key}</span>
1177 1183 </td>
1178 1184 <td>${desc}</td>
1179 1185 </tr>
1180 1186 %endfor
1181 1187 </tbody>
1182 1188 </table>
1183 1189 </div>
1184 1190 </div>
1185 1191 <div class="modal-footer">
1186 1192 </div>
1187 1193 </div><!-- /.modal-content -->
1188 1194 </div><!-- /.modal-dialog -->
1189 1195 </div><!-- /.modal -->
General Comments 0
You need to be logged in to leave comments. Login now