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