##// END OF EJS Templates
pull-requests: loosen strict view of pull-requests that state is changing...
ergo -
r4103:78e087c7 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,762 +1,762 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 logging
22 22 import datetime
23 23 import string
24 24
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import peppercorn
28 28 from pyramid.httpexceptions import HTTPFound
29 29 from pyramid.view import view_config
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
32 32 from rhodecode import forms
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib import audit_logger
35 35 from rhodecode.lib.ext_json import json
36 36 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired, \
37 37 HasRepoPermissionAny, HasRepoGroupPermissionAny
38 38 from rhodecode.lib.channelstream import (
39 39 channelstream_request, ChannelstreamException)
40 40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.model.comment import CommentsModel
43 43 from rhodecode.model.db import (
44 44 IntegrityError, joinedload,
45 45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 46 PullRequest, UserBookmark, RepoGroup)
47 47 from rhodecode.model.meta import Session
48 48 from rhodecode.model.pull_request import PullRequestModel
49 49 from rhodecode.model.scm import RepoList
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.model.user_group import UserGroupModel
53 53 from rhodecode.model.validation_schema.schemas import user_schema
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class MyAccountView(BaseAppView, DataGridAppView):
59 59 ALLOW_SCOPED_TOKENS = False
60 60 """
61 61 This view has alternative version inside EE, if modified please take a look
62 62 in there as well.
63 63 """
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 c.user = c.auth_user.get_instance()
68 68 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
69 69
70 70 return c
71 71
72 72 @LoginRequired()
73 73 @NotAnonymous()
74 74 @view_config(
75 75 route_name='my_account_profile', request_method='GET',
76 76 renderer='rhodecode:templates/admin/my_account/my_account.mako')
77 77 def my_account_profile(self):
78 78 c = self.load_default_context()
79 79 c.active = 'profile'
80 80 return self._get_template_context(c)
81 81
82 82 @LoginRequired()
83 83 @NotAnonymous()
84 84 @view_config(
85 85 route_name='my_account_password', request_method='GET',
86 86 renderer='rhodecode:templates/admin/my_account/my_account.mako')
87 87 def my_account_password(self):
88 88 c = self.load_default_context()
89 89 c.active = 'password'
90 90 c.extern_type = c.user.extern_type
91 91
92 92 schema = user_schema.ChangePasswordSchema().bind(
93 93 username=c.user.username)
94 94
95 95 form = forms.Form(
96 96 schema,
97 97 action=h.route_path('my_account_password_update'),
98 98 buttons=(forms.buttons.save, forms.buttons.reset))
99 99
100 100 c.form = form
101 101 return self._get_template_context(c)
102 102
103 103 @LoginRequired()
104 104 @NotAnonymous()
105 105 @CSRFRequired()
106 106 @view_config(
107 107 route_name='my_account_password_update', request_method='POST',
108 108 renderer='rhodecode:templates/admin/my_account/my_account.mako')
109 109 def my_account_password_update(self):
110 110 _ = self.request.translate
111 111 c = self.load_default_context()
112 112 c.active = 'password'
113 113 c.extern_type = c.user.extern_type
114 114
115 115 schema = user_schema.ChangePasswordSchema().bind(
116 116 username=c.user.username)
117 117
118 118 form = forms.Form(
119 119 schema, buttons=(forms.buttons.save, forms.buttons.reset))
120 120
121 121 if c.extern_type != 'rhodecode':
122 122 raise HTTPFound(self.request.route_path('my_account_password'))
123 123
124 124 controls = self.request.POST.items()
125 125 try:
126 126 valid_data = form.validate(controls)
127 127 UserModel().update_user(c.user.user_id, **valid_data)
128 128 c.user.update_userdata(force_password_change=False)
129 129 Session().commit()
130 130 except forms.ValidationFailure as e:
131 131 c.form = e
132 132 return self._get_template_context(c)
133 133
134 134 except Exception:
135 135 log.exception("Exception updating password")
136 136 h.flash(_('Error occurred during update of user password'),
137 137 category='error')
138 138 else:
139 139 instance = c.auth_user.get_instance()
140 140 self.session.setdefault('rhodecode_user', {}).update(
141 141 {'password': md5(instance.password)})
142 142 self.session.save()
143 143 h.flash(_("Successfully updated password"), category='success')
144 144
145 145 raise HTTPFound(self.request.route_path('my_account_password'))
146 146
147 147 @LoginRequired()
148 148 @NotAnonymous()
149 149 @view_config(
150 150 route_name='my_account_auth_tokens', request_method='GET',
151 151 renderer='rhodecode:templates/admin/my_account/my_account.mako')
152 152 def my_account_auth_tokens(self):
153 153 _ = self.request.translate
154 154
155 155 c = self.load_default_context()
156 156 c.active = 'auth_tokens'
157 157 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
158 158 c.role_values = [
159 159 (x, AuthTokenModel.cls._get_role_name(x))
160 160 for x in AuthTokenModel.cls.ROLES]
161 161 c.role_options = [(c.role_values, _("Role"))]
162 162 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
163 163 c.user.user_id, show_expired=True)
164 164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 165 return self._get_template_context(c)
166 166
167 167 def maybe_attach_token_scope(self, token):
168 168 # implemented in EE edition
169 169 pass
170 170
171 171 @LoginRequired()
172 172 @NotAnonymous()
173 173 @CSRFRequired()
174 174 @view_config(
175 175 route_name='my_account_auth_tokens_add', request_method='POST',)
176 176 def my_account_auth_tokens_add(self):
177 177 _ = self.request.translate
178 178 c = self.load_default_context()
179 179
180 180 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
181 181 description = self.request.POST.get('description')
182 182 role = self.request.POST.get('role')
183 183
184 184 token = UserModel().add_auth_token(
185 185 user=c.user.user_id,
186 186 lifetime_minutes=lifetime, role=role, description=description,
187 187 scope_callback=self.maybe_attach_token_scope)
188 188 token_data = token.get_api_data()
189 189
190 190 audit_logger.store_web(
191 191 'user.edit.token.add', action_data={
192 192 'data': {'token': token_data, 'user': 'self'}},
193 193 user=self._rhodecode_user, )
194 194 Session().commit()
195 195
196 196 h.flash(_("Auth token successfully created"), category='success')
197 197 return HTTPFound(h.route_path('my_account_auth_tokens'))
198 198
199 199 @LoginRequired()
200 200 @NotAnonymous()
201 201 @CSRFRequired()
202 202 @view_config(
203 203 route_name='my_account_auth_tokens_delete', request_method='POST')
204 204 def my_account_auth_tokens_delete(self):
205 205 _ = self.request.translate
206 206 c = self.load_default_context()
207 207
208 208 del_auth_token = self.request.POST.get('del_auth_token')
209 209
210 210 if del_auth_token:
211 211 token = UserApiKeys.get_or_404(del_auth_token)
212 212 token_data = token.get_api_data()
213 213
214 214 AuthTokenModel().delete(del_auth_token, c.user.user_id)
215 215 audit_logger.store_web(
216 216 'user.edit.token.delete', action_data={
217 217 'data': {'token': token_data, 'user': 'self'}},
218 218 user=self._rhodecode_user,)
219 219 Session().commit()
220 220 h.flash(_("Auth token successfully deleted"), category='success')
221 221
222 222 return HTTPFound(h.route_path('my_account_auth_tokens'))
223 223
224 224 @LoginRequired()
225 225 @NotAnonymous()
226 226 @view_config(
227 227 route_name='my_account_emails', request_method='GET',
228 228 renderer='rhodecode:templates/admin/my_account/my_account.mako')
229 229 def my_account_emails(self):
230 230 _ = self.request.translate
231 231
232 232 c = self.load_default_context()
233 233 c.active = 'emails'
234 234
235 235 c.user_email_map = UserEmailMap.query()\
236 236 .filter(UserEmailMap.user == c.user).all()
237 237
238 238 schema = user_schema.AddEmailSchema().bind(
239 239 username=c.user.username, user_emails=c.user.emails)
240 240
241 241 form = forms.RcForm(schema,
242 242 action=h.route_path('my_account_emails_add'),
243 243 buttons=(forms.buttons.save, forms.buttons.reset))
244 244
245 245 c.form = form
246 246 return self._get_template_context(c)
247 247
248 248 @LoginRequired()
249 249 @NotAnonymous()
250 250 @CSRFRequired()
251 251 @view_config(
252 252 route_name='my_account_emails_add', request_method='POST',
253 253 renderer='rhodecode:templates/admin/my_account/my_account.mako')
254 254 def my_account_emails_add(self):
255 255 _ = self.request.translate
256 256 c = self.load_default_context()
257 257 c.active = 'emails'
258 258
259 259 schema = user_schema.AddEmailSchema().bind(
260 260 username=c.user.username, user_emails=c.user.emails)
261 261
262 262 form = forms.RcForm(
263 263 schema, action=h.route_path('my_account_emails_add'),
264 264 buttons=(forms.buttons.save, forms.buttons.reset))
265 265
266 266 controls = self.request.POST.items()
267 267 try:
268 268 valid_data = form.validate(controls)
269 269 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
270 270 audit_logger.store_web(
271 271 'user.edit.email.add', action_data={
272 272 'data': {'email': valid_data['email'], 'user': 'self'}},
273 273 user=self._rhodecode_user,)
274 274 Session().commit()
275 275 except formencode.Invalid as error:
276 276 h.flash(h.escape(error.error_dict['email']), category='error')
277 277 except forms.ValidationFailure as e:
278 278 c.user_email_map = UserEmailMap.query() \
279 279 .filter(UserEmailMap.user == c.user).all()
280 280 c.form = e
281 281 return self._get_template_context(c)
282 282 except Exception:
283 283 log.exception("Exception adding email")
284 284 h.flash(_('Error occurred during adding email'),
285 285 category='error')
286 286 else:
287 287 h.flash(_("Successfully added email"), category='success')
288 288
289 289 raise HTTPFound(self.request.route_path('my_account_emails'))
290 290
291 291 @LoginRequired()
292 292 @NotAnonymous()
293 293 @CSRFRequired()
294 294 @view_config(
295 295 route_name='my_account_emails_delete', request_method='POST')
296 296 def my_account_emails_delete(self):
297 297 _ = self.request.translate
298 298 c = self.load_default_context()
299 299
300 300 del_email_id = self.request.POST.get('del_email_id')
301 301 if del_email_id:
302 302 email = UserEmailMap.get_or_404(del_email_id).email
303 303 UserModel().delete_extra_email(c.user.user_id, del_email_id)
304 304 audit_logger.store_web(
305 305 'user.edit.email.delete', action_data={
306 306 'data': {'email': email, 'user': 'self'}},
307 307 user=self._rhodecode_user,)
308 308 Session().commit()
309 309 h.flash(_("Email successfully deleted"),
310 310 category='success')
311 311 return HTTPFound(h.route_path('my_account_emails'))
312 312
313 313 @LoginRequired()
314 314 @NotAnonymous()
315 315 @CSRFRequired()
316 316 @view_config(
317 317 route_name='my_account_notifications_test_channelstream',
318 318 request_method='POST', renderer='json_ext')
319 319 def my_account_notifications_test_channelstream(self):
320 320 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
321 321 self._rhodecode_user.username, datetime.datetime.now())
322 322 payload = {
323 323 # 'channel': 'broadcast',
324 324 'type': 'message',
325 325 'timestamp': datetime.datetime.utcnow(),
326 326 'user': 'system',
327 327 'pm_users': [self._rhodecode_user.username],
328 328 'message': {
329 329 'message': message,
330 330 'level': 'info',
331 331 'topic': '/notifications'
332 332 }
333 333 }
334 334
335 335 registry = self.request.registry
336 336 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
337 337 channelstream_config = rhodecode_plugins.get('channelstream', {})
338 338
339 339 try:
340 340 channelstream_request(channelstream_config, [payload], '/message')
341 341 except ChannelstreamException as e:
342 342 log.exception('Failed to send channelstream data')
343 343 return {"response": 'ERROR: {}'.format(e.__class__.__name__)}
344 344 return {"response": 'Channelstream data sent. '
345 345 'You should see a new live message now.'}
346 346
347 347 def _load_my_repos_data(self, watched=False):
348 348 if watched:
349 349 admin = False
350 350 follows_repos = Session().query(UserFollowing)\
351 351 .filter(UserFollowing.user_id == self._rhodecode_user.user_id)\
352 352 .options(joinedload(UserFollowing.follows_repository))\
353 353 .all()
354 354 repo_list = [x.follows_repository for x in follows_repos]
355 355 else:
356 356 admin = True
357 357 repo_list = Repository.get_all_repos(
358 358 user_id=self._rhodecode_user.user_id)
359 359 repo_list = RepoList(repo_list, perm_set=[
360 360 'repository.read', 'repository.write', 'repository.admin'])
361 361
362 362 repos_data = RepoModel().get_repos_as_dict(
363 363 repo_list=repo_list, admin=admin, short_name=False)
364 364 # json used to render the grid
365 365 return json.dumps(repos_data)
366 366
367 367 @LoginRequired()
368 368 @NotAnonymous()
369 369 @view_config(
370 370 route_name='my_account_repos', request_method='GET',
371 371 renderer='rhodecode:templates/admin/my_account/my_account.mako')
372 372 def my_account_repos(self):
373 373 c = self.load_default_context()
374 374 c.active = 'repos'
375 375
376 376 # json used to render the grid
377 377 c.data = self._load_my_repos_data()
378 378 return self._get_template_context(c)
379 379
380 380 @LoginRequired()
381 381 @NotAnonymous()
382 382 @view_config(
383 383 route_name='my_account_watched', request_method='GET',
384 384 renderer='rhodecode:templates/admin/my_account/my_account.mako')
385 385 def my_account_watched(self):
386 386 c = self.load_default_context()
387 387 c.active = 'watched'
388 388
389 389 # json used to render the grid
390 390 c.data = self._load_my_repos_data(watched=True)
391 391 return self._get_template_context(c)
392 392
393 393 @LoginRequired()
394 394 @NotAnonymous()
395 395 @view_config(
396 396 route_name='my_account_bookmarks', request_method='GET',
397 397 renderer='rhodecode:templates/admin/my_account/my_account.mako')
398 398 def my_account_bookmarks(self):
399 399 c = self.load_default_context()
400 400 c.active = 'bookmarks'
401 401 return self._get_template_context(c)
402 402
403 403 def _process_bookmark_entry(self, entry, user_id):
404 404 position = safe_int(entry.get('position'))
405 405 cur_position = safe_int(entry.get('cur_position'))
406 406 if position is None or cur_position is None:
407 407 return
408 408
409 409 # check if this is an existing entry
410 410 is_new = False
411 411 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
412 412
413 413 if db_entry and str2bool(entry.get('remove')):
414 414 log.debug('Marked bookmark %s for deletion', db_entry)
415 415 Session().delete(db_entry)
416 416 return
417 417
418 418 if not db_entry:
419 419 # new
420 420 db_entry = UserBookmark()
421 421 is_new = True
422 422
423 423 should_save = False
424 424 default_redirect_url = ''
425 425
426 426 # save repo
427 427 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
428 428 repo = Repository.get(entry['bookmark_repo'])
429 429 perm_check = HasRepoPermissionAny(
430 430 'repository.read', 'repository.write', 'repository.admin')
431 431 if repo and perm_check(repo_name=repo.repo_name):
432 432 db_entry.repository = repo
433 433 should_save = True
434 434 default_redirect_url = '${repo_url}'
435 435 # save repo group
436 436 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
437 437 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
438 438 perm_check = HasRepoGroupPermissionAny(
439 439 'group.read', 'group.write', 'group.admin')
440 440
441 441 if repo_group and perm_check(group_name=repo_group.group_name):
442 442 db_entry.repository_group = repo_group
443 443 should_save = True
444 444 default_redirect_url = '${repo_group_url}'
445 445 # save generic info
446 446 elif entry.get('title') and entry.get('redirect_url'):
447 447 should_save = True
448 448
449 449 if should_save:
450 450 # mark user and position
451 451 db_entry.user_id = user_id
452 452 db_entry.position = position
453 453 db_entry.title = entry.get('title')
454 454 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
455 455 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
456 456
457 457 Session().add(db_entry)
458 458
459 459 @LoginRequired()
460 460 @NotAnonymous()
461 461 @CSRFRequired()
462 462 @view_config(
463 463 route_name='my_account_bookmarks_update', request_method='POST')
464 464 def my_account_bookmarks_update(self):
465 465 _ = self.request.translate
466 466 c = self.load_default_context()
467 467 c.active = 'bookmarks'
468 468
469 469 controls = peppercorn.parse(self.request.POST.items())
470 470 user_id = c.user.user_id
471 471
472 472 # validate positions
473 473 positions = {}
474 474 for entry in controls.get('bookmarks', []):
475 475 position = safe_int(entry['position'])
476 476 if position is None:
477 477 continue
478 478
479 479 if position in positions:
480 480 h.flash(_("Position {} is defined twice. "
481 481 "Please correct this error.").format(position), category='error')
482 482 return HTTPFound(h.route_path('my_account_bookmarks'))
483 483
484 484 entry['position'] = position
485 485 entry['cur_position'] = safe_int(entry.get('cur_position'))
486 486 positions[position] = entry
487 487
488 488 try:
489 489 for entry in positions.values():
490 490 self._process_bookmark_entry(entry, user_id)
491 491
492 492 Session().commit()
493 493 h.flash(_("Update Bookmarks"), category='success')
494 494 except IntegrityError:
495 495 h.flash(_("Failed to update bookmarks. "
496 496 "Make sure an unique position is used."), category='error')
497 497
498 498 return HTTPFound(h.route_path('my_account_bookmarks'))
499 499
500 500 @LoginRequired()
501 501 @NotAnonymous()
502 502 @view_config(
503 503 route_name='my_account_goto_bookmark', request_method='GET',
504 504 renderer='rhodecode:templates/admin/my_account/my_account.mako')
505 505 def my_account_goto_bookmark(self):
506 506
507 507 bookmark_id = self.request.matchdict['bookmark_id']
508 508 user_bookmark = UserBookmark().query()\
509 509 .filter(UserBookmark.user_id == self.request.user.user_id) \
510 510 .filter(UserBookmark.position == bookmark_id).scalar()
511 511
512 512 redirect_url = h.route_path('my_account_bookmarks')
513 513 if not user_bookmark:
514 514 raise HTTPFound(redirect_url)
515 515
516 516 # repository set
517 517 if user_bookmark.repository:
518 518 repo_name = user_bookmark.repository.repo_name
519 519 base_redirect_url = h.route_path(
520 520 'repo_summary', repo_name=repo_name)
521 521 if user_bookmark.redirect_url and \
522 522 '${repo_url}' in user_bookmark.redirect_url:
523 523 redirect_url = string.Template(user_bookmark.redirect_url)\
524 524 .safe_substitute({'repo_url': base_redirect_url})
525 525 else:
526 526 redirect_url = base_redirect_url
527 527 # repository group set
528 528 elif user_bookmark.repository_group:
529 529 repo_group_name = user_bookmark.repository_group.group_name
530 530 base_redirect_url = h.route_path(
531 531 'repo_group_home', repo_group_name=repo_group_name)
532 532 if user_bookmark.redirect_url and \
533 533 '${repo_group_url}' in user_bookmark.redirect_url:
534 534 redirect_url = string.Template(user_bookmark.redirect_url)\
535 535 .safe_substitute({'repo_group_url': base_redirect_url})
536 536 else:
537 537 redirect_url = base_redirect_url
538 538 # custom URL set
539 539 elif user_bookmark.redirect_url:
540 540 server_url = h.route_url('home').rstrip('/')
541 541 redirect_url = string.Template(user_bookmark.redirect_url) \
542 542 .safe_substitute({'server_url': server_url})
543 543
544 544 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
545 545 raise HTTPFound(redirect_url)
546 546
547 547 @LoginRequired()
548 548 @NotAnonymous()
549 549 @view_config(
550 550 route_name='my_account_perms', request_method='GET',
551 551 renderer='rhodecode:templates/admin/my_account/my_account.mako')
552 552 def my_account_perms(self):
553 553 c = self.load_default_context()
554 554 c.active = 'perms'
555 555
556 556 c.perm_user = c.auth_user
557 557 return self._get_template_context(c)
558 558
559 559 @LoginRequired()
560 560 @NotAnonymous()
561 561 @view_config(
562 562 route_name='my_account_notifications', request_method='GET',
563 563 renderer='rhodecode:templates/admin/my_account/my_account.mako')
564 564 def my_notifications(self):
565 565 c = self.load_default_context()
566 566 c.active = 'notifications'
567 567
568 568 return self._get_template_context(c)
569 569
570 570 @LoginRequired()
571 571 @NotAnonymous()
572 572 @CSRFRequired()
573 573 @view_config(
574 574 route_name='my_account_notifications_toggle_visibility',
575 575 request_method='POST', renderer='json_ext')
576 576 def my_notifications_toggle_visibility(self):
577 577 user = self._rhodecode_db_user
578 578 new_status = not user.user_data.get('notification_status', True)
579 579 user.update_userdata(notification_status=new_status)
580 580 Session().commit()
581 581 return user.user_data['notification_status']
582 582
583 583 @LoginRequired()
584 584 @NotAnonymous()
585 585 @view_config(
586 586 route_name='my_account_edit',
587 587 request_method='GET',
588 588 renderer='rhodecode:templates/admin/my_account/my_account.mako')
589 589 def my_account_edit(self):
590 590 c = self.load_default_context()
591 591 c.active = 'profile_edit'
592 592 c.extern_type = c.user.extern_type
593 593 c.extern_name = c.user.extern_name
594 594
595 595 schema = user_schema.UserProfileSchema().bind(
596 596 username=c.user.username, user_emails=c.user.emails)
597 597 appstruct = {
598 598 'username': c.user.username,
599 599 'email': c.user.email,
600 600 'firstname': c.user.firstname,
601 601 'lastname': c.user.lastname,
602 602 'description': c.user.description,
603 603 }
604 604 c.form = forms.RcForm(
605 605 schema, appstruct=appstruct,
606 606 action=h.route_path('my_account_update'),
607 607 buttons=(forms.buttons.save, forms.buttons.reset))
608 608
609 609 return self._get_template_context(c)
610 610
611 611 @LoginRequired()
612 612 @NotAnonymous()
613 613 @CSRFRequired()
614 614 @view_config(
615 615 route_name='my_account_update',
616 616 request_method='POST',
617 617 renderer='rhodecode:templates/admin/my_account/my_account.mako')
618 618 def my_account_update(self):
619 619 _ = self.request.translate
620 620 c = self.load_default_context()
621 621 c.active = 'profile_edit'
622 622 c.perm_user = c.auth_user
623 623 c.extern_type = c.user.extern_type
624 624 c.extern_name = c.user.extern_name
625 625
626 626 schema = user_schema.UserProfileSchema().bind(
627 627 username=c.user.username, user_emails=c.user.emails)
628 628 form = forms.RcForm(
629 629 schema, buttons=(forms.buttons.save, forms.buttons.reset))
630 630
631 631 controls = self.request.POST.items()
632 632 try:
633 633 valid_data = form.validate(controls)
634 634 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
635 635 'new_password', 'password_confirmation']
636 636 if c.extern_type != "rhodecode":
637 637 # forbid updating username for external accounts
638 638 skip_attrs.append('username')
639 639 old_email = c.user.email
640 640 UserModel().update_user(
641 641 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
642 642 **valid_data)
643 643 if old_email != valid_data['email']:
644 644 old = UserEmailMap.query() \
645 645 .filter(UserEmailMap.user == c.user).filter(UserEmailMap.email == valid_data['email']).first()
646 646 old.email = old_email
647 647 h.flash(_('Your account was updated successfully'), category='success')
648 648 Session().commit()
649 649 except forms.ValidationFailure as e:
650 650 c.form = e
651 651 return self._get_template_context(c)
652 652 except Exception:
653 653 log.exception("Exception updating user")
654 654 h.flash(_('Error occurred during update of user'),
655 655 category='error')
656 656 raise HTTPFound(h.route_path('my_account_profile'))
657 657
658 658 def _get_pull_requests_list(self, statuses):
659 659 draw, start, limit = self._extract_chunk(self.request)
660 660 search_q, order_by, order_dir = self._extract_ordering(self.request)
661 661 _render = self.request.get_partial_renderer(
662 662 'rhodecode:templates/data_table/_dt_elements.mako')
663 663
664 664 pull_requests = PullRequestModel().get_im_participating_in(
665 665 user_id=self._rhodecode_user.user_id,
666 666 statuses=statuses,
667 667 offset=start, length=limit, order_by=order_by,
668 668 order_dir=order_dir)
669 669
670 670 pull_requests_total_count = PullRequestModel().count_im_participating_in(
671 671 user_id=self._rhodecode_user.user_id, statuses=statuses)
672 672
673 673 data = []
674 674 comments_model = CommentsModel()
675 675 for pr in pull_requests:
676 676 repo_id = pr.target_repo_id
677 677 comments = comments_model.get_all_comments(
678 678 repo_id, pull_request=pr)
679 679 owned = pr.user_id == self._rhodecode_user.user_id
680 680
681 681 data.append({
682 682 'target_repo': _render('pullrequest_target_repo',
683 683 pr.target_repo.repo_name),
684 684 'name': _render('pullrequest_name',
685 pr.pull_request_id, pr.work_in_progress,
686 pr.target_repo.repo_name,
685 pr.pull_request_id, pr.pull_request_state,
686 pr.work_in_progress, pr.target_repo.repo_name,
687 687 short=True),
688 688 'name_raw': pr.pull_request_id,
689 689 'status': _render('pullrequest_status',
690 690 pr.calculated_review_status()),
691 691 'title': _render('pullrequest_title', pr.title, pr.description),
692 692 'description': h.escape(pr.description),
693 693 'updated_on': _render('pullrequest_updated_on',
694 694 h.datetime_to_time(pr.updated_on)),
695 695 'updated_on_raw': h.datetime_to_time(pr.updated_on),
696 696 'created_on': _render('pullrequest_updated_on',
697 697 h.datetime_to_time(pr.created_on)),
698 698 'created_on_raw': h.datetime_to_time(pr.created_on),
699 699 'state': pr.pull_request_state,
700 700 'author': _render('pullrequest_author',
701 701 pr.author.full_contact, ),
702 702 'author_raw': pr.author.full_name,
703 703 'comments': _render('pullrequest_comments', len(comments)),
704 704 'comments_raw': len(comments),
705 705 'closed': pr.is_closed(),
706 706 'owned': owned
707 707 })
708 708
709 709 # json used to render the grid
710 710 data = ({
711 711 'draw': draw,
712 712 'data': data,
713 713 'recordsTotal': pull_requests_total_count,
714 714 'recordsFiltered': pull_requests_total_count,
715 715 })
716 716 return data
717 717
718 718 @LoginRequired()
719 719 @NotAnonymous()
720 720 @view_config(
721 721 route_name='my_account_pullrequests',
722 722 request_method='GET',
723 723 renderer='rhodecode:templates/admin/my_account/my_account.mako')
724 724 def my_account_pullrequests(self):
725 725 c = self.load_default_context()
726 726 c.active = 'pullrequests'
727 727 req_get = self.request.GET
728 728
729 729 c.closed = str2bool(req_get.get('pr_show_closed'))
730 730
731 731 return self._get_template_context(c)
732 732
733 733 @LoginRequired()
734 734 @NotAnonymous()
735 735 @view_config(
736 736 route_name='my_account_pullrequests_data',
737 737 request_method='GET', renderer='json_ext')
738 738 def my_account_pullrequests_data(self):
739 739 self.load_default_context()
740 740 req_get = self.request.GET
741 741 closed = str2bool(req_get.get('closed'))
742 742
743 743 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
744 744 if closed:
745 745 statuses += [PullRequest.STATUS_CLOSED]
746 746
747 747 data = self._get_pull_requests_list(statuses=statuses)
748 748 return data
749 749
750 750 @LoginRequired()
751 751 @NotAnonymous()
752 752 @view_config(
753 753 route_name='my_account_user_group_membership',
754 754 request_method='GET',
755 755 renderer='rhodecode:templates/admin/my_account/my_account.mako')
756 756 def my_account_user_group_membership(self):
757 757 c = self.load_default_context()
758 758 c.active = 'user_group_membership'
759 759 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
760 760 for group in self._rhodecode_db_user.group_member]
761 761 c.user_groups = json.dumps(groups)
762 762 return self._get_template_context(c)
@@ -1,1482 +1,1476 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2011-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 logging
22 22 import collections
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import peppercorn
27 27 from pyramid.httpexceptions import (
28 28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest)
29 29 from pyramid.view import view_config
30 30 from pyramid.renderers import render
31 31
32 32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33 33
34 34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 35 from rhodecode.lib.base import vcs_operation_context
36 36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.auth import (
39 39 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 40 NotAnonymous, CSRFRequired)
41 41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
44 44 RepositoryRequirementError, EmptyRepositoryError)
45 45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 46 from rhodecode.model.comment import CommentsModel
47 47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
48 48 ChangesetComment, ChangesetStatus, Repository)
49 49 from rhodecode.model.forms import PullRequestForm
50 50 from rhodecode.model.meta import Session
51 51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 52 from rhodecode.model.scm import ScmModel
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58 58
59 59 def load_default_context(self):
60 60 c = self._get_local_tmpl_context(include_app_defaults=True)
61 61 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 62 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 63 # backward compat., we use for OLD PRs a plain renderer
64 64 c.renderer = 'plain'
65 65 return c
66 66
67 67 def _get_pull_requests_list(
68 68 self, repo_name, source, filter_type, opened_by, statuses):
69 69
70 70 draw, start, limit = self._extract_chunk(self.request)
71 71 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 72 _render = self.request.get_partial_renderer(
73 73 'rhodecode:templates/data_table/_dt_elements.mako')
74 74
75 75 # pagination
76 76
77 77 if filter_type == 'awaiting_review':
78 78 pull_requests = PullRequestModel().get_awaiting_review(
79 79 repo_name, search_q=search_q, source=source, opened_by=opened_by,
80 80 statuses=statuses, offset=start, length=limit,
81 81 order_by=order_by, order_dir=order_dir)
82 82 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 83 repo_name, search_q=search_q, source=source, statuses=statuses,
84 84 opened_by=opened_by)
85 85 elif filter_type == 'awaiting_my_review':
86 86 pull_requests = PullRequestModel().get_awaiting_my_review(
87 87 repo_name, search_q=search_q, source=source, opened_by=opened_by,
88 88 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 89 offset=start, length=limit, order_by=order_by,
90 90 order_dir=order_dir)
91 91 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 92 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
93 93 statuses=statuses, opened_by=opened_by)
94 94 else:
95 95 pull_requests = PullRequestModel().get_all(
96 96 repo_name, search_q=search_q, source=source, opened_by=opened_by,
97 97 statuses=statuses, offset=start, length=limit,
98 98 order_by=order_by, order_dir=order_dir)
99 99 pull_requests_total_count = PullRequestModel().count_all(
100 100 repo_name, search_q=search_q, source=source, statuses=statuses,
101 101 opened_by=opened_by)
102 102
103 103 data = []
104 104 comments_model = CommentsModel()
105 105 for pr in pull_requests:
106 106 comments = comments_model.get_all_comments(
107 107 self.db_repo.repo_id, pull_request=pr)
108 108
109 109 data.append({
110 110 'name': _render('pullrequest_name',
111 pr.pull_request_id, pr.work_in_progress,
112 pr.target_repo.repo_name),
111 pr.pull_request_id, pr.pull_request_state,
112 pr.work_in_progress, pr.target_repo.repo_name),
113 113 'name_raw': pr.pull_request_id,
114 114 'status': _render('pullrequest_status',
115 115 pr.calculated_review_status()),
116 116 'title': _render('pullrequest_title', pr.title, pr.description),
117 117 'description': h.escape(pr.description),
118 118 'updated_on': _render('pullrequest_updated_on',
119 119 h.datetime_to_time(pr.updated_on)),
120 120 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 121 'created_on': _render('pullrequest_updated_on',
122 122 h.datetime_to_time(pr.created_on)),
123 123 'created_on_raw': h.datetime_to_time(pr.created_on),
124 124 'state': pr.pull_request_state,
125 125 'author': _render('pullrequest_author',
126 126 pr.author.full_contact, ),
127 127 'author_raw': pr.author.full_name,
128 128 'comments': _render('pullrequest_comments', len(comments)),
129 129 'comments_raw': len(comments),
130 130 'closed': pr.is_closed(),
131 131 })
132 132
133 133 data = ({
134 134 'draw': draw,
135 135 'data': data,
136 136 'recordsTotal': pull_requests_total_count,
137 137 'recordsFiltered': pull_requests_total_count,
138 138 })
139 139 return data
140 140
141 141 @LoginRequired()
142 142 @HasRepoPermissionAnyDecorator(
143 143 'repository.read', 'repository.write', 'repository.admin')
144 144 @view_config(
145 145 route_name='pullrequest_show_all', request_method='GET',
146 146 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
147 147 def pull_request_list(self):
148 148 c = self.load_default_context()
149 149
150 150 req_get = self.request.GET
151 151 c.source = str2bool(req_get.get('source'))
152 152 c.closed = str2bool(req_get.get('closed'))
153 153 c.my = str2bool(req_get.get('my'))
154 154 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
155 155 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
156 156
157 157 c.active = 'open'
158 158 if c.my:
159 159 c.active = 'my'
160 160 if c.closed:
161 161 c.active = 'closed'
162 162 if c.awaiting_review and not c.source:
163 163 c.active = 'awaiting'
164 164 if c.source and not c.awaiting_review:
165 165 c.active = 'source'
166 166 if c.awaiting_my_review:
167 167 c.active = 'awaiting_my'
168 168
169 169 return self._get_template_context(c)
170 170
171 171 @LoginRequired()
172 172 @HasRepoPermissionAnyDecorator(
173 173 'repository.read', 'repository.write', 'repository.admin')
174 174 @view_config(
175 175 route_name='pullrequest_show_all_data', request_method='GET',
176 176 renderer='json_ext', xhr=True)
177 177 def pull_request_list_data(self):
178 178 self.load_default_context()
179 179
180 180 # additional filters
181 181 req_get = self.request.GET
182 182 source = str2bool(req_get.get('source'))
183 183 closed = str2bool(req_get.get('closed'))
184 184 my = str2bool(req_get.get('my'))
185 185 awaiting_review = str2bool(req_get.get('awaiting_review'))
186 186 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
187 187
188 188 filter_type = 'awaiting_review' if awaiting_review \
189 189 else 'awaiting_my_review' if awaiting_my_review \
190 190 else None
191 191
192 192 opened_by = None
193 193 if my:
194 194 opened_by = [self._rhodecode_user.user_id]
195 195
196 196 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
197 197 if closed:
198 198 statuses = [PullRequest.STATUS_CLOSED]
199 199
200 200 data = self._get_pull_requests_list(
201 201 repo_name=self.db_repo_name, source=source,
202 202 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
203 203
204 204 return data
205 205
206 206 def _is_diff_cache_enabled(self, target_repo):
207 207 caching_enabled = self._get_general_setting(
208 208 target_repo, 'rhodecode_diff_cache')
209 209 log.debug('Diff caching enabled: %s', caching_enabled)
210 210 return caching_enabled
211 211
212 212 def _get_diffset(self, source_repo_name, source_repo,
213 213 source_ref_id, target_ref_id,
214 214 target_commit, source_commit, diff_limit, file_limit,
215 215 fulldiff, hide_whitespace_changes, diff_context):
216 216
217 217 vcs_diff = PullRequestModel().get_diff(
218 218 source_repo, source_ref_id, target_ref_id,
219 219 hide_whitespace_changes, diff_context)
220 220
221 221 diff_processor = diffs.DiffProcessor(
222 222 vcs_diff, format='newdiff', diff_limit=diff_limit,
223 223 file_limit=file_limit, show_full_diff=fulldiff)
224 224
225 225 _parsed = diff_processor.prepare()
226 226
227 227 diffset = codeblocks.DiffSet(
228 228 repo_name=self.db_repo_name,
229 229 source_repo_name=source_repo_name,
230 230 source_node_getter=codeblocks.diffset_node_getter(target_commit),
231 231 target_node_getter=codeblocks.diffset_node_getter(source_commit),
232 232 )
233 233 diffset = self.path_filter.render_patchset_filtered(
234 234 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
235 235
236 236 return diffset
237 237
238 238 def _get_range_diffset(self, source_scm, source_repo,
239 239 commit1, commit2, diff_limit, file_limit,
240 240 fulldiff, hide_whitespace_changes, diff_context):
241 241 vcs_diff = source_scm.get_diff(
242 242 commit1, commit2,
243 243 ignore_whitespace=hide_whitespace_changes,
244 244 context=diff_context)
245 245
246 246 diff_processor = diffs.DiffProcessor(
247 247 vcs_diff, format='newdiff', diff_limit=diff_limit,
248 248 file_limit=file_limit, show_full_diff=fulldiff)
249 249
250 250 _parsed = diff_processor.prepare()
251 251
252 252 diffset = codeblocks.DiffSet(
253 253 repo_name=source_repo.repo_name,
254 254 source_node_getter=codeblocks.diffset_node_getter(commit1),
255 255 target_node_getter=codeblocks.diffset_node_getter(commit2))
256 256
257 257 diffset = self.path_filter.render_patchset_filtered(
258 258 diffset, _parsed, commit1.raw_id, commit2.raw_id)
259 259
260 260 return diffset
261 261
262 262 @LoginRequired()
263 263 @HasRepoPermissionAnyDecorator(
264 264 'repository.read', 'repository.write', 'repository.admin')
265 265 @view_config(
266 266 route_name='pullrequest_show', request_method='GET',
267 267 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
268 268 def pull_request_show(self):
269 269 _ = self.request.translate
270 270 c = self.load_default_context()
271 271
272 272 pull_request = PullRequest.get_or_404(
273 273 self.request.matchdict['pull_request_id'])
274 274 pull_request_id = pull_request.pull_request_id
275 275
276 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
277 log.debug('show: forbidden because pull request is in state %s',
278 pull_request.pull_request_state)
279 msg = _(u'Cannot show pull requests in state other than `{}`. '
280 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
281 pull_request.pull_request_state)
282 h.flash(msg, category='error')
283 raise HTTPFound(h.route_path('pullrequest_show_all',
284 repo_name=self.db_repo_name))
276 c.state_progressing = pull_request.is_state_changing()
285 277
286 278 version = self.request.GET.get('version')
287 279 from_version = self.request.GET.get('from_version') or version
288 280 merge_checks = self.request.GET.get('merge_checks')
289 281 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
290 282
291 283 # fetch global flags of ignore ws or context lines
292 284 diff_context = diffs.get_diff_context(self.request)
293 285 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
294 286
295 287 force_refresh = str2bool(self.request.GET.get('force_refresh'))
296 288
297 289 (pull_request_latest,
298 290 pull_request_at_ver,
299 291 pull_request_display_obj,
300 292 at_version) = PullRequestModel().get_pr_version(
301 293 pull_request_id, version=version)
302 294 pr_closed = pull_request_latest.is_closed()
303 295
304 296 if pr_closed and (version or from_version):
305 297 # not allow to browse versions
306 298 raise HTTPFound(h.route_path(
307 299 'pullrequest_show', repo_name=self.db_repo_name,
308 300 pull_request_id=pull_request_id))
309 301
310 302 versions = pull_request_display_obj.versions()
311 303 # used to store per-commit range diffs
312 304 c.changes = collections.OrderedDict()
313 305 c.range_diff_on = self.request.GET.get('range-diff') == "1"
314 306
315 307 c.at_version = at_version
316 308 c.at_version_num = (at_version
317 309 if at_version and at_version != 'latest'
318 310 else None)
319 311 c.at_version_pos = ChangesetComment.get_index_from_version(
320 312 c.at_version_num, versions)
321 313
322 314 (prev_pull_request_latest,
323 315 prev_pull_request_at_ver,
324 316 prev_pull_request_display_obj,
325 317 prev_at_version) = PullRequestModel().get_pr_version(
326 318 pull_request_id, version=from_version)
327 319
328 320 c.from_version = prev_at_version
329 321 c.from_version_num = (prev_at_version
330 322 if prev_at_version and prev_at_version != 'latest'
331 323 else None)
332 324 c.from_version_pos = ChangesetComment.get_index_from_version(
333 325 c.from_version_num, versions)
334 326
335 327 # define if we're in COMPARE mode or VIEW at version mode
336 328 compare = at_version != prev_at_version
337 329
338 330 # pull_requests repo_name we opened it against
339 331 # ie. target_repo must match
340 332 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
341 333 raise HTTPNotFound()
342 334
343 335 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
344 336 pull_request_at_ver)
345 337
346 338 c.pull_request = pull_request_display_obj
347 339 c.renderer = pull_request_at_ver.description_renderer or c.renderer
348 340 c.pull_request_latest = pull_request_latest
349 341
350 342 if compare or (at_version and not at_version == 'latest'):
351 343 c.allowed_to_change_status = False
352 344 c.allowed_to_update = False
353 345 c.allowed_to_merge = False
354 346 c.allowed_to_delete = False
355 347 c.allowed_to_comment = False
356 348 c.allowed_to_close = False
357 349 else:
358 350 can_change_status = PullRequestModel().check_user_change_status(
359 351 pull_request_at_ver, self._rhodecode_user)
360 352 c.allowed_to_change_status = can_change_status and not pr_closed
361 353
362 354 c.allowed_to_update = PullRequestModel().check_user_update(
363 355 pull_request_latest, self._rhodecode_user) and not pr_closed
364 356 c.allowed_to_merge = PullRequestModel().check_user_merge(
365 357 pull_request_latest, self._rhodecode_user) and not pr_closed
366 358 c.allowed_to_delete = PullRequestModel().check_user_delete(
367 359 pull_request_latest, self._rhodecode_user) and not pr_closed
368 360 c.allowed_to_comment = not pr_closed
369 361 c.allowed_to_close = c.allowed_to_merge and not pr_closed
370 362
371 363 c.forbid_adding_reviewers = False
372 364 c.forbid_author_to_review = False
373 365 c.forbid_commit_author_to_review = False
374 366
375 367 if pull_request_latest.reviewer_data and \
376 368 'rules' in pull_request_latest.reviewer_data:
377 369 rules = pull_request_latest.reviewer_data['rules'] or {}
378 370 try:
379 371 c.forbid_adding_reviewers = rules.get(
380 372 'forbid_adding_reviewers')
381 373 c.forbid_author_to_review = rules.get(
382 374 'forbid_author_to_review')
383 375 c.forbid_commit_author_to_review = rules.get(
384 376 'forbid_commit_author_to_review')
385 377 except Exception:
386 378 pass
387 379
388 380 # check merge capabilities
389 381 _merge_check = MergeCheck.validate(
390 382 pull_request_latest, auth_user=self._rhodecode_user,
391 383 translator=self.request.translate,
392 384 force_shadow_repo_refresh=force_refresh)
393 385 c.pr_merge_errors = _merge_check.error_details
394 386 c.pr_merge_possible = not _merge_check.failed
395 387 c.pr_merge_message = _merge_check.merge_msg
396 388
397 389 c.pr_merge_info = MergeCheck.get_merge_conditions(
398 390 pull_request_latest, translator=self.request.translate)
399 391
400 392 c.pull_request_review_status = _merge_check.review_status
401 393 if merge_checks:
402 394 self.request.override_renderer = \
403 395 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
404 396 return self._get_template_context(c)
405 397
406 398 comments_model = CommentsModel()
407 399
408 400 # reviewers and statuses
409 401 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
410 402 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
411 403
412 404 # GENERAL COMMENTS with versions #
413 405 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
414 406 q = q.order_by(ChangesetComment.comment_id.asc())
415 407 general_comments = q
416 408
417 409 # pick comments we want to render at current version
418 410 c.comment_versions = comments_model.aggregate_comments(
419 411 general_comments, versions, c.at_version_num)
420 412 c.comments = c.comment_versions[c.at_version_num]['until']
421 413
422 414 # INLINE COMMENTS with versions #
423 415 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
424 416 q = q.order_by(ChangesetComment.comment_id.asc())
425 417 inline_comments = q
426 418
427 419 c.inline_versions = comments_model.aggregate_comments(
428 420 inline_comments, versions, c.at_version_num, inline=True)
429 421
430 422 # TODOs
431 423 c.unresolved_comments = CommentsModel() \
432 424 .get_pull_request_unresolved_todos(pull_request)
433 425 c.resolved_comments = CommentsModel() \
434 426 .get_pull_request_resolved_todos(pull_request)
435 427
436 428 # inject latest version
437 429 latest_ver = PullRequest.get_pr_display_object(
438 430 pull_request_latest, pull_request_latest)
439 431
440 432 c.versions = versions + [latest_ver]
441 433
442 434 # if we use version, then do not show later comments
443 435 # than current version
444 436 display_inline_comments = collections.defaultdict(
445 437 lambda: collections.defaultdict(list))
446 438 for co in inline_comments:
447 439 if c.at_version_num:
448 440 # pick comments that are at least UPTO given version, so we
449 441 # don't render comments for higher version
450 442 should_render = co.pull_request_version_id and \
451 443 co.pull_request_version_id <= c.at_version_num
452 444 else:
453 445 # showing all, for 'latest'
454 446 should_render = True
455 447
456 448 if should_render:
457 449 display_inline_comments[co.f_path][co.line_no].append(co)
458 450
459 451 # load diff data into template context, if we use compare mode then
460 452 # diff is calculated based on changes between versions of PR
461 453
462 454 source_repo = pull_request_at_ver.source_repo
463 455 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
464 456
465 457 target_repo = pull_request_at_ver.target_repo
466 458 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
467 459
468 460 if compare:
469 461 # in compare switch the diff base to latest commit from prev version
470 462 target_ref_id = prev_pull_request_display_obj.revisions[0]
471 463
472 464 # despite opening commits for bookmarks/branches/tags, we always
473 465 # convert this to rev to prevent changes after bookmark or branch change
474 466 c.source_ref_type = 'rev'
475 467 c.source_ref = source_ref_id
476 468
477 469 c.target_ref_type = 'rev'
478 470 c.target_ref = target_ref_id
479 471
480 472 c.source_repo = source_repo
481 473 c.target_repo = target_repo
482 474
483 475 c.commit_ranges = []
484 476 source_commit = EmptyCommit()
485 477 target_commit = EmptyCommit()
486 478 c.missing_requirements = False
487 479
488 480 source_scm = source_repo.scm_instance()
489 481 target_scm = target_repo.scm_instance()
490 482
491 483 shadow_scm = None
492 484 try:
493 485 shadow_scm = pull_request_latest.get_shadow_repo()
494 486 except Exception:
495 487 log.debug('Failed to get shadow repo', exc_info=True)
496 488 # try first the existing source_repo, and then shadow
497 489 # repo if we can obtain one
498 490 commits_source_repo = source_scm or shadow_scm
499 491
500 492 c.commits_source_repo = commits_source_repo
501 493 c.ancestor = None # set it to None, to hide it from PR view
502 494
503 495 # empty version means latest, so we keep this to prevent
504 496 # double caching
505 497 version_normalized = version or 'latest'
506 498 from_version_normalized = from_version or 'latest'
507 499
508 500 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
509 501 cache_file_path = diff_cache_exist(
510 502 cache_path, 'pull_request', pull_request_id, version_normalized,
511 503 from_version_normalized, source_ref_id, target_ref_id,
512 504 hide_whitespace_changes, diff_context, c.fulldiff)
513 505
514 506 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
515 507 force_recache = self.get_recache_flag()
516 508
517 509 cached_diff = None
518 510 if caching_enabled:
519 511 cached_diff = load_cached_diff(cache_file_path)
520 512
521 513 has_proper_commit_cache = (
522 514 cached_diff and cached_diff.get('commits')
523 515 and len(cached_diff.get('commits', [])) == 5
524 516 and cached_diff.get('commits')[0]
525 517 and cached_diff.get('commits')[3])
526 518
527 519 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
528 520 diff_commit_cache = \
529 521 (ancestor_commit, commit_cache, missing_requirements,
530 522 source_commit, target_commit) = cached_diff['commits']
531 523 else:
532 524 diff_commit_cache = \
533 525 (ancestor_commit, commit_cache, missing_requirements,
534 526 source_commit, target_commit) = self.get_commits(
535 527 commits_source_repo,
536 528 pull_request_at_ver,
537 529 source_commit,
538 530 source_ref_id,
539 531 source_scm,
540 532 target_commit,
541 533 target_ref_id,
542 534 target_scm)
543 535
544 536 # register our commit range
545 537 for comm in commit_cache.values():
546 538 c.commit_ranges.append(comm)
547 539
548 540 c.missing_requirements = missing_requirements
549 541 c.ancestor_commit = ancestor_commit
550 542 c.statuses = source_repo.statuses(
551 543 [x.raw_id for x in c.commit_ranges])
552 544
553 545 # auto collapse if we have more than limit
554 546 collapse_limit = diffs.DiffProcessor._collapse_commits_over
555 547 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
556 548 c.compare_mode = compare
557 549
558 550 # diff_limit is the old behavior, will cut off the whole diff
559 551 # if the limit is applied otherwise will just hide the
560 552 # big files from the front-end
561 553 diff_limit = c.visual.cut_off_limit_diff
562 554 file_limit = c.visual.cut_off_limit_file
563 555
564 556 c.missing_commits = False
565 557 if (c.missing_requirements
566 558 or isinstance(source_commit, EmptyCommit)
567 559 or source_commit == target_commit):
568 560
569 561 c.missing_commits = True
570 562 else:
571 563 c.inline_comments = display_inline_comments
572 564
573 565 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
574 566 if not force_recache and has_proper_diff_cache:
575 567 c.diffset = cached_diff['diff']
576 568 (ancestor_commit, commit_cache, missing_requirements,
577 569 source_commit, target_commit) = cached_diff['commits']
578 570 else:
579 571 c.diffset = self._get_diffset(
580 572 c.source_repo.repo_name, commits_source_repo,
581 573 source_ref_id, target_ref_id,
582 574 target_commit, source_commit,
583 575 diff_limit, file_limit, c.fulldiff,
584 576 hide_whitespace_changes, diff_context)
585 577
586 578 # save cached diff
587 579 if caching_enabled:
588 580 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
589 581
590 582 c.limited_diff = c.diffset.limited_diff
591 583
592 584 # calculate removed files that are bound to comments
593 585 comment_deleted_files = [
594 586 fname for fname in display_inline_comments
595 587 if fname not in c.diffset.file_stats]
596 588
597 589 c.deleted_files_comments = collections.defaultdict(dict)
598 590 for fname, per_line_comments in display_inline_comments.items():
599 591 if fname in comment_deleted_files:
600 592 c.deleted_files_comments[fname]['stats'] = 0
601 593 c.deleted_files_comments[fname]['comments'] = list()
602 594 for lno, comments in per_line_comments.items():
603 595 c.deleted_files_comments[fname]['comments'].extend(comments)
604 596
605 597 # maybe calculate the range diff
606 598 if c.range_diff_on:
607 599 # TODO(marcink): set whitespace/context
608 600 context_lcl = 3
609 601 ign_whitespace_lcl = False
610 602
611 603 for commit in c.commit_ranges:
612 604 commit2 = commit
613 605 commit1 = commit.first_parent
614 606
615 607 range_diff_cache_file_path = diff_cache_exist(
616 608 cache_path, 'diff', commit.raw_id,
617 609 ign_whitespace_lcl, context_lcl, c.fulldiff)
618 610
619 611 cached_diff = None
620 612 if caching_enabled:
621 613 cached_diff = load_cached_diff(range_diff_cache_file_path)
622 614
623 615 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
624 616 if not force_recache and has_proper_diff_cache:
625 617 diffset = cached_diff['diff']
626 618 else:
627 619 diffset = self._get_range_diffset(
628 620 source_scm, source_repo,
629 621 commit1, commit2, diff_limit, file_limit,
630 622 c.fulldiff, ign_whitespace_lcl, context_lcl
631 623 )
632 624
633 625 # save cached diff
634 626 if caching_enabled:
635 627 cache_diff(range_diff_cache_file_path, diffset, None)
636 628
637 629 c.changes[commit.raw_id] = diffset
638 630
639 631 # this is a hack to properly display links, when creating PR, the
640 632 # compare view and others uses different notation, and
641 633 # compare_commits.mako renders links based on the target_repo.
642 634 # We need to swap that here to generate it properly on the html side
643 635 c.target_repo = c.source_repo
644 636
645 637 c.commit_statuses = ChangesetStatus.STATUSES
646 638
647 639 c.show_version_changes = not pr_closed
648 640 if c.show_version_changes:
649 641 cur_obj = pull_request_at_ver
650 642 prev_obj = prev_pull_request_at_ver
651 643
652 644 old_commit_ids = prev_obj.revisions
653 645 new_commit_ids = cur_obj.revisions
654 646 commit_changes = PullRequestModel()._calculate_commit_id_changes(
655 647 old_commit_ids, new_commit_ids)
656 648 c.commit_changes_summary = commit_changes
657 649
658 650 # calculate the diff for commits between versions
659 651 c.commit_changes = []
660 652 mark = lambda cs, fw: list(
661 653 h.itertools.izip_longest([], cs, fillvalue=fw))
662 654 for c_type, raw_id in mark(commit_changes.added, 'a') \
663 655 + mark(commit_changes.removed, 'r') \
664 656 + mark(commit_changes.common, 'c'):
665 657
666 658 if raw_id in commit_cache:
667 659 commit = commit_cache[raw_id]
668 660 else:
669 661 try:
670 662 commit = commits_source_repo.get_commit(raw_id)
671 663 except CommitDoesNotExistError:
672 664 # in case we fail extracting still use "dummy" commit
673 665 # for display in commit diff
674 666 commit = h.AttributeDict(
675 667 {'raw_id': raw_id,
676 668 'message': 'EMPTY or MISSING COMMIT'})
677 669 c.commit_changes.append([c_type, commit])
678 670
679 671 # current user review statuses for each version
680 672 c.review_versions = {}
681 673 if self._rhodecode_user.user_id in allowed_reviewers:
682 674 for co in general_comments:
683 675 if co.author.user_id == self._rhodecode_user.user_id:
684 676 status = co.status_change
685 677 if status:
686 678 _ver_pr = status[0].comment.pull_request_version_id
687 679 c.review_versions[_ver_pr] = status[0]
688 680
689 681 return self._get_template_context(c)
690 682
691 683 def get_commits(
692 684 self, commits_source_repo, pull_request_at_ver, source_commit,
693 685 source_ref_id, source_scm, target_commit, target_ref_id, target_scm):
694 686 commit_cache = collections.OrderedDict()
695 687 missing_requirements = False
696 688 try:
697 689 pre_load = ["author", "date", "message", "branch", "parents"]
698 690 show_revs = pull_request_at_ver.revisions
699 691 for rev in show_revs:
700 692 comm = commits_source_repo.get_commit(
701 693 commit_id=rev, pre_load=pre_load)
702 694 commit_cache[comm.raw_id] = comm
703 695
704 696 # Order here matters, we first need to get target, and then
705 697 # the source
706 698 target_commit = commits_source_repo.get_commit(
707 699 commit_id=safe_str(target_ref_id))
708 700
709 701 source_commit = commits_source_repo.get_commit(
710 702 commit_id=safe_str(source_ref_id))
711 703 except CommitDoesNotExistError:
712 704 log.warning(
713 705 'Failed to get commit from `{}` repo'.format(
714 706 commits_source_repo), exc_info=True)
715 707 except RepositoryRequirementError:
716 708 log.warning(
717 709 'Failed to get all required data from repo', exc_info=True)
718 710 missing_requirements = True
719 711 ancestor_commit = None
720 712 try:
721 713 ancestor_id = source_scm.get_common_ancestor(
722 714 source_commit.raw_id, target_commit.raw_id, target_scm)
723 715 ancestor_commit = source_scm.get_commit(ancestor_id)
724 716 except Exception:
725 717 ancestor_commit = None
726 718 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
727 719
728 720 def assure_not_empty_repo(self):
729 721 _ = self.request.translate
730 722
731 723 try:
732 724 self.db_repo.scm_instance().get_commit()
733 725 except EmptyRepositoryError:
734 726 h.flash(h.literal(_('There are no commits yet')),
735 727 category='warning')
736 728 raise HTTPFound(
737 729 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
738 730
739 731 @LoginRequired()
740 732 @NotAnonymous()
741 733 @HasRepoPermissionAnyDecorator(
742 734 'repository.read', 'repository.write', 'repository.admin')
743 735 @view_config(
744 736 route_name='pullrequest_new', request_method='GET',
745 737 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
746 738 def pull_request_new(self):
747 739 _ = self.request.translate
748 740 c = self.load_default_context()
749 741
750 742 self.assure_not_empty_repo()
751 743 source_repo = self.db_repo
752 744
753 745 commit_id = self.request.GET.get('commit')
754 746 branch_ref = self.request.GET.get('branch')
755 747 bookmark_ref = self.request.GET.get('bookmark')
756 748
757 749 try:
758 750 source_repo_data = PullRequestModel().generate_repo_data(
759 751 source_repo, commit_id=commit_id,
760 752 branch=branch_ref, bookmark=bookmark_ref,
761 753 translator=self.request.translate)
762 754 except CommitDoesNotExistError as e:
763 755 log.exception(e)
764 756 h.flash(_('Commit does not exist'), 'error')
765 757 raise HTTPFound(
766 758 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
767 759
768 760 default_target_repo = source_repo
769 761
770 762 if source_repo.parent and c.has_origin_repo_read_perm:
771 763 parent_vcs_obj = source_repo.parent.scm_instance()
772 764 if parent_vcs_obj and not parent_vcs_obj.is_empty():
773 765 # change default if we have a parent repo
774 766 default_target_repo = source_repo.parent
775 767
776 768 target_repo_data = PullRequestModel().generate_repo_data(
777 769 default_target_repo, translator=self.request.translate)
778 770
779 771 selected_source_ref = source_repo_data['refs']['selected_ref']
780 772 title_source_ref = ''
781 773 if selected_source_ref:
782 774 title_source_ref = selected_source_ref.split(':', 2)[1]
783 775 c.default_title = PullRequestModel().generate_pullrequest_title(
784 776 source=source_repo.repo_name,
785 777 source_ref=title_source_ref,
786 778 target=default_target_repo.repo_name
787 779 )
788 780
789 781 c.default_repo_data = {
790 782 'source_repo_name': source_repo.repo_name,
791 783 'source_refs_json': json.dumps(source_repo_data),
792 784 'target_repo_name': default_target_repo.repo_name,
793 785 'target_refs_json': json.dumps(target_repo_data),
794 786 }
795 787 c.default_source_ref = selected_source_ref
796 788
797 789 return self._get_template_context(c)
798 790
799 791 @LoginRequired()
800 792 @NotAnonymous()
801 793 @HasRepoPermissionAnyDecorator(
802 794 'repository.read', 'repository.write', 'repository.admin')
803 795 @view_config(
804 796 route_name='pullrequest_repo_refs', request_method='GET',
805 797 renderer='json_ext', xhr=True)
806 798 def pull_request_repo_refs(self):
807 799 self.load_default_context()
808 800 target_repo_name = self.request.matchdict['target_repo_name']
809 801 repo = Repository.get_by_repo_name(target_repo_name)
810 802 if not repo:
811 803 raise HTTPNotFound()
812 804
813 805 target_perm = HasRepoPermissionAny(
814 806 'repository.read', 'repository.write', 'repository.admin')(
815 807 target_repo_name)
816 808 if not target_perm:
817 809 raise HTTPNotFound()
818 810
819 811 return PullRequestModel().generate_repo_data(
820 812 repo, translator=self.request.translate)
821 813
822 814 @LoginRequired()
823 815 @NotAnonymous()
824 816 @HasRepoPermissionAnyDecorator(
825 817 'repository.read', 'repository.write', 'repository.admin')
826 818 @view_config(
827 819 route_name='pullrequest_repo_targets', request_method='GET',
828 820 renderer='json_ext', xhr=True)
829 821 def pullrequest_repo_targets(self):
830 822 _ = self.request.translate
831 823 filter_query = self.request.GET.get('query')
832 824
833 825 # get the parents
834 826 parent_target_repos = []
835 827 if self.db_repo.parent:
836 828 parents_query = Repository.query() \
837 829 .order_by(func.length(Repository.repo_name)) \
838 830 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
839 831
840 832 if filter_query:
841 833 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
842 834 parents_query = parents_query.filter(
843 835 Repository.repo_name.ilike(ilike_expression))
844 836 parents = parents_query.limit(20).all()
845 837
846 838 for parent in parents:
847 839 parent_vcs_obj = parent.scm_instance()
848 840 if parent_vcs_obj and not parent_vcs_obj.is_empty():
849 841 parent_target_repos.append(parent)
850 842
851 843 # get other forks, and repo itself
852 844 query = Repository.query() \
853 845 .order_by(func.length(Repository.repo_name)) \
854 846 .filter(
855 847 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
856 848 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
857 849 ) \
858 850 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
859 851
860 852 if filter_query:
861 853 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
862 854 query = query.filter(Repository.repo_name.ilike(ilike_expression))
863 855
864 856 limit = max(20 - len(parent_target_repos), 5) # not less then 5
865 857 target_repos = query.limit(limit).all()
866 858
867 859 all_target_repos = target_repos + parent_target_repos
868 860
869 861 repos = []
870 862 # This checks permissions to the repositories
871 863 for obj in ScmModel().get_repos(all_target_repos):
872 864 repos.append({
873 865 'id': obj['name'],
874 866 'text': obj['name'],
875 867 'type': 'repo',
876 868 'repo_id': obj['dbrepo']['repo_id'],
877 869 'repo_type': obj['dbrepo']['repo_type'],
878 870 'private': obj['dbrepo']['private'],
879 871
880 872 })
881 873
882 874 data = {
883 875 'more': False,
884 876 'results': [{
885 877 'text': _('Repositories'),
886 878 'children': repos
887 879 }] if repos else []
888 880 }
889 881 return data
890 882
891 883 @LoginRequired()
892 884 @NotAnonymous()
893 885 @HasRepoPermissionAnyDecorator(
894 886 'repository.read', 'repository.write', 'repository.admin')
895 887 @CSRFRequired()
896 888 @view_config(
897 889 route_name='pullrequest_create', request_method='POST',
898 890 renderer=None)
899 891 def pull_request_create(self):
900 892 _ = self.request.translate
901 893 self.assure_not_empty_repo()
902 894 self.load_default_context()
903 895
904 896 controls = peppercorn.parse(self.request.POST.items())
905 897
906 898 try:
907 899 form = PullRequestForm(
908 900 self.request.translate, self.db_repo.repo_id)()
909 901 _form = form.to_python(controls)
910 902 except formencode.Invalid as errors:
911 903 if errors.error_dict.get('revisions'):
912 904 msg = 'Revisions: %s' % errors.error_dict['revisions']
913 905 elif errors.error_dict.get('pullrequest_title'):
914 906 msg = errors.error_dict.get('pullrequest_title')
915 907 else:
916 908 msg = _('Error creating pull request: {}').format(errors)
917 909 log.exception(msg)
918 910 h.flash(msg, 'error')
919 911
920 912 # would rather just go back to form ...
921 913 raise HTTPFound(
922 914 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
923 915
924 916 source_repo = _form['source_repo']
925 917 source_ref = _form['source_ref']
926 918 target_repo = _form['target_repo']
927 919 target_ref = _form['target_ref']
928 920 commit_ids = _form['revisions'][::-1]
929 921
930 922 # find the ancestor for this pr
931 923 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
932 924 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
933 925
934 926 if not (source_db_repo or target_db_repo):
935 927 h.flash(_('source_repo or target repo not found'), category='error')
936 928 raise HTTPFound(
937 929 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
938 930
939 931 # re-check permissions again here
940 932 # source_repo we must have read permissions
941 933
942 934 source_perm = HasRepoPermissionAny(
943 935 'repository.read', 'repository.write', 'repository.admin')(
944 936 source_db_repo.repo_name)
945 937 if not source_perm:
946 938 msg = _('Not Enough permissions to source repo `{}`.'.format(
947 939 source_db_repo.repo_name))
948 940 h.flash(msg, category='error')
949 941 # copy the args back to redirect
950 942 org_query = self.request.GET.mixed()
951 943 raise HTTPFound(
952 944 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
953 945 _query=org_query))
954 946
955 947 # target repo we must have read permissions, and also later on
956 948 # we want to check branch permissions here
957 949 target_perm = HasRepoPermissionAny(
958 950 'repository.read', 'repository.write', 'repository.admin')(
959 951 target_db_repo.repo_name)
960 952 if not target_perm:
961 953 msg = _('Not Enough permissions to target repo `{}`.'.format(
962 954 target_db_repo.repo_name))
963 955 h.flash(msg, category='error')
964 956 # copy the args back to redirect
965 957 org_query = self.request.GET.mixed()
966 958 raise HTTPFound(
967 959 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
968 960 _query=org_query))
969 961
970 962 source_scm = source_db_repo.scm_instance()
971 963 target_scm = target_db_repo.scm_instance()
972 964
973 965 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
974 966 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
975 967
976 968 ancestor = source_scm.get_common_ancestor(
977 969 source_commit.raw_id, target_commit.raw_id, target_scm)
978 970
979 971 # recalculate target ref based on ancestor
980 972 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
981 973 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
982 974
983 975 get_default_reviewers_data, validate_default_reviewers = \
984 976 PullRequestModel().get_reviewer_functions()
985 977
986 978 # recalculate reviewers logic, to make sure we can validate this
987 979 reviewer_rules = get_default_reviewers_data(
988 980 self._rhodecode_db_user, source_db_repo,
989 981 source_commit, target_db_repo, target_commit)
990 982
991 983 given_reviewers = _form['review_members']
992 984 reviewers = validate_default_reviewers(
993 985 given_reviewers, reviewer_rules)
994 986
995 987 pullrequest_title = _form['pullrequest_title']
996 988 title_source_ref = source_ref.split(':', 2)[1]
997 989 if not pullrequest_title:
998 990 pullrequest_title = PullRequestModel().generate_pullrequest_title(
999 991 source=source_repo,
1000 992 source_ref=title_source_ref,
1001 993 target=target_repo
1002 994 )
1003 995
1004 996 description = _form['pullrequest_desc']
1005 997 description_renderer = _form['description_renderer']
1006 998
1007 999 try:
1008 1000 pull_request = PullRequestModel().create(
1009 1001 created_by=self._rhodecode_user.user_id,
1010 1002 source_repo=source_repo,
1011 1003 source_ref=source_ref,
1012 1004 target_repo=target_repo,
1013 1005 target_ref=target_ref,
1014 1006 revisions=commit_ids,
1015 1007 reviewers=reviewers,
1016 1008 title=pullrequest_title,
1017 1009 description=description,
1018 1010 description_renderer=description_renderer,
1019 1011 reviewer_data=reviewer_rules,
1020 1012 auth_user=self._rhodecode_user
1021 1013 )
1022 1014 Session().commit()
1023 1015
1024 1016 h.flash(_('Successfully opened new pull request'),
1025 1017 category='success')
1026 1018 except Exception:
1027 1019 msg = _('Error occurred during creation of this pull request.')
1028 1020 log.exception(msg)
1029 1021 h.flash(msg, category='error')
1030 1022
1031 1023 # copy the args back to redirect
1032 1024 org_query = self.request.GET.mixed()
1033 1025 raise HTTPFound(
1034 1026 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1035 1027 _query=org_query))
1036 1028
1037 1029 raise HTTPFound(
1038 1030 h.route_path('pullrequest_show', repo_name=target_repo,
1039 1031 pull_request_id=pull_request.pull_request_id))
1040 1032
1041 1033 @LoginRequired()
1042 1034 @NotAnonymous()
1043 1035 @HasRepoPermissionAnyDecorator(
1044 1036 'repository.read', 'repository.write', 'repository.admin')
1045 1037 @CSRFRequired()
1046 1038 @view_config(
1047 1039 route_name='pullrequest_update', request_method='POST',
1048 1040 renderer='json_ext')
1049 1041 def pull_request_update(self):
1050 1042 pull_request = PullRequest.get_or_404(
1051 1043 self.request.matchdict['pull_request_id'])
1052 1044 _ = self.request.translate
1053 1045
1054 1046 self.load_default_context()
1055 1047 redirect_url = None
1056 1048
1057 1049 if pull_request.is_closed():
1058 1050 log.debug('update: forbidden because pull request is closed')
1059 1051 msg = _(u'Cannot update closed pull requests.')
1060 1052 h.flash(msg, category='error')
1061 1053 return {'response': True,
1062 1054 'redirect_url': redirect_url}
1063 1055
1064 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1065 log.debug('update: forbidden because pull request is in state %s',
1066 pull_request.pull_request_state)
1067 msg = _(u'Cannot update pull requests in state other than `{}`. '
1068 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1069 pull_request.pull_request_state)
1070 h.flash(msg, category='error')
1071 return {'response': True,
1072 'redirect_url': redirect_url}
1056 is_state_changing = pull_request.is_state_changing()
1073 1057
1074 1058 # only owner or admin can update it
1075 1059 allowed_to_update = PullRequestModel().check_user_update(
1076 1060 pull_request, self._rhodecode_user)
1077 1061 if allowed_to_update:
1078 1062 controls = peppercorn.parse(self.request.POST.items())
1079 1063 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1080 1064
1081 1065 if 'review_members' in controls:
1082 1066 self._update_reviewers(
1083 1067 pull_request, controls['review_members'],
1084 1068 pull_request.reviewer_data)
1085 1069 elif str2bool(self.request.POST.get('update_commits', 'false')):
1070 if is_state_changing:
1071 log.debug('commits update: forbidden because pull request is in state %s',
1072 pull_request.pull_request_state)
1073 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1074 u'Current state is: `{}`').format(
1075 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1076 h.flash(msg, category='error')
1077 return {'response': True,
1078 'redirect_url': redirect_url}
1079
1086 1080 self._update_commits(pull_request)
1087 1081 if force_refresh:
1088 1082 redirect_url = h.route_path(
1089 1083 'pullrequest_show', repo_name=self.db_repo_name,
1090 1084 pull_request_id=pull_request.pull_request_id,
1091 1085 _query={"force_refresh": 1})
1092 1086 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1093 1087 self._edit_pull_request(pull_request)
1094 1088 else:
1095 1089 raise HTTPBadRequest()
1096 1090
1097 1091 return {'response': True,
1098 1092 'redirect_url': redirect_url}
1099 1093 raise HTTPForbidden()
1100 1094
1101 1095 def _edit_pull_request(self, pull_request):
1102 1096 _ = self.request.translate
1103 1097
1104 1098 try:
1105 1099 PullRequestModel().edit(
1106 1100 pull_request,
1107 1101 self.request.POST.get('title'),
1108 1102 self.request.POST.get('description'),
1109 1103 self.request.POST.get('description_renderer'),
1110 1104 self._rhodecode_user)
1111 1105 except ValueError:
1112 1106 msg = _(u'Cannot update closed pull requests.')
1113 1107 h.flash(msg, category='error')
1114 1108 return
1115 1109 else:
1116 1110 Session().commit()
1117 1111
1118 1112 msg = _(u'Pull request title & description updated.')
1119 1113 h.flash(msg, category='success')
1120 1114 return
1121 1115
1122 1116 def _update_commits(self, pull_request):
1123 1117 _ = self.request.translate
1124 1118
1125 1119 with pull_request.set_state(PullRequest.STATE_UPDATING):
1126 1120 resp = PullRequestModel().update_commits(pull_request)
1127 1121
1128 1122 if resp.executed:
1129 1123
1130 1124 if resp.target_changed and resp.source_changed:
1131 1125 changed = 'target and source repositories'
1132 1126 elif resp.target_changed and not resp.source_changed:
1133 1127 changed = 'target repository'
1134 1128 elif not resp.target_changed and resp.source_changed:
1135 1129 changed = 'source repository'
1136 1130 else:
1137 1131 changed = 'nothing'
1138 1132
1139 1133 msg = _(u'Pull request updated to "{source_commit_id}" with '
1140 1134 u'{count_added} added, {count_removed} removed commits. '
1141 1135 u'Source of changes: {change_source}')
1142 1136 msg = msg.format(
1143 1137 source_commit_id=pull_request.source_ref_parts.commit_id,
1144 1138 count_added=len(resp.changes.added),
1145 1139 count_removed=len(resp.changes.removed),
1146 1140 change_source=changed)
1147 1141 h.flash(msg, category='success')
1148 1142
1149 1143 channel = '/repo${}$/pr/{}'.format(
1150 1144 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1151 1145 message = msg + (
1152 1146 ' - <a onclick="window.location.reload()">'
1153 1147 '<strong>{}</strong></a>'.format(_('Reload page')))
1154 1148 channelstream.post_message(
1155 1149 channel, message, self._rhodecode_user.username,
1156 1150 registry=self.request.registry)
1157 1151 else:
1158 1152 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1159 1153 warning_reasons = [
1160 1154 UpdateFailureReason.NO_CHANGE,
1161 1155 UpdateFailureReason.WRONG_REF_TYPE,
1162 1156 ]
1163 1157 category = 'warning' if resp.reason in warning_reasons else 'error'
1164 1158 h.flash(msg, category=category)
1165 1159
1166 1160 @LoginRequired()
1167 1161 @NotAnonymous()
1168 1162 @HasRepoPermissionAnyDecorator(
1169 1163 'repository.read', 'repository.write', 'repository.admin')
1170 1164 @CSRFRequired()
1171 1165 @view_config(
1172 1166 route_name='pullrequest_merge', request_method='POST',
1173 1167 renderer='json_ext')
1174 1168 def pull_request_merge(self):
1175 1169 """
1176 1170 Merge will perform a server-side merge of the specified
1177 1171 pull request, if the pull request is approved and mergeable.
1178 1172 After successful merging, the pull request is automatically
1179 1173 closed, with a relevant comment.
1180 1174 """
1181 1175 pull_request = PullRequest.get_or_404(
1182 1176 self.request.matchdict['pull_request_id'])
1183 1177 _ = self.request.translate
1184 1178
1185 if pull_request.pull_request_state != PullRequest.STATE_CREATED:
1179 if pull_request.is_state_changing():
1186 1180 log.debug('show: forbidden because pull request is in state %s',
1187 1181 pull_request.pull_request_state)
1188 1182 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1189 1183 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1190 1184 pull_request.pull_request_state)
1191 1185 h.flash(msg, category='error')
1192 1186 raise HTTPFound(
1193 1187 h.route_path('pullrequest_show',
1194 1188 repo_name=pull_request.target_repo.repo_name,
1195 1189 pull_request_id=pull_request.pull_request_id))
1196 1190
1197 1191 self.load_default_context()
1198 1192
1199 1193 with pull_request.set_state(PullRequest.STATE_UPDATING):
1200 1194 check = MergeCheck.validate(
1201 1195 pull_request, auth_user=self._rhodecode_user,
1202 1196 translator=self.request.translate)
1203 1197 merge_possible = not check.failed
1204 1198
1205 1199 for err_type, error_msg in check.errors:
1206 1200 h.flash(error_msg, category=err_type)
1207 1201
1208 1202 if merge_possible:
1209 1203 log.debug("Pre-conditions checked, trying to merge.")
1210 1204 extras = vcs_operation_context(
1211 1205 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1212 1206 username=self._rhodecode_db_user.username, action='push',
1213 1207 scm=pull_request.target_repo.repo_type)
1214 1208 with pull_request.set_state(PullRequest.STATE_UPDATING):
1215 1209 self._merge_pull_request(
1216 1210 pull_request, self._rhodecode_db_user, extras)
1217 1211 else:
1218 1212 log.debug("Pre-conditions failed, NOT merging.")
1219 1213
1220 1214 raise HTTPFound(
1221 1215 h.route_path('pullrequest_show',
1222 1216 repo_name=pull_request.target_repo.repo_name,
1223 1217 pull_request_id=pull_request.pull_request_id))
1224 1218
1225 1219 def _merge_pull_request(self, pull_request, user, extras):
1226 1220 _ = self.request.translate
1227 1221 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1228 1222
1229 1223 if merge_resp.executed:
1230 1224 log.debug("The merge was successful, closing the pull request.")
1231 1225 PullRequestModel().close_pull_request(
1232 1226 pull_request.pull_request_id, user)
1233 1227 Session().commit()
1234 1228 msg = _('Pull request was successfully merged and closed.')
1235 1229 h.flash(msg, category='success')
1236 1230 else:
1237 1231 log.debug(
1238 1232 "The merge was not successful. Merge response: %s", merge_resp)
1239 1233 msg = merge_resp.merge_status_message
1240 1234 h.flash(msg, category='error')
1241 1235
1242 1236 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1243 1237 _ = self.request.translate
1244 1238
1245 1239 get_default_reviewers_data, validate_default_reviewers = \
1246 1240 PullRequestModel().get_reviewer_functions()
1247 1241
1248 1242 try:
1249 1243 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1250 1244 except ValueError as e:
1251 1245 log.error('Reviewers Validation: {}'.format(e))
1252 1246 h.flash(e, category='error')
1253 1247 return
1254 1248
1255 1249 old_calculated_status = pull_request.calculated_review_status()
1256 1250 PullRequestModel().update_reviewers(
1257 1251 pull_request, reviewers, self._rhodecode_user)
1258 1252 h.flash(_('Pull request reviewers updated.'), category='success')
1259 1253 Session().commit()
1260 1254
1261 1255 # trigger status changed if change in reviewers changes the status
1262 1256 calculated_status = pull_request.calculated_review_status()
1263 1257 if old_calculated_status != calculated_status:
1264 1258 PullRequestModel().trigger_pull_request_hook(
1265 1259 pull_request, self._rhodecode_user, 'review_status_change',
1266 1260 data={'status': calculated_status})
1267 1261
1268 1262 @LoginRequired()
1269 1263 @NotAnonymous()
1270 1264 @HasRepoPermissionAnyDecorator(
1271 1265 'repository.read', 'repository.write', 'repository.admin')
1272 1266 @CSRFRequired()
1273 1267 @view_config(
1274 1268 route_name='pullrequest_delete', request_method='POST',
1275 1269 renderer='json_ext')
1276 1270 def pull_request_delete(self):
1277 1271 _ = self.request.translate
1278 1272
1279 1273 pull_request = PullRequest.get_or_404(
1280 1274 self.request.matchdict['pull_request_id'])
1281 1275 self.load_default_context()
1282 1276
1283 1277 pr_closed = pull_request.is_closed()
1284 1278 allowed_to_delete = PullRequestModel().check_user_delete(
1285 1279 pull_request, self._rhodecode_user) and not pr_closed
1286 1280
1287 1281 # only owner can delete it !
1288 1282 if allowed_to_delete:
1289 1283 PullRequestModel().delete(pull_request, self._rhodecode_user)
1290 1284 Session().commit()
1291 1285 h.flash(_('Successfully deleted pull request'),
1292 1286 category='success')
1293 1287 raise HTTPFound(h.route_path('pullrequest_show_all',
1294 1288 repo_name=self.db_repo_name))
1295 1289
1296 1290 log.warning('user %s tried to delete pull request without access',
1297 1291 self._rhodecode_user)
1298 1292 raise HTTPNotFound()
1299 1293
1300 1294 @LoginRequired()
1301 1295 @NotAnonymous()
1302 1296 @HasRepoPermissionAnyDecorator(
1303 1297 'repository.read', 'repository.write', 'repository.admin')
1304 1298 @CSRFRequired()
1305 1299 @view_config(
1306 1300 route_name='pullrequest_comment_create', request_method='POST',
1307 1301 renderer='json_ext')
1308 1302 def pull_request_comment_create(self):
1309 1303 _ = self.request.translate
1310 1304
1311 1305 pull_request = PullRequest.get_or_404(
1312 1306 self.request.matchdict['pull_request_id'])
1313 1307 pull_request_id = pull_request.pull_request_id
1314 1308
1315 1309 if pull_request.is_closed():
1316 1310 log.debug('comment: forbidden because pull request is closed')
1317 1311 raise HTTPForbidden()
1318 1312
1319 1313 allowed_to_comment = PullRequestModel().check_user_comment(
1320 1314 pull_request, self._rhodecode_user)
1321 1315 if not allowed_to_comment:
1322 1316 log.debug(
1323 1317 'comment: forbidden because pull request is from forbidden repo')
1324 1318 raise HTTPForbidden()
1325 1319
1326 1320 c = self.load_default_context()
1327 1321
1328 1322 status = self.request.POST.get('changeset_status', None)
1329 1323 text = self.request.POST.get('text')
1330 1324 comment_type = self.request.POST.get('comment_type')
1331 1325 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1332 1326 close_pull_request = self.request.POST.get('close_pull_request')
1333 1327
1334 1328 # the logic here should work like following, if we submit close
1335 1329 # pr comment, use `close_pull_request_with_comment` function
1336 1330 # else handle regular comment logic
1337 1331
1338 1332 if close_pull_request:
1339 1333 # only owner or admin or person with write permissions
1340 1334 allowed_to_close = PullRequestModel().check_user_update(
1341 1335 pull_request, self._rhodecode_user)
1342 1336 if not allowed_to_close:
1343 1337 log.debug('comment: forbidden because not allowed to close '
1344 1338 'pull request %s', pull_request_id)
1345 1339 raise HTTPForbidden()
1346 1340
1347 1341 # This also triggers `review_status_change`
1348 1342 comment, status = PullRequestModel().close_pull_request_with_comment(
1349 1343 pull_request, self._rhodecode_user, self.db_repo, message=text,
1350 1344 auth_user=self._rhodecode_user)
1351 1345 Session().flush()
1352 1346
1353 1347 PullRequestModel().trigger_pull_request_hook(
1354 1348 pull_request, self._rhodecode_user, 'comment',
1355 1349 data={'comment': comment})
1356 1350
1357 1351 else:
1358 1352 # regular comment case, could be inline, or one with status.
1359 1353 # for that one we check also permissions
1360 1354
1361 1355 allowed_to_change_status = PullRequestModel().check_user_change_status(
1362 1356 pull_request, self._rhodecode_user)
1363 1357
1364 1358 if status and allowed_to_change_status:
1365 1359 message = (_('Status change %(transition_icon)s %(status)s')
1366 1360 % {'transition_icon': '>',
1367 1361 'status': ChangesetStatus.get_status_lbl(status)})
1368 1362 text = text or message
1369 1363
1370 1364 comment = CommentsModel().create(
1371 1365 text=text,
1372 1366 repo=self.db_repo.repo_id,
1373 1367 user=self._rhodecode_user.user_id,
1374 1368 pull_request=pull_request,
1375 1369 f_path=self.request.POST.get('f_path'),
1376 1370 line_no=self.request.POST.get('line'),
1377 1371 status_change=(ChangesetStatus.get_status_lbl(status)
1378 1372 if status and allowed_to_change_status else None),
1379 1373 status_change_type=(status
1380 1374 if status and allowed_to_change_status else None),
1381 1375 comment_type=comment_type,
1382 1376 resolves_comment_id=resolves_comment_id,
1383 1377 auth_user=self._rhodecode_user
1384 1378 )
1385 1379
1386 1380 if allowed_to_change_status:
1387 1381 # calculate old status before we change it
1388 1382 old_calculated_status = pull_request.calculated_review_status()
1389 1383
1390 1384 # get status if set !
1391 1385 if status:
1392 1386 ChangesetStatusModel().set_status(
1393 1387 self.db_repo.repo_id,
1394 1388 status,
1395 1389 self._rhodecode_user.user_id,
1396 1390 comment,
1397 1391 pull_request=pull_request
1398 1392 )
1399 1393
1400 1394 Session().flush()
1401 1395 # this is somehow required to get access to some relationship
1402 1396 # loaded on comment
1403 1397 Session().refresh(comment)
1404 1398
1405 1399 PullRequestModel().trigger_pull_request_hook(
1406 1400 pull_request, self._rhodecode_user, 'comment',
1407 1401 data={'comment': comment})
1408 1402
1409 1403 # we now calculate the status of pull request, and based on that
1410 1404 # calculation we set the commits status
1411 1405 calculated_status = pull_request.calculated_review_status()
1412 1406 if old_calculated_status != calculated_status:
1413 1407 PullRequestModel().trigger_pull_request_hook(
1414 1408 pull_request, self._rhodecode_user, 'review_status_change',
1415 1409 data={'status': calculated_status})
1416 1410
1417 1411 Session().commit()
1418 1412
1419 1413 data = {
1420 1414 'target_id': h.safeid(h.safe_unicode(
1421 1415 self.request.POST.get('f_path'))),
1422 1416 }
1423 1417 if comment:
1424 1418 c.co = comment
1425 1419 rendered_comment = render(
1426 1420 'rhodecode:templates/changeset/changeset_comment_block.mako',
1427 1421 self._get_template_context(c), self.request)
1428 1422
1429 1423 data.update(comment.get_dict())
1430 1424 data.update({'rendered_text': rendered_comment})
1431 1425
1432 1426 return data
1433 1427
1434 1428 @LoginRequired()
1435 1429 @NotAnonymous()
1436 1430 @HasRepoPermissionAnyDecorator(
1437 1431 'repository.read', 'repository.write', 'repository.admin')
1438 1432 @CSRFRequired()
1439 1433 @view_config(
1440 1434 route_name='pullrequest_comment_delete', request_method='POST',
1441 1435 renderer='json_ext')
1442 1436 def pull_request_comment_delete(self):
1443 1437 pull_request = PullRequest.get_or_404(
1444 1438 self.request.matchdict['pull_request_id'])
1445 1439
1446 1440 comment = ChangesetComment.get_or_404(
1447 1441 self.request.matchdict['comment_id'])
1448 1442 comment_id = comment.comment_id
1449 1443
1450 1444 if pull_request.is_closed():
1451 1445 log.debug('comment: forbidden because pull request is closed')
1452 1446 raise HTTPForbidden()
1453 1447
1454 1448 if not comment:
1455 1449 log.debug('Comment with id:%s not found, skipping', comment_id)
1456 1450 # comment already deleted in another call probably
1457 1451 return True
1458 1452
1459 1453 if comment.pull_request.is_closed():
1460 1454 # don't allow deleting comments on closed pull request
1461 1455 raise HTTPForbidden()
1462 1456
1463 1457 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1464 1458 super_admin = h.HasPermissionAny('hg.admin')()
1465 1459 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1466 1460 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1467 1461 comment_repo_admin = is_repo_admin and is_repo_comment
1468 1462
1469 1463 if super_admin or comment_owner or comment_repo_admin:
1470 1464 old_calculated_status = comment.pull_request.calculated_review_status()
1471 1465 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1472 1466 Session().commit()
1473 1467 calculated_status = comment.pull_request.calculated_review_status()
1474 1468 if old_calculated_status != calculated_status:
1475 1469 PullRequestModel().trigger_pull_request_hook(
1476 1470 comment.pull_request, self._rhodecode_user, 'review_status_change',
1477 1471 data={'status': calculated_status})
1478 1472 return True
1479 1473 else:
1480 1474 log.warning('No permissions for user %s to delete comment_id: %s',
1481 1475 self._rhodecode_db_user, comment_id)
1482 1476 raise HTTPNotFound()
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,2925 +1,2937 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'variables';
9 9 @import 'bootstrap-variables';
10 10 @import 'form-bootstrap';
11 11 @import 'codemirror';
12 12 @import 'legacy_code_styles';
13 13 @import 'readme-box';
14 14 @import 'progress-bar';
15 15
16 16 @import 'type';
17 17 @import 'alerts';
18 18 @import 'buttons';
19 19 @import 'tags';
20 20 @import 'code-block';
21 21 @import 'examples';
22 22 @import 'login';
23 23 @import 'main-content';
24 24 @import 'select2';
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 28 @import 'deform';
29 29 @import 'tooltips';
30 30
31 31 //--- BASE ------------------//
32 32 .noscript-error {
33 33 top: 0;
34 34 left: 0;
35 35 width: 100%;
36 36 z-index: 101;
37 37 text-align: center;
38 38 font-size: 120%;
39 39 color: white;
40 40 background-color: @alert2;
41 41 padding: 5px 0 5px 0;
42 42 font-weight: @text-semibold-weight;
43 43 font-family: @text-semibold;
44 44 }
45 45
46 46 html {
47 47 display: table;
48 48 height: 100%;
49 49 width: 100%;
50 50 }
51 51
52 52 body {
53 53 display: table-cell;
54 54 width: 100%;
55 55 }
56 56
57 57 //--- LAYOUT ------------------//
58 58
59 59 .hidden{
60 60 display: none !important;
61 61 }
62 62
63 63 .box{
64 64 float: left;
65 65 width: 100%;
66 66 }
67 67
68 68 .browser-header {
69 69 clear: both;
70 70 }
71 71 .main {
72 72 clear: both;
73 73 padding:0 0 @pagepadding;
74 74 height: auto;
75 75
76 76 &:after { //clearfix
77 77 content:"";
78 78 clear:both;
79 79 width:100%;
80 80 display:block;
81 81 }
82 82 }
83 83
84 84 .action-link{
85 85 margin-left: @padding;
86 86 padding-left: @padding;
87 87 border-left: @border-thickness solid @border-default-color;
88 88 }
89 89
90 90 input + .action-link, .action-link.first{
91 91 border-left: none;
92 92 }
93 93
94 94 .action-link.last{
95 95 margin-right: @padding;
96 96 padding-right: @padding;
97 97 }
98 98
99 99 .action-link.active,
100 100 .action-link.active a{
101 101 color: @grey4;
102 102 }
103 103
104 104 .action-link.disabled {
105 105 color: @grey4;
106 106 cursor: inherit;
107 107 }
108 108
109 109
110 110 .clipboard-action {
111 111 cursor: pointer;
112 112 margin-left: 5px;
113 113
114 114 &:not(.no-grey) {
115 115
116 116 &:hover {
117 117 color: @grey2;
118 118 }
119 119 color: @grey4;
120 120 }
121 121 }
122 122
123 123 ul.simple-list{
124 124 list-style: none;
125 125 margin: 0;
126 126 padding: 0;
127 127 }
128 128
129 129 .main-content {
130 130 padding-bottom: @pagepadding;
131 131 }
132 132
133 133 .wide-mode-wrapper {
134 134 max-width:4000px !important;
135 135 }
136 136
137 137 .wrapper {
138 138 position: relative;
139 139 max-width: @wrapper-maxwidth;
140 140 margin: 0 auto;
141 141 }
142 142
143 143 #content {
144 144 clear: both;
145 145 padding: 0 @contentpadding;
146 146 }
147 147
148 148 .advanced-settings-fields{
149 149 input{
150 150 margin-left: @textmargin;
151 151 margin-right: @padding/2;
152 152 }
153 153 }
154 154
155 155 .cs_files_title {
156 156 margin: @pagepadding 0 0;
157 157 }
158 158
159 159 input.inline[type="file"] {
160 160 display: inline;
161 161 }
162 162
163 163 .error_page {
164 164 margin: 10% auto;
165 165
166 166 h1 {
167 167 color: @grey2;
168 168 }
169 169
170 170 .alert {
171 171 margin: @padding 0;
172 172 }
173 173
174 174 .error-branding {
175 175 color: @grey4;
176 176 font-weight: @text-semibold-weight;
177 177 font-family: @text-semibold;
178 178 }
179 179
180 180 .error_message {
181 181 font-family: @text-regular;
182 182 }
183 183
184 184 .sidebar {
185 185 min-height: 275px;
186 186 margin: 0;
187 187 padding: 0 0 @sidebarpadding @sidebarpadding;
188 188 border: none;
189 189 }
190 190
191 191 .main-content {
192 192 position: relative;
193 193 margin: 0 @sidebarpadding @sidebarpadding;
194 194 padding: 0 0 0 @sidebarpadding;
195 195 border-left: @border-thickness solid @grey5;
196 196
197 197 @media (max-width:767px) {
198 198 clear: both;
199 199 width: 100%;
200 200 margin: 0;
201 201 border: none;
202 202 }
203 203 }
204 204
205 205 .inner-column {
206 206 float: left;
207 207 width: 29.75%;
208 208 min-height: 150px;
209 209 margin: @sidebarpadding 2% 0 0;
210 210 padding: 0 2% 0 0;
211 211 border-right: @border-thickness solid @grey5;
212 212
213 213 @media (max-width:767px) {
214 214 clear: both;
215 215 width: 100%;
216 216 border: none;
217 217 }
218 218
219 219 ul {
220 220 padding-left: 1.25em;
221 221 }
222 222
223 223 &:last-child {
224 224 margin: @sidebarpadding 0 0;
225 225 border: none;
226 226 }
227 227
228 228 h4 {
229 229 margin: 0 0 @padding;
230 230 font-weight: @text-semibold-weight;
231 231 font-family: @text-semibold;
232 232 }
233 233 }
234 234 }
235 235 .error-page-logo {
236 236 width: 130px;
237 237 height: 160px;
238 238 }
239 239
240 240 // HEADER
241 241 .header {
242 242
243 243 // TODO: johbo: Fix login pages, so that they work without a min-height
244 244 // for the header and then remove the min-height. I chose a smaller value
245 245 // intentionally here to avoid rendering issues in the main navigation.
246 246 min-height: 49px;
247 247 min-width: 1024px;
248 248
249 249 position: relative;
250 250 vertical-align: bottom;
251 251 padding: 0 @header-padding;
252 252 background-color: @grey1;
253 253 color: @grey5;
254 254
255 255 .title {
256 256 overflow: visible;
257 257 }
258 258
259 259 &:before,
260 260 &:after {
261 261 content: "";
262 262 clear: both;
263 263 width: 100%;
264 264 }
265 265
266 266 // TODO: johbo: Avoids breaking "Repositories" chooser
267 267 .select2-container .select2-choice .select2-arrow {
268 268 display: none;
269 269 }
270 270 }
271 271
272 272 #header-inner {
273 273 &.title {
274 274 margin: 0;
275 275 }
276 276 &:before,
277 277 &:after {
278 278 content: "";
279 279 clear: both;
280 280 }
281 281 }
282 282
283 283 // Gists
284 284 #files_data {
285 285 clear: both; //for firefox
286 286 padding-top: 10px;
287 287 }
288 288
289 289 #gistid {
290 290 margin-right: @padding;
291 291 }
292 292
293 293 // Global Settings Editor
294 294 .textarea.editor {
295 295 float: left;
296 296 position: relative;
297 297 max-width: @texteditor-width;
298 298
299 299 select {
300 300 position: absolute;
301 301 top:10px;
302 302 right:0;
303 303 }
304 304
305 305 .CodeMirror {
306 306 margin: 0;
307 307 }
308 308
309 309 .help-block {
310 310 margin: 0 0 @padding;
311 311 padding:.5em;
312 312 background-color: @grey6;
313 313 &.pre-formatting {
314 314 white-space: pre;
315 315 }
316 316 }
317 317 }
318 318
319 319 ul.auth_plugins {
320 320 margin: @padding 0 @padding @legend-width;
321 321 padding: 0;
322 322
323 323 li {
324 324 margin-bottom: @padding;
325 325 line-height: 1em;
326 326 list-style-type: none;
327 327
328 328 .auth_buttons .btn {
329 329 margin-right: @padding;
330 330 }
331 331
332 332 }
333 333 }
334 334
335 335
336 336 // My Account PR list
337 337
338 338 #show_closed {
339 339 margin: 0 1em 0 0;
340 340 }
341 341
342 342 #pull_request_list_table {
343 343 .closed {
344 344 background-color: @grey6;
345 345 }
346 346
347 347 .state-creating,
348 348 .state-updating,
349 349 .state-merging
350 350 {
351 351 background-color: @grey6;
352 352 }
353 353
354 354 .td-status {
355 355 padding-left: .5em;
356 356 }
357 357 .log-container .truncate {
358 358 height: 2.75em;
359 359 white-space: pre-line;
360 360 }
361 361 table.rctable .user {
362 362 padding-left: 0;
363 363 }
364 364 table.rctable {
365 365 td.td-description,
366 366 .rc-user {
367 367 min-width: auto;
368 368 }
369 369 }
370 370 }
371 371
372 372 // Pull Requests
373 373
374 374 .pullrequests_section_head {
375 375 display: block;
376 376 clear: both;
377 377 margin: @padding 0;
378 378 font-weight: @text-bold-weight;
379 379 font-family: @text-bold;
380 380 }
381 381
382 382 .pr-origininfo, .pr-targetinfo {
383 383 position: relative;
384 384
385 385 .tag {
386 386 display: inline-block;
387 387 margin: 0 1em .5em 0;
388 388 }
389 389
390 390 .clone-url {
391 391 display: inline-block;
392 392 margin: 0 0 .5em 0;
393 393 padding: 0;
394 394 line-height: 1.2em;
395 395 }
396 396 }
397 397
398 398 .pr-mergeinfo {
399 399 min-width: 95% !important;
400 400 padding: 0 !important;
401 401 border: 0;
402 402 }
403 403 .pr-mergeinfo-copy {
404 404 padding: 0 0;
405 405 }
406 406
407 407 .pr-pullinfo {
408 408 min-width: 95% !important;
409 409 padding: 0 !important;
410 410 border: 0;
411 411 }
412 412 .pr-pullinfo-copy {
413 413 padding: 0 0;
414 414 }
415 415
416 416
417 #pr-title-input {
418 width: 72%;
417 .pr-title-input {
418 width: 80%;
419 419 font-size: 1em;
420 margin: 0;
421 padding: 0 0 0 @padding/4;
420 margin: 0 0 4px 0;
421 padding: 0;
422 422 line-height: 1.7em;
423 423 color: @text-color;
424 424 letter-spacing: .02em;
425 425 font-weight: @text-bold-weight;
426 426 font-family: @text-bold;
427
428 &:hover {
429 box-shadow: none;
430 }
431 }
432
433 #pr-title {
434 input {
435 border: 1px transparent;
436 color: black;
437 opacity: 1
438 }
427 439 }
428 440
429 441 #pullrequest_title {
430 442 width: 100%;
431 443 box-sizing: border-box;
432 444 }
433 445
434 446 #pr_open_message {
435 447 border: @border-thickness solid #fff;
436 448 border-radius: @border-radius;
437 449 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
438 450 text-align: left;
439 451 overflow: hidden;
440 452 }
441 453
442 454 .pr-submit-button {
443 455 float: right;
444 456 margin: 0 0 0 5px;
445 457 }
446 458
447 459 .pr-spacing-container {
448 460 padding: 20px;
449 461 clear: both
450 462 }
451 463
452 464 #pr-description-input {
453 465 margin-bottom: 0;
454 466 }
455 467
456 468 .pr-description-label {
457 469 vertical-align: top;
458 470 }
459 471
460 472 .perms_section_head {
461 473 min-width: 625px;
462 474
463 475 h2 {
464 476 margin-bottom: 0;
465 477 }
466 478
467 479 .label-checkbox {
468 480 float: left;
469 481 }
470 482
471 483 &.field {
472 484 margin: @space 0 @padding;
473 485 }
474 486
475 487 &:first-child.field {
476 488 margin-top: 0;
477 489
478 490 .label {
479 491 margin-top: 0;
480 492 padding-top: 0;
481 493 }
482 494
483 495 .radios {
484 496 padding-top: 0;
485 497 }
486 498 }
487 499
488 500 .radios {
489 501 position: relative;
490 502 width: 505px;
491 503 }
492 504 }
493 505
494 506 //--- MODULES ------------------//
495 507
496 508
497 509 // Server Announcement
498 510 #server-announcement {
499 511 width: 95%;
500 512 margin: @padding auto;
501 513 padding: @padding;
502 514 border-width: 2px;
503 515 border-style: solid;
504 516 .border-radius(2px);
505 517 font-weight: @text-bold-weight;
506 518 font-family: @text-bold;
507 519
508 520 &.info { border-color: @alert4; background-color: @alert4-inner; }
509 521 &.warning { border-color: @alert3; background-color: @alert3-inner; }
510 522 &.error { border-color: @alert2; background-color: @alert2-inner; }
511 523 &.success { border-color: @alert1; background-color: @alert1-inner; }
512 524 &.neutral { border-color: @grey3; background-color: @grey6; }
513 525 }
514 526
515 527 // Fixed Sidebar Column
516 528 .sidebar-col-wrapper {
517 529 padding-left: @sidebar-all-width;
518 530
519 531 .sidebar {
520 532 width: @sidebar-width;
521 533 margin-left: -@sidebar-all-width;
522 534 }
523 535 }
524 536
525 537 .sidebar-col-wrapper.scw-small {
526 538 padding-left: @sidebar-small-all-width;
527 539
528 540 .sidebar {
529 541 width: @sidebar-small-width;
530 542 margin-left: -@sidebar-small-all-width;
531 543 }
532 544 }
533 545
534 546
535 547 // FOOTER
536 548 #footer {
537 549 padding: 0;
538 550 text-align: center;
539 551 vertical-align: middle;
540 552 color: @grey2;
541 553 font-size: 11px;
542 554
543 555 p {
544 556 margin: 0;
545 557 padding: 1em;
546 558 line-height: 1em;
547 559 }
548 560
549 561 .server-instance { //server instance
550 562 display: none;
551 563 }
552 564
553 565 .title {
554 566 float: none;
555 567 margin: 0 auto;
556 568 }
557 569 }
558 570
559 571 button.close {
560 572 padding: 0;
561 573 cursor: pointer;
562 574 background: transparent;
563 575 border: 0;
564 576 .box-shadow(none);
565 577 -webkit-appearance: none;
566 578 }
567 579
568 580 .close {
569 581 float: right;
570 582 font-size: 21px;
571 583 font-family: @text-bootstrap;
572 584 line-height: 1em;
573 585 font-weight: bold;
574 586 color: @grey2;
575 587
576 588 &:hover,
577 589 &:focus {
578 590 color: @grey1;
579 591 text-decoration: none;
580 592 cursor: pointer;
581 593 }
582 594 }
583 595
584 596 // GRID
585 597 .sorting,
586 598 .sorting_desc,
587 599 .sorting_asc {
588 600 cursor: pointer;
589 601 }
590 602 .sorting_desc:after {
591 603 content: "\00A0\25B2";
592 604 font-size: .75em;
593 605 }
594 606 .sorting_asc:after {
595 607 content: "\00A0\25BC";
596 608 font-size: .68em;
597 609 }
598 610
599 611
600 612 .user_auth_tokens {
601 613
602 614 &.truncate {
603 615 white-space: nowrap;
604 616 overflow: hidden;
605 617 text-overflow: ellipsis;
606 618 }
607 619
608 620 .fields .field .input {
609 621 margin: 0;
610 622 }
611 623
612 624 input#description {
613 625 width: 100px;
614 626 margin: 0;
615 627 }
616 628
617 629 .drop-menu {
618 630 // TODO: johbo: Remove this, should work out of the box when
619 631 // having multiple inputs inline
620 632 margin: 0 0 0 5px;
621 633 }
622 634 }
623 635 #user_list_table {
624 636 .closed {
625 637 background-color: @grey6;
626 638 }
627 639 }
628 640
629 641
630 642 input, textarea {
631 643 &.disabled {
632 644 opacity: .5;
633 645 }
634 646
635 647 &:hover {
636 648 border-color: @grey3;
637 649 box-shadow: @button-shadow;
638 650 }
639 651
640 652 &:focus {
641 653 border-color: @rcblue;
642 654 box-shadow: @button-shadow;
643 655 }
644 656 }
645 657
646 658 // remove extra padding in firefox
647 659 input::-moz-focus-inner { border:0; padding:0 }
648 660
649 661 .adjacent input {
650 662 margin-bottom: @padding;
651 663 }
652 664
653 665 .permissions_boxes {
654 666 display: block;
655 667 }
656 668
657 669 //FORMS
658 670
659 671 .medium-inline,
660 672 input#description.medium-inline {
661 673 display: inline;
662 674 width: @medium-inline-input-width;
663 675 min-width: 100px;
664 676 }
665 677
666 678 select {
667 679 //reset
668 680 -webkit-appearance: none;
669 681 -moz-appearance: none;
670 682
671 683 display: inline-block;
672 684 height: 28px;
673 685 width: auto;
674 686 margin: 0 @padding @padding 0;
675 687 padding: 0 18px 0 8px;
676 688 line-height:1em;
677 689 font-size: @basefontsize;
678 690 border: @border-thickness solid @grey5;
679 691 border-radius: @border-radius;
680 692 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
681 693 color: @grey4;
682 694 box-shadow: @button-shadow;
683 695
684 696 &:after {
685 697 content: "\00A0\25BE";
686 698 }
687 699
688 700 &:focus, &:hover {
689 701 outline: none;
690 702 border-color: @grey4;
691 703 color: @rcdarkblue;
692 704 }
693 705 }
694 706
695 707 option {
696 708 &:focus {
697 709 outline: none;
698 710 }
699 711 }
700 712
701 713 input,
702 714 textarea {
703 715 padding: @input-padding;
704 716 border: @input-border-thickness solid @border-highlight-color;
705 717 .border-radius (@border-radius);
706 718 font-family: @text-light;
707 719 font-size: @basefontsize;
708 720
709 721 &.input-sm {
710 722 padding: 5px;
711 723 }
712 724
713 725 &#description {
714 726 min-width: @input-description-minwidth;
715 727 min-height: 1em;
716 728 padding: 10px;
717 729 }
718 730 }
719 731
720 732 .field-sm {
721 733 input,
722 734 textarea {
723 735 padding: 5px;
724 736 }
725 737 }
726 738
727 739 textarea {
728 740 display: block;
729 741 clear: both;
730 742 width: 100%;
731 743 min-height: 100px;
732 744 margin-bottom: @padding;
733 745 .box-sizing(border-box);
734 746 overflow: auto;
735 747 }
736 748
737 749 label {
738 750 font-family: @text-light;
739 751 }
740 752
741 753 // GRAVATARS
742 754 // centers gravatar on username to the right
743 755
744 756 .gravatar {
745 757 display: inline;
746 758 min-width: 16px;
747 759 min-height: 16px;
748 760 margin: -5px 0;
749 761 padding: 0;
750 762 line-height: 1em;
751 763 box-sizing: content-box;
752 764 border-radius: 50%;
753 765
754 766 &.gravatar-large {
755 767 margin: -0.5em .25em -0.5em 0;
756 768 }
757 769
758 770 & + .user {
759 771 display: inline;
760 772 margin: 0;
761 773 padding: 0 0 0 .17em;
762 774 line-height: 1em;
763 775 }
764 776 }
765 777
766 778 .user-inline-data {
767 779 display: inline-block;
768 780 float: left;
769 781 padding-left: .5em;
770 782 line-height: 1.3em;
771 783 }
772 784
773 785 .rc-user { // gravatar + user wrapper
774 786 float: left;
775 787 position: relative;
776 788 min-width: 100px;
777 789 max-width: 200px;
778 790 min-height: (@gravatar-size + @border-thickness * 2); // account for border
779 791 display: block;
780 792 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
781 793
782 794
783 795 .gravatar {
784 796 display: block;
785 797 position: absolute;
786 798 top: 0;
787 799 left: 0;
788 800 min-width: @gravatar-size;
789 801 min-height: @gravatar-size;
790 802 margin: 0;
791 803 }
792 804
793 805 .user {
794 806 display: block;
795 807 max-width: 175px;
796 808 padding-top: 2px;
797 809 overflow: hidden;
798 810 text-overflow: ellipsis;
799 811 }
800 812 }
801 813
802 814 .gist-gravatar,
803 815 .journal_container {
804 816 .gravatar-large {
805 817 margin: 0 .5em -10px 0;
806 818 }
807 819 }
808 820
809 821 .gist-type-fields {
810 822 line-height: 30px;
811 823 height: 30px;
812 824
813 825 .gist-type-fields-wrapper {
814 826 vertical-align: middle;
815 827 display: inline-block;
816 828 line-height: 25px;
817 829 }
818 830 }
819 831
820 832 // ADMIN SETTINGS
821 833
822 834 // Tag Patterns
823 835 .tag_patterns {
824 836 .tag_input {
825 837 margin-bottom: @padding;
826 838 }
827 839 }
828 840
829 841 .locked_input {
830 842 position: relative;
831 843
832 844 input {
833 845 display: inline;
834 846 margin: 3px 5px 0px 0px;
835 847 }
836 848
837 849 br {
838 850 display: none;
839 851 }
840 852
841 853 .error-message {
842 854 float: left;
843 855 width: 100%;
844 856 }
845 857
846 858 .lock_input_button {
847 859 display: inline;
848 860 }
849 861
850 862 .help-block {
851 863 clear: both;
852 864 }
853 865 }
854 866
855 867 // Notifications
856 868
857 869 .notifications_buttons {
858 870 margin: 0 0 @space 0;
859 871 padding: 0;
860 872
861 873 .btn {
862 874 display: inline-block;
863 875 }
864 876 }
865 877
866 878 .notification-list {
867 879
868 880 div {
869 881 vertical-align: middle;
870 882 }
871 883
872 884 .container {
873 885 display: block;
874 886 margin: 0 0 @padding 0;
875 887 }
876 888
877 889 .delete-notifications {
878 890 margin-left: @padding;
879 891 text-align: right;
880 892 cursor: pointer;
881 893 }
882 894
883 895 .read-notifications {
884 896 margin-left: @padding/2;
885 897 text-align: right;
886 898 width: 35px;
887 899 cursor: pointer;
888 900 }
889 901
890 902 .icon-minus-sign {
891 903 color: @alert2;
892 904 }
893 905
894 906 .icon-ok-sign {
895 907 color: @alert1;
896 908 }
897 909 }
898 910
899 911 .user_settings {
900 912 float: left;
901 913 clear: both;
902 914 display: block;
903 915 width: 100%;
904 916
905 917 .gravatar_box {
906 918 margin-bottom: @padding;
907 919
908 920 &:after {
909 921 content: " ";
910 922 clear: both;
911 923 width: 100%;
912 924 }
913 925 }
914 926
915 927 .fields .field {
916 928 clear: both;
917 929 }
918 930 }
919 931
920 932 .advanced_settings {
921 933 margin-bottom: @space;
922 934
923 935 .help-block {
924 936 margin-left: 0;
925 937 }
926 938
927 939 button + .help-block {
928 940 margin-top: @padding;
929 941 }
930 942 }
931 943
932 944 // admin settings radio buttons and labels
933 945 .label-2 {
934 946 float: left;
935 947 width: @label2-width;
936 948
937 949 label {
938 950 color: @grey1;
939 951 }
940 952 }
941 953 .checkboxes {
942 954 float: left;
943 955 width: @checkboxes-width;
944 956 margin-bottom: @padding;
945 957
946 958 .checkbox {
947 959 width: 100%;
948 960
949 961 label {
950 962 margin: 0;
951 963 padding: 0;
952 964 }
953 965 }
954 966
955 967 .checkbox + .checkbox {
956 968 display: inline-block;
957 969 }
958 970
959 971 label {
960 972 margin-right: 1em;
961 973 }
962 974 }
963 975
964 976 // CHANGELOG
965 977 .container_header {
966 978 float: left;
967 979 display: block;
968 980 width: 100%;
969 981 margin: @padding 0 @padding;
970 982
971 983 #filter_changelog {
972 984 float: left;
973 985 margin-right: @padding;
974 986 }
975 987
976 988 .breadcrumbs_light {
977 989 display: inline-block;
978 990 }
979 991 }
980 992
981 993 .info_box {
982 994 float: right;
983 995 }
984 996
985 997
986 998
987 999 #graph_content{
988 1000
989 1001 // adjust for table headers so that graph renders properly
990 1002 // #graph_nodes padding - table cell padding
991 1003 padding-top: (@space - (@basefontsize * 2.4));
992 1004
993 1005 &.graph_full_width {
994 1006 width: 100%;
995 1007 max-width: 100%;
996 1008 }
997 1009 }
998 1010
999 1011 #graph {
1000 1012
1001 1013 .pagination-left {
1002 1014 float: left;
1003 1015 clear: both;
1004 1016 }
1005 1017
1006 1018 .log-container {
1007 1019 max-width: 345px;
1008 1020
1009 1021 .message{
1010 1022 max-width: 340px;
1011 1023 }
1012 1024 }
1013 1025
1014 1026 .graph-col-wrapper {
1015 1027
1016 1028 #graph_nodes {
1017 1029 width: 100px;
1018 1030 position: absolute;
1019 1031 left: 70px;
1020 1032 z-index: -1;
1021 1033 }
1022 1034 }
1023 1035
1024 1036 .load-more-commits {
1025 1037 text-align: center;
1026 1038 }
1027 1039 .load-more-commits:hover {
1028 1040 background-color: @grey7;
1029 1041 }
1030 1042 .load-more-commits {
1031 1043 a {
1032 1044 display: block;
1033 1045 }
1034 1046 }
1035 1047 }
1036 1048
1037 1049 .obsolete-toggle {
1038 1050 line-height: 30px;
1039 1051 margin-left: -15px;
1040 1052 }
1041 1053
1042 1054 #rev_range_container, #rev_range_clear, #rev_range_more {
1043 1055 margin-top: -5px;
1044 1056 margin-bottom: -5px;
1045 1057 }
1046 1058
1047 1059 #filter_changelog {
1048 1060 float: left;
1049 1061 }
1050 1062
1051 1063
1052 1064 //--- THEME ------------------//
1053 1065
1054 1066 #logo {
1055 1067 float: left;
1056 1068 margin: 9px 0 0 0;
1057 1069
1058 1070 .header {
1059 1071 background-color: transparent;
1060 1072 }
1061 1073
1062 1074 a {
1063 1075 display: inline-block;
1064 1076 }
1065 1077
1066 1078 img {
1067 1079 height:30px;
1068 1080 }
1069 1081 }
1070 1082
1071 1083 .logo-wrapper {
1072 1084 float:left;
1073 1085 }
1074 1086
1075 1087 .branding {
1076 1088 float: left;
1077 1089 padding: 9px 2px;
1078 1090 line-height: 1em;
1079 1091 font-size: @navigation-fontsize;
1080 1092
1081 1093 a {
1082 1094 color: @grey5
1083 1095 }
1084 1096 @media screen and (max-width: 1200px) {
1085 1097 display: none;
1086 1098 }
1087 1099 }
1088 1100
1089 1101 img {
1090 1102 border: none;
1091 1103 outline: none;
1092 1104 }
1093 1105 user-profile-header
1094 1106 label {
1095 1107
1096 1108 input[type="checkbox"] {
1097 1109 margin-right: 1em;
1098 1110 }
1099 1111 input[type="radio"] {
1100 1112 margin-right: 1em;
1101 1113 }
1102 1114 }
1103 1115
1104 1116 .review-status {
1105 1117 &.under_review {
1106 1118 color: @alert3;
1107 1119 }
1108 1120 &.approved {
1109 1121 color: @alert1;
1110 1122 }
1111 1123 &.rejected,
1112 1124 &.forced_closed{
1113 1125 color: @alert2;
1114 1126 }
1115 1127 &.not_reviewed {
1116 1128 color: @grey5;
1117 1129 }
1118 1130 }
1119 1131
1120 1132 .review-status-under_review {
1121 1133 color: @alert3;
1122 1134 }
1123 1135 .status-tag-under_review {
1124 1136 border-color: @alert3;
1125 1137 }
1126 1138
1127 1139 .review-status-approved {
1128 1140 color: @alert1;
1129 1141 }
1130 1142 .status-tag-approved {
1131 1143 border-color: @alert1;
1132 1144 }
1133 1145
1134 1146 .review-status-rejected,
1135 1147 .review-status-forced_closed {
1136 1148 color: @alert2;
1137 1149 }
1138 1150 .status-tag-rejected,
1139 1151 .status-tag-forced_closed {
1140 1152 border-color: @alert2;
1141 1153 }
1142 1154
1143 1155 .review-status-not_reviewed {
1144 1156 color: @grey5;
1145 1157 }
1146 1158 .status-tag-not_reviewed {
1147 1159 border-color: @grey5;
1148 1160 }
1149 1161
1150 1162 .test_pattern_preview {
1151 1163 margin: @space 0;
1152 1164
1153 1165 p {
1154 1166 margin-bottom: 0;
1155 1167 border-bottom: @border-thickness solid @border-default-color;
1156 1168 color: @grey3;
1157 1169 }
1158 1170
1159 1171 .btn {
1160 1172 margin-bottom: @padding;
1161 1173 }
1162 1174 }
1163 1175 #test_pattern_result {
1164 1176 display: none;
1165 1177 &:extend(pre);
1166 1178 padding: .9em;
1167 1179 color: @grey3;
1168 1180 background-color: @grey7;
1169 1181 border-right: @border-thickness solid @border-default-color;
1170 1182 border-bottom: @border-thickness solid @border-default-color;
1171 1183 border-left: @border-thickness solid @border-default-color;
1172 1184 }
1173 1185
1174 1186 #repo_vcs_settings {
1175 1187 #inherit_overlay_vcs_default {
1176 1188 display: none;
1177 1189 }
1178 1190 #inherit_overlay_vcs_custom {
1179 1191 display: custom;
1180 1192 }
1181 1193 &.inherited {
1182 1194 #inherit_overlay_vcs_default {
1183 1195 display: block;
1184 1196 }
1185 1197 #inherit_overlay_vcs_custom {
1186 1198 display: none;
1187 1199 }
1188 1200 }
1189 1201 }
1190 1202
1191 1203 .issue-tracker-link {
1192 1204 color: @rcblue;
1193 1205 }
1194 1206
1195 1207 // Issue Tracker Table Show/Hide
1196 1208 #repo_issue_tracker {
1197 1209 #inherit_overlay {
1198 1210 display: none;
1199 1211 }
1200 1212 #custom_overlay {
1201 1213 display: custom;
1202 1214 }
1203 1215 &.inherited {
1204 1216 #inherit_overlay {
1205 1217 display: block;
1206 1218 }
1207 1219 #custom_overlay {
1208 1220 display: none;
1209 1221 }
1210 1222 }
1211 1223 }
1212 1224 table.issuetracker {
1213 1225 &.readonly {
1214 1226 tr, td {
1215 1227 color: @grey3;
1216 1228 }
1217 1229 }
1218 1230 .edit {
1219 1231 display: none;
1220 1232 }
1221 1233 .editopen {
1222 1234 .edit {
1223 1235 display: inline;
1224 1236 }
1225 1237 .entry {
1226 1238 display: none;
1227 1239 }
1228 1240 }
1229 1241 tr td.td-action {
1230 1242 min-width: 117px;
1231 1243 }
1232 1244 td input {
1233 1245 max-width: none;
1234 1246 min-width: 30px;
1235 1247 width: 80%;
1236 1248 }
1237 1249 .issuetracker_pref input {
1238 1250 width: 40%;
1239 1251 }
1240 1252 input.edit_issuetracker_update {
1241 1253 margin-right: 0;
1242 1254 width: auto;
1243 1255 }
1244 1256 }
1245 1257
1246 1258 table.integrations {
1247 1259 .td-icon {
1248 1260 width: 20px;
1249 1261 .integration-icon {
1250 1262 height: 20px;
1251 1263 width: 20px;
1252 1264 }
1253 1265 }
1254 1266 }
1255 1267
1256 1268 .integrations {
1257 1269 a.integration-box {
1258 1270 color: @text-color;
1259 1271 &:hover {
1260 1272 .panel {
1261 1273 background: #fbfbfb;
1262 1274 }
1263 1275 }
1264 1276 .integration-icon {
1265 1277 width: 30px;
1266 1278 height: 30px;
1267 1279 margin-right: 20px;
1268 1280 float: left;
1269 1281 }
1270 1282
1271 1283 .panel-body {
1272 1284 padding: 10px;
1273 1285 }
1274 1286 .panel {
1275 1287 margin-bottom: 10px;
1276 1288 }
1277 1289 h2 {
1278 1290 display: inline-block;
1279 1291 margin: 0;
1280 1292 min-width: 140px;
1281 1293 }
1282 1294 }
1283 1295 a.integration-box.dummy-integration {
1284 1296 color: @grey4
1285 1297 }
1286 1298 }
1287 1299
1288 1300 //Permissions Settings
1289 1301 #add_perm {
1290 1302 margin: 0 0 @padding;
1291 1303 cursor: pointer;
1292 1304 }
1293 1305
1294 1306 .perm_ac {
1295 1307 input {
1296 1308 width: 95%;
1297 1309 }
1298 1310 }
1299 1311
1300 1312 .autocomplete-suggestions {
1301 1313 width: auto !important; // overrides autocomplete.js
1302 1314 min-width: 278px;
1303 1315 margin: 0;
1304 1316 border: @border-thickness solid @grey5;
1305 1317 border-radius: @border-radius;
1306 1318 color: @grey2;
1307 1319 background-color: white;
1308 1320 }
1309 1321
1310 1322 .autocomplete-qfilter-suggestions {
1311 1323 width: auto !important; // overrides autocomplete.js
1312 1324 max-height: 100% !important;
1313 1325 min-width: 376px;
1314 1326 margin: 0;
1315 1327 border: @border-thickness solid @grey5;
1316 1328 color: @grey2;
1317 1329 background-color: white;
1318 1330 }
1319 1331
1320 1332 .autocomplete-selected {
1321 1333 background: #F0F0F0;
1322 1334 }
1323 1335
1324 1336 .ac-container-wrap {
1325 1337 margin: 0;
1326 1338 padding: 8px;
1327 1339 border-bottom: @border-thickness solid @grey5;
1328 1340 list-style-type: none;
1329 1341 cursor: pointer;
1330 1342
1331 1343 &:hover {
1332 1344 background-color: @grey7;
1333 1345 }
1334 1346
1335 1347 img {
1336 1348 height: @gravatar-size;
1337 1349 width: @gravatar-size;
1338 1350 margin-right: 1em;
1339 1351 }
1340 1352
1341 1353 strong {
1342 1354 font-weight: normal;
1343 1355 }
1344 1356 }
1345 1357
1346 1358 // Settings Dropdown
1347 1359 .user-menu .container {
1348 1360 padding: 0 4px;
1349 1361 margin: 0;
1350 1362 }
1351 1363
1352 1364 .user-menu .gravatar {
1353 1365 cursor: pointer;
1354 1366 }
1355 1367
1356 1368 .codeblock {
1357 1369 margin-bottom: @padding;
1358 1370 clear: both;
1359 1371
1360 1372 .stats {
1361 1373 overflow: hidden;
1362 1374 }
1363 1375
1364 1376 .message{
1365 1377 textarea{
1366 1378 margin: 0;
1367 1379 }
1368 1380 }
1369 1381
1370 1382 .code-header {
1371 1383 .stats {
1372 1384 line-height: 2em;
1373 1385
1374 1386 .revision_id {
1375 1387 margin-left: 0;
1376 1388 }
1377 1389 .buttons {
1378 1390 padding-right: 0;
1379 1391 }
1380 1392 }
1381 1393
1382 1394 .item{
1383 1395 margin-right: 0.5em;
1384 1396 }
1385 1397 }
1386 1398
1387 1399 #editor_container {
1388 1400 position: relative;
1389 1401 margin: @padding 10px;
1390 1402 }
1391 1403 }
1392 1404
1393 1405 #file_history_container {
1394 1406 display: none;
1395 1407 }
1396 1408
1397 1409 .file-history-inner {
1398 1410 margin-bottom: 10px;
1399 1411 }
1400 1412
1401 1413 // Pull Requests
1402 1414 .summary-details {
1403 1415 width: 72%;
1404 1416 }
1405 1417 .pr-summary {
1406 1418 border-bottom: @border-thickness solid @grey5;
1407 1419 margin-bottom: @space;
1408 1420 }
1409 1421 .reviewers-title {
1410 1422 width: 25%;
1411 1423 min-width: 200px;
1412 1424 }
1413 1425 .reviewers {
1414 1426 width: 25%;
1415 1427 min-width: 200px;
1416 1428 }
1417 1429 .reviewers ul li {
1418 1430 position: relative;
1419 1431 width: 100%;
1420 1432 padding-bottom: 8px;
1421 1433 list-style-type: none;
1422 1434 }
1423 1435
1424 1436 .reviewer_entry {
1425 1437 min-height: 55px;
1426 1438 }
1427 1439
1428 1440 .reviewers_member {
1429 1441 width: 100%;
1430 1442 overflow: auto;
1431 1443 }
1432 1444 .reviewer_reason {
1433 1445 padding-left: 20px;
1434 1446 line-height: 1.5em;
1435 1447 }
1436 1448 .reviewer_status {
1437 1449 display: inline-block;
1438 1450 width: 25px;
1439 1451 min-width: 25px;
1440 1452 height: 1.2em;
1441 1453 line-height: 1em;
1442 1454 }
1443 1455
1444 1456 .reviewer_name {
1445 1457 display: inline-block;
1446 1458 max-width: 83%;
1447 1459 padding-right: 20px;
1448 1460 vertical-align: middle;
1449 1461 line-height: 1;
1450 1462
1451 1463 .rc-user {
1452 1464 min-width: 0;
1453 1465 margin: -2px 1em 0 0;
1454 1466 }
1455 1467
1456 1468 .reviewer {
1457 1469 float: left;
1458 1470 }
1459 1471 }
1460 1472
1461 1473 .reviewer_member_mandatory {
1462 1474 position: absolute;
1463 1475 left: 15px;
1464 1476 top: 8px;
1465 1477 width: 16px;
1466 1478 font-size: 11px;
1467 1479 margin: 0;
1468 1480 padding: 0;
1469 1481 color: black;
1470 1482 }
1471 1483
1472 1484 .reviewer_member_mandatory_remove,
1473 1485 .reviewer_member_remove {
1474 1486 position: absolute;
1475 1487 right: 0;
1476 1488 top: 0;
1477 1489 width: 16px;
1478 1490 margin-bottom: 10px;
1479 1491 padding: 0;
1480 1492 color: black;
1481 1493 }
1482 1494
1483 1495 .reviewer_member_mandatory_remove {
1484 1496 color: @grey4;
1485 1497 }
1486 1498
1487 1499 .reviewer_member_status {
1488 1500 margin-top: 5px;
1489 1501 }
1490 1502 .pr-summary #summary{
1491 1503 width: 100%;
1492 1504 }
1493 1505 .pr-summary .action_button:hover {
1494 1506 border: 0;
1495 1507 cursor: pointer;
1496 1508 }
1497 1509 .pr-details-title {
1498 1510 padding-bottom: 8px;
1499 1511 border-bottom: @border-thickness solid @grey5;
1500 1512
1501 1513 .action_button.disabled {
1502 1514 color: @grey4;
1503 1515 cursor: inherit;
1504 1516 }
1505 1517 .action_button {
1506 1518 color: @rcblue;
1507 1519 }
1508 1520 }
1509 1521 .pr-details-content {
1510 1522 margin-top: @textmargin;
1511 1523 margin-bottom: @textmargin;
1512 1524 }
1513 1525
1514 1526 .pr-reviewer-rules {
1515 1527 padding: 10px 0px 20px 0px;
1516 1528 }
1517 1529
1518 1530 .group_members {
1519 1531 margin-top: 0;
1520 1532 padding: 0;
1521 1533 list-style: outside none none;
1522 1534
1523 1535 img {
1524 1536 height: @gravatar-size;
1525 1537 width: @gravatar-size;
1526 1538 margin-right: .5em;
1527 1539 margin-left: 3px;
1528 1540 }
1529 1541
1530 1542 .to-delete {
1531 1543 .user {
1532 1544 text-decoration: line-through;
1533 1545 }
1534 1546 }
1535 1547 }
1536 1548
1537 1549 .compare_view_commits_title {
1538 1550 .disabled {
1539 1551 cursor: inherit;
1540 1552 &:hover{
1541 1553 background-color: inherit;
1542 1554 color: inherit;
1543 1555 }
1544 1556 }
1545 1557 }
1546 1558
1547 1559 .subtitle-compare {
1548 1560 margin: -15px 0px 0px 0px;
1549 1561 }
1550 1562
1551 1563 // new entry in group_members
1552 1564 .td-author-new-entry {
1553 1565 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1554 1566 }
1555 1567
1556 1568 .usergroup_member_remove {
1557 1569 width: 16px;
1558 1570 margin-bottom: 10px;
1559 1571 padding: 0;
1560 1572 color: black !important;
1561 1573 cursor: pointer;
1562 1574 }
1563 1575
1564 1576 .reviewer_ac .ac-input {
1565 1577 width: 92%;
1566 1578 margin-bottom: 1em;
1567 1579 }
1568 1580
1569 1581 .compare_view_commits tr{
1570 1582 height: 20px;
1571 1583 }
1572 1584 .compare_view_commits td {
1573 1585 vertical-align: top;
1574 1586 padding-top: 10px;
1575 1587 }
1576 1588 .compare_view_commits .author {
1577 1589 margin-left: 5px;
1578 1590 }
1579 1591
1580 1592 .compare_view_commits {
1581 1593 .color-a {
1582 1594 color: @alert1;
1583 1595 }
1584 1596
1585 1597 .color-c {
1586 1598 color: @color3;
1587 1599 }
1588 1600
1589 1601 .color-r {
1590 1602 color: @color5;
1591 1603 }
1592 1604
1593 1605 .color-a-bg {
1594 1606 background-color: @alert1;
1595 1607 }
1596 1608
1597 1609 .color-c-bg {
1598 1610 background-color: @alert3;
1599 1611 }
1600 1612
1601 1613 .color-r-bg {
1602 1614 background-color: @alert2;
1603 1615 }
1604 1616
1605 1617 .color-a-border {
1606 1618 border: 1px solid @alert1;
1607 1619 }
1608 1620
1609 1621 .color-c-border {
1610 1622 border: 1px solid @alert3;
1611 1623 }
1612 1624
1613 1625 .color-r-border {
1614 1626 border: 1px solid @alert2;
1615 1627 }
1616 1628
1617 1629 .commit-change-indicator {
1618 1630 width: 15px;
1619 1631 height: 15px;
1620 1632 position: relative;
1621 1633 left: 15px;
1622 1634 }
1623 1635
1624 1636 .commit-change-content {
1625 1637 text-align: center;
1626 1638 vertical-align: middle;
1627 1639 line-height: 15px;
1628 1640 }
1629 1641 }
1630 1642
1631 1643 .compare_view_filepath {
1632 1644 color: @grey1;
1633 1645 }
1634 1646
1635 1647 .show_more {
1636 1648 display: inline-block;
1637 1649 width: 0;
1638 1650 height: 0;
1639 1651 vertical-align: middle;
1640 1652 content: "";
1641 1653 border: 4px solid;
1642 1654 border-right-color: transparent;
1643 1655 border-bottom-color: transparent;
1644 1656 border-left-color: transparent;
1645 1657 font-size: 0;
1646 1658 }
1647 1659
1648 1660 .journal_more .show_more {
1649 1661 display: inline;
1650 1662
1651 1663 &:after {
1652 1664 content: none;
1653 1665 }
1654 1666 }
1655 1667
1656 1668 .compare_view_commits .collapse_commit:after {
1657 1669 cursor: pointer;
1658 1670 content: "\00A0\25B4";
1659 1671 margin-left: -3px;
1660 1672 font-size: 17px;
1661 1673 color: @grey4;
1662 1674 }
1663 1675
1664 1676 .diff_links {
1665 1677 margin-left: 8px;
1666 1678 }
1667 1679
1668 1680 #pull_request_overview {
1669 1681 div.ancestor {
1670 1682 margin: -33px 0;
1671 1683 }
1672 1684 }
1673 1685
1674 1686 div.ancestor {
1675 1687 line-height: 33px;
1676 1688 }
1677 1689
1678 1690 .cs_icon_td input[type="checkbox"] {
1679 1691 display: none;
1680 1692 }
1681 1693
1682 1694 .cs_icon_td .expand_file_icon:after {
1683 1695 cursor: pointer;
1684 1696 content: "\00A0\25B6";
1685 1697 font-size: 12px;
1686 1698 color: @grey4;
1687 1699 }
1688 1700
1689 1701 .cs_icon_td .collapse_file_icon:after {
1690 1702 cursor: pointer;
1691 1703 content: "\00A0\25BC";
1692 1704 font-size: 12px;
1693 1705 color: @grey4;
1694 1706 }
1695 1707
1696 1708 /*new binary
1697 1709 NEW_FILENODE = 1
1698 1710 DEL_FILENODE = 2
1699 1711 MOD_FILENODE = 3
1700 1712 RENAMED_FILENODE = 4
1701 1713 COPIED_FILENODE = 5
1702 1714 CHMOD_FILENODE = 6
1703 1715 BIN_FILENODE = 7
1704 1716 */
1705 1717 .cs_files_expand {
1706 1718 font-size: @basefontsize + 5px;
1707 1719 line-height: 1.8em;
1708 1720 float: right;
1709 1721 }
1710 1722
1711 1723 .cs_files_expand span{
1712 1724 color: @rcblue;
1713 1725 cursor: pointer;
1714 1726 }
1715 1727 .cs_files {
1716 1728 clear: both;
1717 1729 padding-bottom: @padding;
1718 1730
1719 1731 .cur_cs {
1720 1732 margin: 10px 2px;
1721 1733 font-weight: bold;
1722 1734 }
1723 1735
1724 1736 .node {
1725 1737 float: left;
1726 1738 }
1727 1739
1728 1740 .changes {
1729 1741 float: right;
1730 1742 color: white;
1731 1743 font-size: @basefontsize - 4px;
1732 1744 margin-top: 4px;
1733 1745 opacity: 0.6;
1734 1746 filter: Alpha(opacity=60); /* IE8 and earlier */
1735 1747
1736 1748 .added {
1737 1749 background-color: @alert1;
1738 1750 float: left;
1739 1751 text-align: center;
1740 1752 }
1741 1753
1742 1754 .deleted {
1743 1755 background-color: @alert2;
1744 1756 float: left;
1745 1757 text-align: center;
1746 1758 }
1747 1759
1748 1760 .bin {
1749 1761 background-color: @alert1;
1750 1762 text-align: center;
1751 1763 }
1752 1764
1753 1765 /*new binary*/
1754 1766 .bin.bin1 {
1755 1767 background-color: @alert1;
1756 1768 text-align: center;
1757 1769 }
1758 1770
1759 1771 /*deleted binary*/
1760 1772 .bin.bin2 {
1761 1773 background-color: @alert2;
1762 1774 text-align: center;
1763 1775 }
1764 1776
1765 1777 /*mod binary*/
1766 1778 .bin.bin3 {
1767 1779 background-color: @grey2;
1768 1780 text-align: center;
1769 1781 }
1770 1782
1771 1783 /*rename file*/
1772 1784 .bin.bin4 {
1773 1785 background-color: @alert4;
1774 1786 text-align: center;
1775 1787 }
1776 1788
1777 1789 /*copied file*/
1778 1790 .bin.bin5 {
1779 1791 background-color: @alert4;
1780 1792 text-align: center;
1781 1793 }
1782 1794
1783 1795 /*chmod file*/
1784 1796 .bin.bin6 {
1785 1797 background-color: @grey2;
1786 1798 text-align: center;
1787 1799 }
1788 1800 }
1789 1801 }
1790 1802
1791 1803 .cs_files .cs_added, .cs_files .cs_A,
1792 1804 .cs_files .cs_added, .cs_files .cs_M,
1793 1805 .cs_files .cs_added, .cs_files .cs_D {
1794 1806 height: 16px;
1795 1807 padding-right: 10px;
1796 1808 margin-top: 7px;
1797 1809 text-align: left;
1798 1810 }
1799 1811
1800 1812 .cs_icon_td {
1801 1813 min-width: 16px;
1802 1814 width: 16px;
1803 1815 }
1804 1816
1805 1817 .pull-request-merge {
1806 1818 border: 1px solid @grey5;
1807 1819 padding: 10px 0px 20px;
1808 1820 margin-top: 10px;
1809 1821 margin-bottom: 20px;
1810 1822 }
1811 1823
1812 1824 .pull-request-merge-refresh {
1813 1825 margin: 2px 7px;
1814 1826 a {
1815 1827 color: @grey3;
1816 1828 }
1817 1829 }
1818 1830
1819 1831 .pull-request-merge ul {
1820 1832 padding: 0px 0px;
1821 1833 }
1822 1834
1823 1835 .pull-request-merge li {
1824 1836 list-style-type: none;
1825 1837 }
1826 1838
1827 1839 .pull-request-merge .pull-request-wrap {
1828 1840 height: auto;
1829 1841 padding: 0px 0px;
1830 1842 text-align: right;
1831 1843 }
1832 1844
1833 1845 .pull-request-merge span {
1834 1846 margin-right: 5px;
1835 1847 }
1836 1848
1837 1849 .pull-request-merge-actions {
1838 1850 min-height: 30px;
1839 1851 padding: 0px 0px;
1840 1852 }
1841 1853
1842 1854 .pull-request-merge-info {
1843 1855 padding: 0px 5px 5px 0px;
1844 1856 }
1845 1857
1846 1858 .merge-status {
1847 1859 margin-right: 5px;
1848 1860 }
1849 1861
1850 1862 .merge-message {
1851 1863 font-size: 1.2em
1852 1864 }
1853 1865
1854 1866 .merge-message.success i,
1855 1867 .merge-icon.success i {
1856 1868 color:@alert1;
1857 1869 }
1858 1870
1859 1871 .merge-message.warning i,
1860 1872 .merge-icon.warning i {
1861 1873 color: @alert3;
1862 1874 }
1863 1875
1864 1876 .merge-message.error i,
1865 1877 .merge-icon.error i {
1866 1878 color:@alert2;
1867 1879 }
1868 1880
1869 1881 .pr-versions {
1870 1882 font-size: 1.1em;
1871 1883
1872 1884 table {
1873 1885 padding: 0px 5px;
1874 1886 }
1875 1887
1876 1888 td {
1877 1889 line-height: 15px;
1878 1890 }
1879 1891
1880 1892 .compare-radio-button {
1881 1893 position: relative;
1882 1894 top: -3px;
1883 1895 }
1884 1896 }
1885 1897
1886 1898
1887 1899 #close_pull_request {
1888 1900 margin-right: 0px;
1889 1901 }
1890 1902
1891 1903 .empty_data {
1892 1904 color: @grey4;
1893 1905 }
1894 1906
1895 1907 #changeset_compare_view_content {
1896 1908 clear: both;
1897 1909 width: 100%;
1898 1910 box-sizing: border-box;
1899 1911 .border-radius(@border-radius);
1900 1912
1901 1913 .help-block {
1902 1914 margin: @padding 0;
1903 1915 color: @text-color;
1904 1916 &.pre-formatting {
1905 1917 white-space: pre;
1906 1918 }
1907 1919 }
1908 1920
1909 1921 .empty_data {
1910 1922 margin: @padding 0;
1911 1923 }
1912 1924
1913 1925 .alert {
1914 1926 margin-bottom: @space;
1915 1927 }
1916 1928 }
1917 1929
1918 1930 .table_disp {
1919 1931 .status {
1920 1932 width: auto;
1921 1933 }
1922 1934 }
1923 1935
1924 1936
1925 1937 .creation_in_progress {
1926 1938 color: @grey4
1927 1939 }
1928 1940
1929 1941 .status_box_menu {
1930 1942 margin: 0;
1931 1943 }
1932 1944
1933 1945 .notification-table{
1934 1946 margin-bottom: @space;
1935 1947 display: table;
1936 1948 width: 100%;
1937 1949
1938 1950 .container{
1939 1951 display: table-row;
1940 1952
1941 1953 .notification-header{
1942 1954 border-bottom: @border-thickness solid @border-default-color;
1943 1955 }
1944 1956
1945 1957 .notification-subject{
1946 1958 display: table-cell;
1947 1959 }
1948 1960 }
1949 1961 }
1950 1962
1951 1963 // Notifications
1952 1964 .notification-header{
1953 1965 display: table;
1954 1966 width: 100%;
1955 1967 padding: floor(@basefontsize/2) 0;
1956 1968 line-height: 1em;
1957 1969
1958 1970 .desc, .delete-notifications, .read-notifications{
1959 1971 display: table-cell;
1960 1972 text-align: left;
1961 1973 }
1962 1974
1963 1975 .delete-notifications, .read-notifications{
1964 1976 width: 35px;
1965 1977 min-width: 35px; //fixes when only one button is displayed
1966 1978 }
1967 1979 }
1968 1980
1969 1981 .notification-body {
1970 1982 .markdown-block,
1971 1983 .rst-block {
1972 1984 padding: @padding 0;
1973 1985 }
1974 1986
1975 1987 .notification-subject {
1976 1988 padding: @textmargin 0;
1977 1989 border-bottom: @border-thickness solid @border-default-color;
1978 1990 }
1979 1991 }
1980 1992
1981 1993
1982 1994 .notifications_buttons{
1983 1995 float: right;
1984 1996 }
1985 1997
1986 1998 #notification-status{
1987 1999 display: inline;
1988 2000 }
1989 2001
1990 2002 // Repositories
1991 2003
1992 2004 #summary.fields{
1993 2005 display: table;
1994 2006
1995 2007 .field{
1996 2008 display: table-row;
1997 2009
1998 2010 .label-summary{
1999 2011 display: table-cell;
2000 2012 min-width: @label-summary-minwidth;
2001 2013 padding-top: @padding/2;
2002 2014 padding-bottom: @padding/2;
2003 2015 padding-right: @padding/2;
2004 2016 }
2005 2017
2006 2018 .input{
2007 2019 display: table-cell;
2008 2020 padding: @padding/2;
2009 2021
2010 2022 input{
2011 2023 min-width: 29em;
2012 2024 padding: @padding/4;
2013 2025 }
2014 2026 }
2015 2027 .statistics, .downloads{
2016 2028 .disabled{
2017 2029 color: @grey4;
2018 2030 }
2019 2031 }
2020 2032 }
2021 2033 }
2022 2034
2023 2035 #summary{
2024 2036 width: 70%;
2025 2037 }
2026 2038
2027 2039
2028 2040 // Journal
2029 2041 .journal.title {
2030 2042 h5 {
2031 2043 float: left;
2032 2044 margin: 0;
2033 2045 width: 70%;
2034 2046 }
2035 2047
2036 2048 ul {
2037 2049 float: right;
2038 2050 display: inline-block;
2039 2051 margin: 0;
2040 2052 width: 30%;
2041 2053 text-align: right;
2042 2054
2043 2055 li {
2044 2056 display: inline;
2045 2057 font-size: @journal-fontsize;
2046 2058 line-height: 1em;
2047 2059
2048 2060 list-style-type: none;
2049 2061 }
2050 2062 }
2051 2063 }
2052 2064
2053 2065 .filterexample {
2054 2066 position: absolute;
2055 2067 top: 95px;
2056 2068 left: @contentpadding;
2057 2069 color: @rcblue;
2058 2070 font-size: 11px;
2059 2071 font-family: @text-regular;
2060 2072 cursor: help;
2061 2073
2062 2074 &:hover {
2063 2075 color: @rcdarkblue;
2064 2076 }
2065 2077
2066 2078 @media (max-width:768px) {
2067 2079 position: relative;
2068 2080 top: auto;
2069 2081 left: auto;
2070 2082 display: block;
2071 2083 }
2072 2084 }
2073 2085
2074 2086
2075 2087 #journal{
2076 2088 margin-bottom: @space;
2077 2089
2078 2090 .journal_day{
2079 2091 margin-bottom: @textmargin/2;
2080 2092 padding-bottom: @textmargin/2;
2081 2093 font-size: @journal-fontsize;
2082 2094 border-bottom: @border-thickness solid @border-default-color;
2083 2095 }
2084 2096
2085 2097 .journal_container{
2086 2098 margin-bottom: @space;
2087 2099
2088 2100 .journal_user{
2089 2101 display: inline-block;
2090 2102 }
2091 2103 .journal_action_container{
2092 2104 display: block;
2093 2105 margin-top: @textmargin;
2094 2106
2095 2107 div{
2096 2108 display: inline;
2097 2109 }
2098 2110
2099 2111 div.journal_action_params{
2100 2112 display: block;
2101 2113 }
2102 2114
2103 2115 div.journal_repo:after{
2104 2116 content: "\A";
2105 2117 white-space: pre;
2106 2118 }
2107 2119
2108 2120 div.date{
2109 2121 display: block;
2110 2122 margin-bottom: @textmargin;
2111 2123 }
2112 2124 }
2113 2125 }
2114 2126 }
2115 2127
2116 2128 // Files
2117 2129 .edit-file-title {
2118 2130 font-size: 16px;
2119 2131
2120 2132 .title-heading {
2121 2133 padding: 2px;
2122 2134 }
2123 2135 }
2124 2136
2125 2137 .edit-file-fieldset {
2126 2138 margin: @sidebarpadding 0;
2127 2139
2128 2140 .fieldset {
2129 2141 .left-label {
2130 2142 width: 13%;
2131 2143 }
2132 2144 .right-content {
2133 2145 width: 87%;
2134 2146 max-width: 100%;
2135 2147 }
2136 2148 .filename-label {
2137 2149 margin-top: 13px;
2138 2150 }
2139 2151 .commit-message-label {
2140 2152 margin-top: 4px;
2141 2153 }
2142 2154 .file-upload-input {
2143 2155 input {
2144 2156 display: none;
2145 2157 }
2146 2158 margin-top: 10px;
2147 2159 }
2148 2160 .file-upload-label {
2149 2161 margin-top: 10px;
2150 2162 }
2151 2163 p {
2152 2164 margin-top: 5px;
2153 2165 }
2154 2166
2155 2167 }
2156 2168 .custom-path-link {
2157 2169 margin-left: 5px;
2158 2170 }
2159 2171 #commit {
2160 2172 resize: vertical;
2161 2173 }
2162 2174 }
2163 2175
2164 2176 .delete-file-preview {
2165 2177 max-height: 250px;
2166 2178 }
2167 2179
2168 2180 .new-file,
2169 2181 #filter_activate,
2170 2182 #filter_deactivate {
2171 2183 float: right;
2172 2184 margin: 0 0 0 10px;
2173 2185 }
2174 2186
2175 2187 .file-upload-transaction-wrapper {
2176 2188 margin-top: 57px;
2177 2189 clear: both;
2178 2190 }
2179 2191
2180 2192 .file-upload-transaction-wrapper .error {
2181 2193 color: @color5;
2182 2194 }
2183 2195
2184 2196 .file-upload-transaction {
2185 2197 min-height: 200px;
2186 2198 padding: 54px;
2187 2199 border: 1px solid @grey5;
2188 2200 text-align: center;
2189 2201 clear: both;
2190 2202 }
2191 2203
2192 2204 .file-upload-transaction i {
2193 2205 font-size: 48px
2194 2206 }
2195 2207
2196 2208 h3.files_location{
2197 2209 line-height: 2.4em;
2198 2210 }
2199 2211
2200 2212 .browser-nav {
2201 2213 width: 100%;
2202 2214 display: table;
2203 2215 margin-bottom: 20px;
2204 2216
2205 2217 .info_box {
2206 2218 float: left;
2207 2219 display: inline-table;
2208 2220 height: 2.5em;
2209 2221
2210 2222 .browser-cur-rev, .info_box_elem {
2211 2223 display: table-cell;
2212 2224 vertical-align: middle;
2213 2225 }
2214 2226
2215 2227 .drop-menu {
2216 2228 margin: 0 10px;
2217 2229 }
2218 2230
2219 2231 .info_box_elem {
2220 2232 border-top: @border-thickness solid @grey5;
2221 2233 border-bottom: @border-thickness solid @grey5;
2222 2234 box-shadow: @button-shadow;
2223 2235
2224 2236 #at_rev, a {
2225 2237 padding: 0.6em 0.4em;
2226 2238 margin: 0;
2227 2239 .box-shadow(none);
2228 2240 border: 0;
2229 2241 height: 12px;
2230 2242 color: @grey2;
2231 2243 }
2232 2244
2233 2245 input#at_rev {
2234 2246 max-width: 50px;
2235 2247 text-align: center;
2236 2248 }
2237 2249
2238 2250 &.previous {
2239 2251 border: @border-thickness solid @grey5;
2240 2252 border-top-left-radius: @border-radius;
2241 2253 border-bottom-left-radius: @border-radius;
2242 2254
2243 2255 &:hover {
2244 2256 border-color: @grey4;
2245 2257 }
2246 2258
2247 2259 .disabled {
2248 2260 color: @grey5;
2249 2261 cursor: not-allowed;
2250 2262 opacity: 0.5;
2251 2263 }
2252 2264 }
2253 2265
2254 2266 &.next {
2255 2267 border: @border-thickness solid @grey5;
2256 2268 border-top-right-radius: @border-radius;
2257 2269 border-bottom-right-radius: @border-radius;
2258 2270
2259 2271 &:hover {
2260 2272 border-color: @grey4;
2261 2273 }
2262 2274
2263 2275 .disabled {
2264 2276 color: @grey5;
2265 2277 cursor: not-allowed;
2266 2278 opacity: 0.5;
2267 2279 }
2268 2280 }
2269 2281 }
2270 2282
2271 2283 .browser-cur-rev {
2272 2284
2273 2285 span{
2274 2286 margin: 0;
2275 2287 color: @rcblue;
2276 2288 height: 12px;
2277 2289 display: inline-block;
2278 2290 padding: 0.7em 1em ;
2279 2291 border: @border-thickness solid @rcblue;
2280 2292 margin-right: @padding;
2281 2293 }
2282 2294 }
2283 2295
2284 2296 }
2285 2297
2286 2298 .select-index-number {
2287 2299 margin: 0 0 0 20px;
2288 2300 color: @grey3;
2289 2301 }
2290 2302
2291 2303 .search_activate {
2292 2304 display: table-cell;
2293 2305 vertical-align: middle;
2294 2306
2295 2307 input, label{
2296 2308 margin: 0;
2297 2309 padding: 0;
2298 2310 }
2299 2311
2300 2312 input{
2301 2313 margin-left: @textmargin;
2302 2314 }
2303 2315
2304 2316 }
2305 2317 }
2306 2318
2307 2319 .browser-cur-rev{
2308 2320 margin-bottom: @textmargin;
2309 2321 }
2310 2322
2311 2323 #node_filter_box_loading{
2312 2324 .info_text;
2313 2325 }
2314 2326
2315 2327 .browser-search {
2316 2328 margin: -25px 0px 5px 0px;
2317 2329 }
2318 2330
2319 2331 .files-quick-filter {
2320 2332 float: right;
2321 2333 width: 180px;
2322 2334 position: relative;
2323 2335 }
2324 2336
2325 2337 .files-filter-box {
2326 2338 display: flex;
2327 2339 padding: 0px;
2328 2340 border-radius: 3px;
2329 2341 margin-bottom: 0;
2330 2342
2331 2343 a {
2332 2344 border: none !important;
2333 2345 }
2334 2346
2335 2347 li {
2336 2348 list-style-type: none
2337 2349 }
2338 2350 }
2339 2351
2340 2352 .files-filter-box-path {
2341 2353 line-height: 33px;
2342 2354 padding: 0;
2343 2355 width: 20px;
2344 2356 position: absolute;
2345 2357 z-index: 11;
2346 2358 left: 5px;
2347 2359 }
2348 2360
2349 2361 .files-filter-box-input {
2350 2362 margin-right: 0;
2351 2363
2352 2364 input {
2353 2365 border: 1px solid @white;
2354 2366 padding-left: 25px;
2355 2367 width: 145px;
2356 2368
2357 2369 &:hover {
2358 2370 border-color: @grey6;
2359 2371 }
2360 2372
2361 2373 &:focus {
2362 2374 border-color: @grey5;
2363 2375 }
2364 2376 }
2365 2377 }
2366 2378
2367 2379 .browser-result{
2368 2380 td a{
2369 2381 margin-left: 0.5em;
2370 2382 display: inline-block;
2371 2383
2372 2384 em {
2373 2385 font-weight: @text-bold-weight;
2374 2386 font-family: @text-bold;
2375 2387 }
2376 2388 }
2377 2389 }
2378 2390
2379 2391 .browser-highlight{
2380 2392 background-color: @grey5-alpha;
2381 2393 }
2382 2394
2383 2395
2384 2396 .edit-file-fieldset #location,
2385 2397 .edit-file-fieldset #filename {
2386 2398 display: flex;
2387 2399 width: -moz-available; /* WebKit-based browsers will ignore this. */
2388 2400 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2389 2401 width: fill-available;
2390 2402 border: 0;
2391 2403 }
2392 2404
2393 2405 .path-items {
2394 2406 display: flex;
2395 2407 padding: 0;
2396 2408 border: 1px solid #eeeeee;
2397 2409 width: 100%;
2398 2410 float: left;
2399 2411
2400 2412 .breadcrumb-path {
2401 2413 line-height: 30px;
2402 2414 padding: 0 4px;
2403 2415 white-space: nowrap;
2404 2416 }
2405 2417
2406 2418 .location-path {
2407 2419 width: -moz-available; /* WebKit-based browsers will ignore this. */
2408 2420 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2409 2421 width: fill-available;
2410 2422
2411 2423 .file-name-input {
2412 2424 padding: 0.5em 0;
2413 2425 }
2414 2426
2415 2427 }
2416 2428
2417 2429 ul {
2418 2430 display: flex;
2419 2431 margin: 0;
2420 2432 padding: 0;
2421 2433 width: 100%;
2422 2434 }
2423 2435
2424 2436 li {
2425 2437 list-style-type: none;
2426 2438 }
2427 2439
2428 2440 }
2429 2441
2430 2442 .editor-items {
2431 2443 height: 40px;
2432 2444 margin: 10px 0 -17px 10px;
2433 2445
2434 2446 .editor-action {
2435 2447 cursor: pointer;
2436 2448 }
2437 2449
2438 2450 .editor-action.active {
2439 2451 border-bottom: 2px solid #5C5C5C;
2440 2452 }
2441 2453
2442 2454 li {
2443 2455 list-style-type: none;
2444 2456 }
2445 2457 }
2446 2458
2447 2459 .edit-file-fieldset .message textarea {
2448 2460 border: 1px solid #eeeeee;
2449 2461 }
2450 2462
2451 2463 #files_data .codeblock {
2452 2464 background-color: #F5F5F5;
2453 2465 }
2454 2466
2455 2467 #editor_preview {
2456 2468 background: white;
2457 2469 }
2458 2470
2459 2471 .show-editor {
2460 2472 padding: 10px;
2461 2473 background-color: white;
2462 2474
2463 2475 }
2464 2476
2465 2477 .show-preview {
2466 2478 padding: 10px;
2467 2479 background-color: white;
2468 2480 border-left: 1px solid #eeeeee;
2469 2481 }
2470 2482 // quick filter
2471 2483 .grid-quick-filter {
2472 2484 float: right;
2473 2485 position: relative;
2474 2486 }
2475 2487
2476 2488 .grid-filter-box {
2477 2489 display: flex;
2478 2490 padding: 0px;
2479 2491 border-radius: 3px;
2480 2492 margin-bottom: 0;
2481 2493
2482 2494 a {
2483 2495 border: none !important;
2484 2496 }
2485 2497
2486 2498 li {
2487 2499 list-style-type: none
2488 2500 }
2489 2501 }
2490 2502
2491 2503 .grid-filter-box-icon {
2492 2504 line-height: 33px;
2493 2505 padding: 0;
2494 2506 width: 20px;
2495 2507 position: absolute;
2496 2508 z-index: 11;
2497 2509 left: 5px;
2498 2510 }
2499 2511
2500 2512 .grid-filter-box-input {
2501 2513 margin-right: 0;
2502 2514
2503 2515 input {
2504 2516 border: 1px solid @white;
2505 2517 padding-left: 25px;
2506 2518 width: 145px;
2507 2519
2508 2520 &:hover {
2509 2521 border-color: @grey6;
2510 2522 }
2511 2523
2512 2524 &:focus {
2513 2525 border-color: @grey5;
2514 2526 }
2515 2527 }
2516 2528 }
2517 2529
2518 2530
2519 2531
2520 2532 // Search
2521 2533
2522 2534 .search-form{
2523 2535 #q {
2524 2536 width: @search-form-width;
2525 2537 }
2526 2538 .fields{
2527 2539 margin: 0 0 @space;
2528 2540 }
2529 2541
2530 2542 label{
2531 2543 display: inline-block;
2532 2544 margin-right: @textmargin;
2533 2545 padding-top: 0.25em;
2534 2546 }
2535 2547
2536 2548
2537 2549 .results{
2538 2550 clear: both;
2539 2551 margin: 0 0 @padding;
2540 2552 }
2541 2553
2542 2554 .search-tags {
2543 2555 padding: 5px 0;
2544 2556 }
2545 2557 }
2546 2558
2547 2559 div.search-feedback-items {
2548 2560 display: inline-block;
2549 2561 }
2550 2562
2551 2563 div.search-code-body {
2552 2564 background-color: #ffffff; padding: 5px 0 5px 10px;
2553 2565 pre {
2554 2566 .match { background-color: #faffa6;}
2555 2567 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2556 2568 }
2557 2569 }
2558 2570
2559 2571 .expand_commit.search {
2560 2572 .show_more.open {
2561 2573 height: auto;
2562 2574 max-height: none;
2563 2575 }
2564 2576 }
2565 2577
2566 2578 .search-results {
2567 2579
2568 2580 h2 {
2569 2581 margin-bottom: 0;
2570 2582 }
2571 2583 .codeblock {
2572 2584 border: none;
2573 2585 background: transparent;
2574 2586 }
2575 2587
2576 2588 .codeblock-header {
2577 2589 border: none;
2578 2590 background: transparent;
2579 2591 }
2580 2592
2581 2593 .code-body {
2582 2594 border: @border-thickness solid @grey6;
2583 2595 .border-radius(@border-radius);
2584 2596 }
2585 2597
2586 2598 .td-commit {
2587 2599 &:extend(pre);
2588 2600 border-bottom: @border-thickness solid @border-default-color;
2589 2601 }
2590 2602
2591 2603 .message {
2592 2604 height: auto;
2593 2605 max-width: 350px;
2594 2606 white-space: normal;
2595 2607 text-overflow: initial;
2596 2608 overflow: visible;
2597 2609
2598 2610 .match { background-color: #faffa6;}
2599 2611 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2600 2612 }
2601 2613
2602 2614 .path {
2603 2615 border-bottom: none !important;
2604 2616 border-left: 1px solid @grey6 !important;
2605 2617 border-right: 1px solid @grey6 !important;
2606 2618 }
2607 2619 }
2608 2620
2609 2621 table.rctable td.td-search-results div {
2610 2622 max-width: 100%;
2611 2623 }
2612 2624
2613 2625 #tip-box, .tip-box{
2614 2626 padding: @menupadding/2;
2615 2627 display: block;
2616 2628 border: @border-thickness solid @border-highlight-color;
2617 2629 .border-radius(@border-radius);
2618 2630 background-color: white;
2619 2631 z-index: 99;
2620 2632 white-space: pre-wrap;
2621 2633 }
2622 2634
2623 2635 #linktt {
2624 2636 width: 79px;
2625 2637 }
2626 2638
2627 2639 #help_kb .modal-content{
2628 2640 max-width: 750px;
2629 2641 margin: 10% auto;
2630 2642
2631 2643 table{
2632 2644 td,th{
2633 2645 border-bottom: none;
2634 2646 line-height: 2.5em;
2635 2647 }
2636 2648 th{
2637 2649 padding-bottom: @textmargin/2;
2638 2650 }
2639 2651 td.keys{
2640 2652 text-align: center;
2641 2653 }
2642 2654 }
2643 2655
2644 2656 .block-left{
2645 2657 width: 45%;
2646 2658 margin-right: 5%;
2647 2659 }
2648 2660 .modal-footer{
2649 2661 clear: both;
2650 2662 }
2651 2663 .key.tag{
2652 2664 padding: 0.5em;
2653 2665 background-color: @rcblue;
2654 2666 color: white;
2655 2667 border-color: @rcblue;
2656 2668 .box-shadow(none);
2657 2669 }
2658 2670 }
2659 2671
2660 2672
2661 2673
2662 2674 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2663 2675
2664 2676 @import 'statistics-graph';
2665 2677 @import 'tables';
2666 2678 @import 'forms';
2667 2679 @import 'diff';
2668 2680 @import 'summary';
2669 2681 @import 'navigation';
2670 2682
2671 2683 //--- SHOW/HIDE SECTIONS --//
2672 2684
2673 2685 .btn-collapse {
2674 2686 float: right;
2675 2687 text-align: right;
2676 2688 font-family: @text-light;
2677 2689 font-size: @basefontsize;
2678 2690 cursor: pointer;
2679 2691 border: none;
2680 2692 color: @rcblue;
2681 2693 }
2682 2694
2683 2695 table.rctable,
2684 2696 table.dataTable {
2685 2697 .btn-collapse {
2686 2698 float: right;
2687 2699 text-align: right;
2688 2700 }
2689 2701 }
2690 2702
2691 2703 table.rctable {
2692 2704 &.permissions {
2693 2705
2694 2706 th.td-owner {
2695 2707 padding: 0;
2696 2708 }
2697 2709
2698 2710 th {
2699 2711 font-weight: normal;
2700 2712 padding: 0 5px;
2701 2713 }
2702 2714
2703 2715 }
2704 2716 }
2705 2717
2706 2718
2707 2719 // TODO: johbo: Fix for IE10, this avoids that we see a border
2708 2720 // and padding around checkboxes and radio boxes. Move to the right place,
2709 2721 // or better: Remove this once we did the form refactoring.
2710 2722 input[type=checkbox],
2711 2723 input[type=radio] {
2712 2724 padding: 0;
2713 2725 border: none;
2714 2726 }
2715 2727
2716 2728 .toggle-ajax-spinner{
2717 2729 height: 16px;
2718 2730 width: 16px;
2719 2731 }
2720 2732
2721 2733
2722 2734 .markup-form .clearfix {
2723 2735 .border-radius(@border-radius);
2724 2736 margin: 0px;
2725 2737 }
2726 2738
2727 2739 .markup-form-area {
2728 2740 padding: 8px 12px;
2729 2741 border: 1px solid @grey4;
2730 2742 .border-radius(@border-radius);
2731 2743 }
2732 2744
2733 2745 .markup-form-area-header .nav-links {
2734 2746 display: flex;
2735 2747 flex-flow: row wrap;
2736 2748 -webkit-flex-flow: row wrap;
2737 2749 width: 100%;
2738 2750 }
2739 2751
2740 2752 .markup-form-area-footer {
2741 2753 display: flex;
2742 2754 }
2743 2755
2744 2756 .markup-form-area-footer .toolbar {
2745 2757
2746 2758 }
2747 2759
2748 2760 // markup Form
2749 2761 div.markup-form {
2750 2762 margin-top: 20px;
2751 2763 }
2752 2764
2753 2765 .markup-form strong {
2754 2766 display: block;
2755 2767 margin-bottom: 15px;
2756 2768 }
2757 2769
2758 2770 .markup-form textarea {
2759 2771 width: 100%;
2760 2772 height: 100px;
2761 2773 font-family: @text-monospace;
2762 2774 }
2763 2775
2764 2776 form.markup-form {
2765 2777 margin-top: 10px;
2766 2778 margin-left: 10px;
2767 2779 }
2768 2780
2769 2781 .markup-form .comment-block-ta,
2770 2782 .markup-form .preview-box {
2771 2783 .border-radius(@border-radius);
2772 2784 .box-sizing(border-box);
2773 2785 background-color: white;
2774 2786 }
2775 2787
2776 2788 .markup-form .preview-box.unloaded {
2777 2789 height: 50px;
2778 2790 text-align: center;
2779 2791 padding: 20px;
2780 2792 background-color: white;
2781 2793 }
2782 2794
2783 2795
2784 2796 .dropzone-wrapper {
2785 2797 border: 1px solid @grey5;
2786 2798 padding: 20px;
2787 2799 }
2788 2800
2789 2801 .dropzone,
2790 2802 .dropzone-pure {
2791 2803 border: 2px dashed @grey5;
2792 2804 border-radius: 5px;
2793 2805 background: white;
2794 2806 min-height: 200px;
2795 2807 padding: 54px;
2796 2808
2797 2809 .dz-message {
2798 2810 font-weight: 700;
2799 2811 text-align: center;
2800 2812 margin: 2em 0;
2801 2813 }
2802 2814
2803 2815 }
2804 2816
2805 2817 .dz-preview {
2806 2818 margin: 10px 0 !important;
2807 2819 position: relative;
2808 2820 vertical-align: top;
2809 2821 padding: 10px;
2810 2822 border-bottom: 1px solid @grey5;
2811 2823 }
2812 2824
2813 2825 .dz-filename {
2814 2826 font-weight: 700;
2815 2827 float: left;
2816 2828 }
2817 2829
2818 2830 .dz-sending {
2819 2831 float: right;
2820 2832 }
2821 2833
2822 2834 .dz-response {
2823 2835 clear: both
2824 2836 }
2825 2837
2826 2838 .dz-filename-size {
2827 2839 float: right
2828 2840 }
2829 2841
2830 2842 .dz-error-message {
2831 2843 color: @alert2;
2832 2844 padding-top: 10px;
2833 2845 clear: both;
2834 2846 }
2835 2847
2836 2848
2837 2849 .user-hovercard {
2838 2850 padding: 5px;
2839 2851 }
2840 2852
2841 2853 .user-hovercard-icon {
2842 2854 display: inline;
2843 2855 padding: 0;
2844 2856 box-sizing: content-box;
2845 2857 border-radius: 50%;
2846 2858 float: left;
2847 2859 }
2848 2860
2849 2861 .user-hovercard-name {
2850 2862 float: right;
2851 2863 vertical-align: top;
2852 2864 padding-left: 10px;
2853 2865 min-width: 150px;
2854 2866 }
2855 2867
2856 2868 .user-hovercard-bio {
2857 2869 clear: both;
2858 2870 padding-top: 10px;
2859 2871 }
2860 2872
2861 2873 .user-hovercard-header {
2862 2874 clear: both;
2863 2875 min-height: 10px;
2864 2876 }
2865 2877
2866 2878 .user-hovercard-footer {
2867 2879 clear: both;
2868 2880 min-height: 10px;
2869 2881 }
2870 2882
2871 2883 .user-group-hovercard {
2872 2884 padding: 5px;
2873 2885 }
2874 2886
2875 2887 .user-group-hovercard-icon {
2876 2888 display: inline;
2877 2889 padding: 0;
2878 2890 box-sizing: content-box;
2879 2891 border-radius: 50%;
2880 2892 float: left;
2881 2893 }
2882 2894
2883 2895 .user-group-hovercard-name {
2884 2896 float: left;
2885 2897 vertical-align: top;
2886 2898 padding-left: 10px;
2887 2899 min-width: 150px;
2888 2900 }
2889 2901
2890 2902 .user-group-hovercard-icon i {
2891 2903 border: 1px solid @grey4;
2892 2904 border-radius: 4px;
2893 2905 }
2894 2906
2895 2907 .user-group-hovercard-bio {
2896 2908 clear: both;
2897 2909 padding-top: 10px;
2898 2910 line-height: 1.0em;
2899 2911 }
2900 2912
2901 2913 .user-group-hovercard-header {
2902 2914 clear: both;
2903 2915 min-height: 10px;
2904 2916 }
2905 2917
2906 2918 .user-group-hovercard-footer {
2907 2919 clear: both;
2908 2920 min-height: 10px;
2909 2921 }
2910 2922
2911 2923 .pr-hovercard-header {
2912 2924 clear: both;
2913 2925 display: block;
2914 2926 line-height: 20px;
2915 2927 }
2916 2928
2917 2929 .pr-hovercard-user {
2918 2930 display: flex;
2919 2931 align-items: center;
2920 2932 padding-left: 5px;
2921 2933 }
2922 2934
2923 2935 .pr-hovercard-title {
2924 2936 padding-top: 5px;
2925 2937 } No newline at end of file
@@ -1,154 +1,171 b''
1 1 // tags.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 // TAGS
6 6 .tag,
7 7 .tagtag {
8 8 display: inline-block;
9 9 min-height: 0;
10 10 margin: 0 auto;
11 11 padding: .25em;
12 12 text-align: center;
13 13 font-size: (-1 + @basefontsize); //fit in tables
14 14 line-height: 1.1em;
15 15 border: none;
16 16 box-shadow: @button-shadow;
17 17 .border-radius(@border-radius);
18 18 font-family: @text-regular;
19 19 background-image: none;
20 20 color: @grey4;
21 21 .border ( @border-thickness-tags, @grey5 );
22 22 white-space: nowrap;
23 23 a {
24 24 color: inherit;
25 25
26 26 &:hover {
27 27 color: @grey2;
28 28 }
29 29
30 30 i,
31 31 [class^="icon-"]:before,
32 32 [class*=" icon-"]:before {
33 33 text-decoration: none;
34 34 }
35 35 }
36 36
37 37 &:hover {
38 38 border-color: @grey4;
39 39 }
40 40 }
41 41
42 42 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
43 43 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
44 44 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
45 45 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
46 46 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
47 47 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
48 48 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
49 49 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
50 50 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
51 51
52 52
53 53 .tag-gist-public {
54 54 .border (@border-thickness-tags, @color1);
55 55 color: @color1;
56 56 }
57 57
58 58
59 59 .tag-gist-private {
60 60 .border (@border-thickness-tags, @color2);
61 61 color: @color2;
62 62 }
63 63
64 64
65 .tag-merge-state-created {
66 color: @color1;
67 }
68
69 .tag-merge-state-creating {
70 color: @color1;
71 }
72
73 .tag-merge-state-merging {
74 color: @color3;
75 }
76
77 .tag-merge-state-updating {
78 color: @color3;
79 }
80
81
65 82 .metatag-list {
66 83 margin: 0;
67 84 padding: 0;
68 85
69 86 li {
70 87 margin: 0 0 @padding;
71 88 line-height: 1em;
72 89 list-style-type: none;
73 90 }
74 91 }
75 92
76 93 .branchtag, .booktag {
77 94 &:extend(.tag);
78 95
79 96
80 97 a {
81 98 color:inherit;
82 99 }
83 100 }
84 101
85 102 .metatag {
86 103 &:extend(.tag);
87 104 a {
88 105 color:inherit;
89 106 text-decoration: underline;
90 107 }
91 108 }
92 109
93 110 [tag="generic"] { &:extend(.tag0); }
94 111 [tag="label"] { &:extend(.tag0); }
95 112
96 113 [tag="state featured"] { &:extend(.tag1); }
97 114 [tag="state dev"] { &:extend(.tag1); }
98 115 [tag="ref base"] { &:extend(.tag1); }
99 116
100 117 [tag="state stable"] { &:extend(.tag2); }
101 118 [tag="state stale"] { &:extend(.tag2); }
102 119
103 120 [tag="ref requires"] { &:extend(.tag3); }
104 121
105 122 [tag="state dead"] { &:extend(.tag4); }
106 123 [tag="state deprecated"] { &:extend(.tag4); }
107 124
108 125 [tag="ref conflicts"] { &:extend(.tag4); }
109 126
110 127 [tag="license"] { &:extend(.tag6); }
111 128
112 129 [tag="lang"] { &:extend(.tag7); }
113 130 [tag="language"] { &:extend(.tag7); }
114 131 [tag="ref recommends"] { &:extend(.tag7); }
115 132
116 133 [tag="see"] { &:extend(.tag8); }
117 134 [tag="url"] { &:extend(.tag8); }
118 135
119 136
120 137 .perm_overriden {
121 138 text-decoration: line-through;
122 139 opacity: 0.6;
123 140 }
124 141
125 142 .perm_tag {
126 143 &:extend(.tag);
127 144
128 145 &.read {
129 146 &:extend(.tag1);
130 147 }
131 148 &.write {
132 149 &:extend(.tag4);
133 150 }
134 151 &.admin {
135 152 &:extend(.tag5);
136 153 }
137 154 &.merge {
138 155 &:extend(.tag1);
139 156 }
140 157 &.push {
141 158 &:extend(.tag4);
142 159 }
143 160 &.push_force {
144 161 &:extend(.tag5);
145 162 }
146 163 }
147 164
148 165 .phase-draft {
149 166 color: @color3
150 167 }
151 168
152 169 .phase-secret {
153 170 color:@grey3
154 171 }
@@ -1,94 +1,91 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-body">
5 5 %if c.closed:
6 6 ${h.checkbox('show_closed',checked="checked", label=_('Show Closed Pull Requests'))}
7 7 %else:
8 8 ${h.checkbox('show_closed',label=_('Show Closed Pull Requests'))}
9 9 %endif
10 10 </div>
11 11 </div>
12 12
13 13 <div class="panel panel-default">
14 14 <div class="panel-heading">
15 15 <h3 class="panel-title">${_('Pull Requests You Participate In')}</h3>
16 16 </div>
17 17 <div class="panel-body panel-body-min-height">
18 18 <table id="pull_request_list_table" class="display"></table>
19 19 </div>
20 20 </div>
21 21
22 22 <script type="text/javascript">
23 23 $(document).ready(function() {
24 24
25 25 $('#show_closed').on('click', function(e){
26 26 if($(this).is(":checked")){
27 27 window.location = "${h.route_path('my_account_pullrequests', _query={'pr_show_closed':1})}";
28 28 }
29 29 else{
30 30 window.location = "${h.route_path('my_account_pullrequests')}";
31 31 }
32 32 });
33 33
34 34 var $pullRequestListTable = $('#pull_request_list_table');
35 35
36 36 // participating object list
37 37 $pullRequestListTable.DataTable({
38 38 processing: true,
39 39 serverSide: true,
40 40 ajax: {
41 41 "url": "${h.route_path('my_account_pullrequests_data')}",
42 42 "data": function (d) {
43 43 d.closed = "${c.closed}";
44 44 }
45 45 },
46 46 dom: 'rtp',
47 47 pageLength: ${c.visual.dashboard_items},
48 48 order: [[ 2, "desc" ]],
49 49 columns: [
50 { data: {"_": "target_repo",
51 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
50 52 { data: {"_": "status",
51 53 "sort": "status"}, title: "", className: "td-status", orderable: false},
52 { data: {"_": "target_repo",
53 "sort": "target_repo"}, title: "${_('Target Repo')}", className: "td-targetrepo", orderable: false},
54 54 { data: {"_": "name",
55 55 "sort": "name_raw"}, title: "${_('Id')}", className: "td-componentname", "type": "num" },
56 56 { data: {"_": "title",
57 57 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
58 58 { data: {"_": "author",
59 59 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
60 60 { data: {"_": "comments",
61 61 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
62 62 { data: {"_": "updated_on",
63 63 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
64 64 ],
65 65 language: {
66 66 paginate: DEFAULT_GRID_PAGINATION,
67 67 sProcessing: _gettext('loading...'),
68 68 emptyTable: _gettext("There are currently no open pull requests requiring your participation.")
69 69 },
70 70 "drawCallback": function( settings, json ) {
71 71 timeagoActivate();
72 72 tooltipActivate();
73 73 },
74 74 "createdRow": function ( row, data, index ) {
75 75 if (data['closed']) {
76 76 $(row).addClass('closed');
77 77 }
78 78 if (data['owned']) {
79 79 $(row).addClass('owned');
80 80 }
81 if (data['state'] !== 'created') {
82 $(row).addClass('state-' + data['state']);
83 }
84 81 }
85 82 });
86 83 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
87 84 $pullRequestListTable.css('opacity', 1);
88 85 });
89 86
90 87 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
91 88 $pullRequestListTable.css('opacity', 0.3);
92 89 });
93 90 });
94 91 </script>
@@ -1,474 +1,479 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 <%def name="metatags_help()">
7 7 <table>
8 8 <%
9 9 example_tags = [
10 10 ('state','[stable]'),
11 11 ('state','[stale]'),
12 12 ('state','[featured]'),
13 13 ('state','[dev]'),
14 14 ('state','[dead]'),
15 15 ('state','[deprecated]'),
16 16
17 17 ('label','[personal]'),
18 18 ('generic','[v2.0.0]'),
19 19
20 20 ('lang','[lang =&gt; JavaScript]'),
21 21 ('license','[license =&gt; LicenseName]'),
22 22
23 23 ('ref','[requires =&gt; RepoName]'),
24 24 ('ref','[recommends =&gt; GroupName]'),
25 25 ('ref','[conflicts =&gt; SomeName]'),
26 26 ('ref','[base =&gt; SomeName]'),
27 27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 28 ('see','[see =&gt; http://rhodecode.com]'),
29 29 ]
30 30 %>
31 31 % for tag_type, tag in example_tags:
32 32 <tr>
33 33 <td>${tag|n}</td>
34 34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 35 </tr>
36 36 % endfor
37 37 </table>
38 38 </%def>
39 39
40 40 <%def name="render_description(description, stylify_metatags)">
41 41 <%
42 42 tags = []
43 43 if stylify_metatags:
44 44 tags, description = h.extract_metatags(description)
45 45 %>
46 46 % for tag_type, tag in tags:
47 47 ${h.style_metatag(tag_type, tag)|n,trim}
48 48 % endfor
49 49 <code style="white-space: pre-wrap">${description}</code>
50 50 </%def>
51 51
52 52 ## REPOSITORY RENDERERS
53 53 <%def name="quick_menu(repo_name)">
54 54 <i class="icon-more"></i>
55 55 <div class="menu_items_container hidden">
56 56 <ul class="menu_items">
57 57 <li>
58 58 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
59 59 <span>${_('Summary')}</span>
60 60 </a>
61 61 </li>
62 62 <li>
63 63 <a title="${_('Commits')}" href="${h.route_path('repo_commits',repo_name=repo_name)}">
64 64 <span>${_('Commits')}</span>
65 65 </a>
66 66 </li>
67 67 <li>
68 68 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
69 69 <span>${_('Files')}</span>
70 70 </a>
71 71 </li>
72 72 <li>
73 73 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
74 74 <span>${_('Fork')}</span>
75 75 </a>
76 76 </li>
77 77 </ul>
78 78 </div>
79 79 </%def>
80 80
81 81 <%def name="repo_name(name,rtype,rstate,private,archived,fork_of,short_name=False,admin=False)">
82 82 <%
83 83 def get_name(name,short_name=short_name):
84 84 if short_name:
85 85 return name.split('/')[-1]
86 86 else:
87 87 return name
88 88 %>
89 89 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
90 90 ##NAME
91 91 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
92 92
93 93 ##TYPE OF REPO
94 94 %if h.is_hg(rtype):
95 95 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
96 96 %elif h.is_git(rtype):
97 97 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
98 98 %elif h.is_svn(rtype):
99 99 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
100 100 %endif
101 101
102 102 ##PRIVATE/PUBLIC
103 103 %if private is True and c.visual.show_private_icon:
104 104 <i class="icon-lock" title="${_('Private repository')}"></i>
105 105 %elif private is False and c.visual.show_public_icon:
106 106 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
107 107 %else:
108 108 <span></span>
109 109 %endif
110 110 ${get_name(name)}
111 111 </a>
112 112 %if fork_of:
113 113 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
114 114 %endif
115 115 %if rstate == 'repo_state_pending':
116 116 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
117 117 (${_('creating...')})
118 118 </span>
119 119 %endif
120 120
121 121 </div>
122 122 </%def>
123 123
124 124 <%def name="repo_desc(description, stylify_metatags)">
125 125 <%
126 126 tags, description = h.extract_metatags(description)
127 127 %>
128 128
129 129 <div class="truncate-wrap">
130 130 % if stylify_metatags:
131 131 % for tag_type, tag in tags:
132 132 ${h.style_metatag(tag_type, tag)|n}
133 133 % endfor
134 134 % endif
135 135 ${description}
136 136 </div>
137 137
138 138 </%def>
139 139
140 140 <%def name="last_change(last_change)">
141 141 ${h.age_component(last_change, time_is_local=True)}
142 142 </%def>
143 143
144 144 <%def name="revision(repo_name, rev, commit_id, author, last_msg, commit_date)">
145 145 <div>
146 146 %if rev >= 0:
147 147 <code><a class="tooltip-hovercard" data-hovercard-alt=${h.tooltip(last_msg)} data-hovercard-url="${h.route_path('hovercard_repo_commit', repo_name=repo_name, commit_id=commit_id)}" href="${h.route_path('repo_commit',repo_name=repo_name,commit_id=commit_id)}">${'r{}:{}'.format(rev,h.short_id(commit_id))}</a></code>
148 148 %else:
149 149 ${_('No commits yet')}
150 150 %endif
151 151 </div>
152 152 </%def>
153 153
154 154 <%def name="rss(name)">
155 155 %if c.rhodecode_user.username != h.DEFAULT_USER:
156 156 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
157 157 %else:
158 158 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
159 159 %endif
160 160 </%def>
161 161
162 162 <%def name="atom(name)">
163 163 %if c.rhodecode_user.username != h.DEFAULT_USER:
164 164 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
165 165 %else:
166 166 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
167 167 %endif
168 168 </%def>
169 169
170 170 <%def name="repo_actions(repo_name, super_user=True)">
171 171 <div>
172 172 <div class="grid_edit">
173 173 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
174 174 Edit
175 175 </a>
176 176 </div>
177 177 <div class="grid_delete">
178 178 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
179 179 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
180 180 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
181 181 ${h.end_form()}
182 182 </div>
183 183 </div>
184 184 </%def>
185 185
186 186 <%def name="repo_state(repo_state)">
187 187 <div>
188 188 %if repo_state == 'repo_state_pending':
189 189 <div class="tag tag4">${_('Creating')}</div>
190 190 %elif repo_state == 'repo_state_created':
191 191 <div class="tag tag1">${_('Created')}</div>
192 192 %else:
193 193 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
194 194 %endif
195 195 </div>
196 196 </%def>
197 197
198 198
199 199 ## REPO GROUP RENDERERS
200 200 <%def name="quick_repo_group_menu(repo_group_name)">
201 201 <i class="icon-more"></i>
202 202 <div class="menu_items_container hidden">
203 203 <ul class="menu_items">
204 204 <li>
205 205 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
206 206 </li>
207 207
208 208 </ul>
209 209 </div>
210 210 </%def>
211 211
212 212 <%def name="repo_group_name(repo_group_name, children_groups=None)">
213 213 <div>
214 214 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
215 215 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
216 216 %if children_groups:
217 217 ${h.literal(' &raquo; '.join(children_groups))}
218 218 %else:
219 219 ${repo_group_name}
220 220 %endif
221 221 </a>
222 222 </div>
223 223 </%def>
224 224
225 225 <%def name="repo_group_desc(description, personal, stylify_metatags)">
226 226
227 227 <%
228 228 if stylify_metatags:
229 229 tags, description = h.extract_metatags(description)
230 230 %>
231 231
232 232 <div class="truncate-wrap">
233 233 % if personal:
234 234 <div class="metatag" tag="personal">${_('personal')}</div>
235 235 % endif
236 236
237 237 % if stylify_metatags:
238 238 % for tag_type, tag in tags:
239 239 ${h.style_metatag(tag_type, tag)|n}
240 240 % endfor
241 241 % endif
242 242 ${description}
243 243 </div>
244 244
245 245 </%def>
246 246
247 247 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
248 248 <div class="grid_edit">
249 249 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
250 250 </div>
251 251 <div class="grid_delete">
252 252 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
253 253 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
254 254 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
255 255 ${h.end_form()}
256 256 </div>
257 257 </%def>
258 258
259 259
260 260 <%def name="user_actions(user_id, username)">
261 261 <div class="grid_edit">
262 262 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
263 263 ${_('Edit')}
264 264 </a>
265 265 </div>
266 266 <div class="grid_delete">
267 267 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
268 268 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
269 269 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
270 270 ${h.end_form()}
271 271 </div>
272 272 </%def>
273 273
274 274 <%def name="user_group_actions(user_group_id, user_group_name)">
275 275 <div class="grid_edit">
276 276 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
277 277 </div>
278 278 <div class="grid_delete">
279 279 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
280 280 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
281 281 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
282 282 ${h.end_form()}
283 283 </div>
284 284 </%def>
285 285
286 286
287 287 <%def name="user_name(user_id, username)">
288 288 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
289 289 </%def>
290 290
291 291 <%def name="user_profile(username)">
292 292 ${base.gravatar_with_user(username, 16, tooltip=True)}
293 293 </%def>
294 294
295 295 <%def name="user_group_name(user_group_name)">
296 296 <div>
297 297 <i class="icon-user-group" title="${_('User group')}"></i>
298 298 ${h.link_to_group(user_group_name)}
299 299 </div>
300 300 </%def>
301 301
302 302
303 303 ## GISTS
304 304
305 305 <%def name="gist_gravatar(full_contact)">
306 306 <div class="gist_gravatar">
307 307 ${base.gravatar(full_contact, 30)}
308 308 </div>
309 309 </%def>
310 310
311 311 <%def name="gist_access_id(gist_access_id, full_contact)">
312 312 <div>
313 313 <b>
314 314 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
315 315 </b>
316 316 </div>
317 317 </%def>
318 318
319 319 <%def name="gist_author(full_contact, created_on, expires)">
320 320 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
321 321 </%def>
322 322
323 323
324 324 <%def name="gist_created(created_on)">
325 325 <div class="created">
326 326 ${h.age_component(created_on, time_is_local=True)}
327 327 </div>
328 328 </%def>
329 329
330 330 <%def name="gist_expires(expires)">
331 331 <div class="created">
332 332 %if expires == -1:
333 333 ${_('never')}
334 334 %else:
335 335 ${h.age_component(h.time_to_utcdatetime(expires))}
336 336 %endif
337 337 </div>
338 338 </%def>
339 339
340 340 <%def name="gist_type(gist_type)">
341 341 %if gist_type == 'public':
342 342 <span class="tag tag-gist-public disabled">${_('Public Gist')}</span>
343 343 %else:
344 344 <span class="tag tag-gist-private disabled">${_('Private Gist')}</span>
345 345 %endif
346 346 </%def>
347 347
348 348 <%def name="gist_description(gist_description)">
349 349 ${gist_description}
350 350 </%def>
351 351
352 352
353 353 ## PULL REQUESTS GRID RENDERERS
354 354
355 355 <%def name="pullrequest_target_repo(repo_name)">
356 356 <div class="truncate">
357 357 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
358 358 </div>
359 359 </%def>
360 360
361 361 <%def name="pullrequest_status(status)">
362 362 <i class="icon-circle review-status-${status}"></i>
363 363 </%def>
364 364
365 365 <%def name="pullrequest_title(title, description)">
366 366 ${title}
367 367 </%def>
368 368
369 369 <%def name="pullrequest_comments(comments_nr)">
370 370 <i class="icon-comment"></i> ${comments_nr}
371 371 </%def>
372 372
373 <%def name="pullrequest_name(pull_request_id, is_wip, target_repo_name, short=False)">
373 <%def name="pullrequest_name(pull_request_id, state, is_wip, target_repo_name, short=False)">
374 374 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
375 % if is_wip:
376 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
377 % endif
378 375
379 376 % if short:
380 377 !${pull_request_id}
381 378 % else:
382 379 ${_('Pull request !{}').format(pull_request_id)}
383 380 % endif
381
382 % if state not in ['created']:
383 <span class="tag tag-merge-state-${state} tooltip" title="Pull request state is changing">${state}</span>
384 % endif
385
386 % if is_wip:
387 <span class="tag tooltip" title="${_('Work in progress')}">wip</span>
388 % endif
384 389 </a>
385 390 </%def>
386 391
387 392 <%def name="pullrequest_updated_on(updated_on)">
388 393 ${h.age_component(h.time_to_utcdatetime(updated_on))}
389 394 </%def>
390 395
391 396 <%def name="pullrequest_author(full_contact)">
392 397 ${base.gravatar_with_user(full_contact, 16, tooltip=True)}
393 398 </%def>
394 399
395 400
396 401 ## ARTIFACT RENDERERS
397 402 <%def name="repo_artifact_name(repo_name, file_uid, artifact_display_name)">
398 403 <a href="${h.route_path('repo_artifacts_get', repo_name=repo_name, uid=file_uid)}">
399 404 ${artifact_display_name or '_EMPTY_NAME_'}
400 405 </a>
401 406 </%def>
402 407
403 408 <%def name="repo_artifact_uid(repo_name, file_uid)">
404 409 <code>${h.shorter(file_uid, size=24, prefix=True)}</code>
405 410 </%def>
406 411
407 412 <%def name="repo_artifact_sha256(artifact_sha256)">
408 413 <div class="code">${h.shorter(artifact_sha256, 12)}</div>
409 414 </%def>
410 415
411 416 <%def name="repo_artifact_actions(repo_name, file_store_id, file_uid)">
412 417 ## <div class="grid_edit">
413 418 ## <a href="#Edit" title="${_('Edit')}">${_('Edit')}</a>
414 419 ## </div>
415 420 <div class="grid_edit">
416 421 <a href="${h.route_path('repo_artifacts_info', repo_name=repo_name, uid=file_store_id)}" title="${_('Info')}">${_('Info')}</a>
417 422 </div>
418 423 % if h.HasRepoPermissionAny('repository.admin')(c.repo_name):
419 424 <div class="grid_delete">
420 425 ${h.secure_form(h.route_path('repo_artifacts_delete', repo_name=repo_name, uid=file_store_id), request=request)}
421 426 ${h.submit('remove_',_('Delete'),id="remove_artifact_%s" % file_store_id, class_="btn btn-link btn-danger",
422 427 onclick="return confirm('"+_('Confirm to delete this artifact: %s') % file_uid+"');")}
423 428 ${h.end_form()}
424 429 </div>
425 430 % endif
426 431 </%def>
427 432
428 433 <%def name="markup_form(form_id, form_text='', help_text=None)">
429 434
430 435 <div class="markup-form">
431 436 <div class="markup-form-area">
432 437 <div class="markup-form-area-header">
433 438 <ul class="nav-links clearfix">
434 439 <li class="active">
435 440 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
436 441 </li>
437 442 <li class="">
438 443 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
439 444 </li>
440 445 </ul>
441 446 </div>
442 447
443 448 <div class="markup-form-area-write" style="display: block;">
444 449 <div id="edit-container_${form_id}">
445 450 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
446 451 </div>
447 452 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
448 453 <div id="preview-box_${form_id}" class="preview-box"></div>
449 454 </div>
450 455 </div>
451 456
452 457 <div class="markup-form-area-footer">
453 458 <div class="toolbar">
454 459 <div class="toolbar-text">
455 460 ${(_('Parsed using %s syntax') % (
456 461 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
457 462 )
458 463 )|n}
459 464 </div>
460 465 </div>
461 466 </div>
462 467 </div>
463 468
464 469 <div class="markup-form-footer">
465 470 % if help_text:
466 471 <span class="help-block">${help_text}</span>
467 472 % endif
468 473 </div>
469 474 </div>
470 475 <script type="text/javascript">
471 476 new MarkupForm('${form_id}');
472 477 </script>
473 478
474 479 </%def>
@@ -1,809 +1,822 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4
5 5 <%def name="title()">
6 6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 7 %if c.rhodecode_name:
8 8 &middot; ${h.branding(c.rhodecode_name)}
9 9 %endif
10 10 </%def>
11 11
12 12 <%def name="breadcrumbs_links()">
13 <span id="pr-title">
14 ${c.pull_request.title}
15 %if c.pull_request.is_closed():
16 (${_('Closed')})
17 %endif
18 </span>
13 <%
14 pr_title = c.pull_request.title
15 if c.pull_request.is_closed():
16 pr_title = '[{}] {}'.format(_('Closed'), pr_title)
17 %>
18
19 <div id="pr-title">
20 <input class="pr-title-input large disabled" disabled="disabled" name="pullrequest_title" type="text" value="${pr_title}">
21 </div>
19 22 <div id="pr-title-edit" class="input" style="display: none;">
20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
23 <input class="pr-title-input large" id="pr-title-input" name="pullrequest_title" type="text" value="${c.pull_request.title}">
21 24 </div>
22 25 </%def>
23 26
24 27 <%def name="menu_bar_nav()">
25 28 ${self.menu_items(active='repositories')}
26 29 </%def>
27 30
28 31 <%def name="menu_bar_subnav()">
29 32 ${self.repo_menu(active='showpullrequest')}
30 33 </%def>
31 34
32 35 <%def name="main()">
33 36
34 37 <script type="text/javascript">
35 38 // TODO: marcink switch this to pyroutes
36 39 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 40 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 41 </script>
39 42 <div class="box">
40 43
41 44 ${self.breadcrumbs()}
42 45
43 46 <div class="box pr-summary">
44 47
45 48 <div class="summary-details block-left">
46 49 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 50 <div class="pr-details-title">
48 51 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 52 %if c.allowed_to_update:
50 53 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 54 % if c.allowed_to_delete:
52 55 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 56 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 57 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 58 ${h.end_form()}
56 59 % else:
57 60 ${_('Delete')}
58 61 % endif
59 62 </div>
60 63 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 64 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 65 %endif
63 66 </div>
64 67
65 68 <div id="summary" class="fields pr-details-content">
66 69 <div class="field">
67 70 <div class="label-summary">
68 71 <label>${_('Source')}:</label>
69 72 </div>
70 73 <div class="input">
71 74 <div class="pr-origininfo">
72 75 ## branch link is only valid if it is a branch
73 76 <span class="tag">
74 77 %if c.pull_request.source_ref_parts.type == 'branch':
75 78 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 79 %else:
77 80 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 81 %endif
79 82 </span>
80 83 <span class="clone-url">
81 84 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 85 </span>
83 86 <br/>
84 87 % if c.ancestor_commit:
85 88 ${_('Common ancestor')}:
86 89 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 90 % endif
88 91 </div>
89 92 %if h.is_hg(c.pull_request.source_repo):
90 93 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 94 %elif h.is_git(c.pull_request.source_repo):
92 95 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 96 %endif
94 97
95 98 <div class="">
96 99 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 100 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 101 </div>
99 102
100 103 </div>
101 104 </div>
102 105 <div class="field">
103 106 <div class="label-summary">
104 107 <label>${_('Target')}:</label>
105 108 </div>
106 109 <div class="input">
107 110 <div class="pr-targetinfo">
108 111 ## branch link is only valid if it is a branch
109 112 <span class="tag">
110 113 %if c.pull_request.target_ref_parts.type == 'branch':
111 114 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 115 %else:
113 116 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 117 %endif
115 118 </span>
116 119 <span class="clone-url">
117 120 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 121 </span>
119 122 </div>
120 123 </div>
121 124 </div>
122 125
123 126 ## Link to the shadow repository.
124 127 <div class="field">
125 128 <div class="label-summary">
126 129 <label>${_('Merge')}:</label>
127 130 </div>
128 131 <div class="input">
129 132 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 133 %if h.is_hg(c.pull_request.target_repo):
131 134 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 135 %elif h.is_git(c.pull_request.target_repo):
133 136 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 137 %endif
135 138 <div class="">
136 139 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 140 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 141 </div>
139 142 % else:
140 143 <div class="">
141 144 ${_('Shadow repository data not available')}.
142 145 </div>
143 146 % endif
144 147 </div>
145 148 </div>
146 149
147 150 <div class="field">
148 151 <div class="label-summary">
149 152 <label>${_('Review')}:</label>
150 153 </div>
151 154 <div class="input">
152 155 %if c.pull_request_review_status:
153 156 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 <span class="changeset-status-lbl tooltip">
157 <span class="changeset-status-lbl">
155 158 %if c.pull_request.is_closed():
156 159 ${_('Closed')},
157 160 %endif
158 161 ${h.commit_status_lbl(c.pull_request_review_status)}
159 162 </span>
160 163 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 164 %endif
162 165 </div>
163 166 </div>
164 167 <div class="field">
165 168 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 169 <label>${_('Description')}:</label>
167 170 </div>
168 171 <div id="pr-desc" class="input">
169 172 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}</div>
170 173 </div>
171 174 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 175 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 176 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 177 </div>
175 178 </div>
176 179
177 180 <div class="field">
178 181 <div class="label-summary">
179 182 <label>${_('Versions')}:</label>
180 183 </div>
181 184
182 185 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 186 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184 187
185 188 <div class="pr-versions">
186 189 % if c.show_version_changes:
187 190 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 191 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 192 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 193 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 194 data-toggle-off="${_('Hide all versions of this pull request')}">
192 195 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 196 </a>
194 197 <table>
195 198 ## SHOW ALL VERSIONS OF PR
196 199 <% ver_pr = None %>
197 200
198 201 % for data in reversed(list(enumerate(c.versions, 1))):
199 202 <% ver_pos = data[0] %>
200 203 <% ver = data[1] %>
201 204 <% ver_pr = ver.pull_request_version_id %>
202 205 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203 206
204 207 <tr class="version-pr" style="display: ${display_row}">
205 208 <td>
206 209 <code>
207 210 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 211 </code>
209 212 </td>
210 213 <td>
211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
214 <input ${('checked="checked"' if c.from_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
215 <input ${('checked="checked"' if c.at_version_num == ver_pr else '')} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 216 </td>
214 217 <td>
215 218 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 219 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 </div>
220
218 221 </td>
219 222 <td>
220 223 % if c.at_version_num != ver_pr:
221 224 <i class="icon-comment"></i>
222 225 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 226 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 227 </code>
225 228 % endif
226 229 </td>
227 230 <td>
228 231 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 232 </td>
230 233 <td>
231 234 ${h.age_component(ver.updated_on, time_is_local=True)}
232 235 </td>
233 236 </tr>
234 237 % endfor
235 238
236 239 <tr>
237 240 <td colspan="6">
238 241 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 242 data-label-text-locked="${_('select versions to show changes')}"
240 243 data-label-text-diff="${_('show changes between versions')}"
241 244 data-label-text-show="${_('show pull request for this version')}"
242 245 >
243 246 ${_('select versions to show changes')}
244 247 </button>
245 248 </td>
246 249 </tr>
247 250 </table>
248 251 % else:
249 252 <div class="input">
250 253 ${_('Pull request versions not available')}.
251 254 </div>
252 255 % endif
253 256 </div>
254 257 </div>
255 258
256 259 <div id="pr-save" class="field" style="display: none;">
257 260 <div class="label-summary"></div>
258 261 <div class="input">
259 262 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
260 263 </div>
261 264 </div>
262 265 </div>
263 266 </div>
264 267 <div>
265 268 ## AUTHOR
266 269 <div class="reviewers-title block-right">
267 270 <div class="pr-details-title">
268 271 ${_('Author of this pull request')}
269 272 </div>
270 273 </div>
271 274 <div class="block-right pr-details-content reviewers">
272 275 <ul class="group_members">
273 276 <li>
274 277 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
275 278 </li>
276 279 </ul>
277 280 </div>
278 281
279 282 ## REVIEW RULES
280 283 <div id="review_rules" style="display: none" class="reviewers-title block-right">
281 284 <div class="pr-details-title">
282 285 ${_('Reviewer rules')}
283 286 %if c.allowed_to_update:
284 287 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
285 288 %endif
286 289 </div>
287 290 <div class="pr-reviewer-rules">
288 291 ## review rules will be appended here, by default reviewers logic
289 292 </div>
290 293 <input id="review_data" type="hidden" name="review_data" value="">
291 294 </div>
292 295
293 296 ## REVIEWERS
294 297 <div class="reviewers-title block-right">
295 298 <div class="pr-details-title">
296 299 ${_('Pull request reviewers')}
297 300 %if c.allowed_to_update:
298 301 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
299 302 %endif
300 303 </div>
301 304 </div>
302 305 <div id="reviewers" class="block-right pr-details-content reviewers">
303 306
304 307 ## members redering block
305 308 <input type="hidden" name="__start__" value="review_members:sequence">
306 309 <ul id="review_members" class="group_members">
307 310
308 311 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
309 312 <script>
310 313 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
311 314 var status = "${(status[0][1].status if status else 'not_reviewed')}";
312 315 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
313 316 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
314 317
315 318 var entry = renderTemplate('reviewMemberEntry', {
316 319 'member': member,
317 320 'mandatory': member.mandatory,
318 321 'reasons': member.reasons,
319 322 'allowed_to_update': allowed_to_update,
320 323 'review_status': status,
321 324 'review_status_label': status_lbl,
322 325 'user_group': member.user_group,
323 326 'create': false
324 327 });
325 328 $('#review_members').append(entry)
326 329 </script>
327 330
328 331 % endfor
329 332
330 333 </ul>
331 334
332 335 <input type="hidden" name="__end__" value="review_members:sequence">
333 336 ## end members redering block
334 337
335 338 %if not c.pull_request.is_closed():
336 339 <div id="add_reviewer" class="ac" style="display: none;">
337 340 %if c.allowed_to_update:
338 341 % if not c.forbid_adding_reviewers:
339 342 <div id="add_reviewer_input" class="reviewer_ac">
340 343 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
341 344 <div id="reviewers_container"></div>
342 345 </div>
343 346 % endif
344 347 <div class="pull-right">
345 348 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
346 349 </div>
347 350 %endif
348 351 </div>
349 352 %endif
350 353 </div>
351 354 </div>
352 355 </div>
356
353 357 <div class="box">
354 ##DIFF
358
359 % if c.state_progressing:
360 <h2 style="text-align: center">
361 ${_('Cannot show diff when pull request state is changing. Current progress state')}: <span class="tag tag-merge-state-${c.pull_request.state}">${c.pull_request.state}</span>
362 </h2>
363
364 % else:
365
366 ## Diffs rendered here
355 367 <div class="table" >
356 368 <div id="changeset_compare_view_content">
357 369 ##CS
358 370 % if c.missing_requirements:
359 371 <div class="box">
360 372 <div class="alert alert-warning">
361 373 <div>
362 374 <strong>${_('Missing requirements:')}</strong>
363 375 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
364 376 </div>
365 377 </div>
366 378 </div>
367 379 % elif c.missing_commits:
368 380 <div class="box">
369 381 <div class="alert alert-warning">
370 382 <div>
371 383 <strong>${_('Missing commits')}:</strong>
372 384 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
373 385 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
374 386 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
375 387 </div>
376 388 </div>
377 389 </div>
378 390 % endif
379 391
380 392 <div class="compare_view_commits_title">
381 393 % if not c.compare_mode:
382 394
383 395 % if c.at_version_pos:
384 396 <h4>
385 397 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
386 398 </h4>
387 399 % endif
388 400
389 401 <div class="pull-left">
390 402 <div class="btn-group">
391 403 <a
392 404 class="btn"
393 405 href="#"
394 406 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
395 407 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
396 408 </a>
397 409 <a
398 410 class="btn"
399 411 href="#"
400 412 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
401 413 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
402 414 </a>
403 415 </div>
404 416 </div>
405 417
406 418 <div class="pull-right">
407 419 % if c.allowed_to_update and not c.pull_request.is_closed():
408 420
409 421 <div class="btn-group btn-group-actions">
410 422 <a id="update_commits" class="btn btn-primary no-margin" onclick="updateController.updateCommits(this); return false">
411 423 ${_('Update commits')}
412 424 </a>
413 425
414 426 <a id="update_commits_switcher" class="btn btn-primary" style="margin-left: -1px" data-toggle="dropdown" aria-pressed="false" role="button">
415 427 <i class="icon-down"></i>
416 428 </a>
417 429
418 430 <div class="btn-action-switcher-container" id="update-commits-switcher">
419 431 <ul class="btn-action-switcher" role="menu">
420 432 <li>
421 433 <a href="#forceUpdate" onclick="updateController.forceUpdateCommits(this); return false">
422 434 ${_('Force update commits')}
423 435 </a>
424 436 <div class="action-help-block">
425 437 ${_('Update commits and force refresh this pull request.')}
426 438 </div>
427 439 </li>
428 440 </ul>
429 441 </div>
430 442 </div>
431 443
432 444 % else:
433 445 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
434 446 % endif
435 447
436 448 </div>
437 449 % endif
438 450 </div>
439 451
440 452 % if not c.missing_commits:
441 453 % if c.compare_mode:
442 454 % if c.at_version:
443 455 <h4>
444 456 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
445 457 </h4>
446 458
447 459 <div class="subtitle-compare">
448 460 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
449 461 </div>
450 462
451 463 <div class="container">
452 464 <table class="rctable compare_view_commits">
453 465 <tr>
454 466 <th></th>
455 467 <th>${_('Time')}</th>
456 468 <th>${_('Author')}</th>
457 469 <th>${_('Commit')}</th>
458 470 <th></th>
459 471 <th>${_('Description')}</th>
460 472 </tr>
461 473
462 474 % for c_type, commit in c.commit_changes:
463 475 % if c_type in ['a', 'r']:
464 476 <%
465 477 if c_type == 'a':
466 478 cc_title = _('Commit added in displayed changes')
467 479 elif c_type == 'r':
468 480 cc_title = _('Commit removed in displayed changes')
469 481 else:
470 482 cc_title = ''
471 483 %>
472 484 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
473 485 <td>
474 486 <div class="commit-change-indicator color-${c_type}-border">
475 487 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
476 488 ${c_type.upper()}
477 489 </div>
478 490 </div>
479 491 </td>
480 492 <td class="td-time">
481 493 ${h.age_component(commit.date)}
482 494 </td>
483 495 <td class="td-user">
484 496 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
485 497 </td>
486 498 <td class="td-hash">
487 499 <code>
488 500 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
489 501 r${commit.idx}:${h.short_id(commit.raw_id)}
490 502 </a>
491 503 ${h.hidden('revisions', commit.raw_id)}
492 504 </code>
493 505 </td>
494 506 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
495 507 <i class="icon-expand-linked"></i>
496 508 </td>
497 509 <td class="mid td-description">
498 510 <div class="log-container truncate-wrap">
499 511 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
500 512 </div>
501 513 </td>
502 514 </tr>
503 515 % endif
504 516 % endfor
505 517 </table>
506 518 </div>
507 519
508 520 % endif
509 521
510 522 % else:
511 523 <%include file="/compare/compare_commits.mako" />
512 524 % endif
513 525
514 526 <div class="cs_files">
515 527 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
516 528 % if c.at_version:
517 529 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
518 530 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
519 531 % else:
520 532 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
521 533 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
522 534 % endif
523 535
524 536 <%
525 537 pr_menu_data = {
526 538 'outdated_comm_count_ver': outdated_comm_count_ver
527 539 }
528 540 %>
529 541
530 542 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
531 543
532 544 % if c.range_diff_on:
533 545 % for commit in c.commit_ranges:
534 546 ${cbdiffs.render_diffset(
535 547 c.changes[commit.raw_id],
536 548 commit=commit, use_comments=True,
537 549 collapse_when_files_over=5,
538 550 disable_new_comments=True,
539 551 deleted_files_comments=c.deleted_files_comments,
540 552 inline_comments=c.inline_comments,
541 553 pull_request_menu=pr_menu_data)}
542 554 % endfor
543 555 % else:
544 556 ${cbdiffs.render_diffset(
545 557 c.diffset, use_comments=True,
546 558 collapse_when_files_over=30,
547 559 disable_new_comments=not c.allowed_to_comment,
548 560 deleted_files_comments=c.deleted_files_comments,
549 561 inline_comments=c.inline_comments,
550 562 pull_request_menu=pr_menu_data)}
551 563 % endif
552 564
553 565 </div>
554 566 % else:
555 567 ## skipping commits we need to clear the view for missing commits
556 568 <div style="clear:both;"></div>
557 569 % endif
558 570
559 571 </div>
560 572 </div>
561 573
562 574 ## template for inline comment form
563 575 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
564 576
565 577 ## comments heading with count
566 578 <div class="comments-heading">
567 579 <i class="icon-comment"></i>
568 580 ${_('Comments')} ${len(c.comments)}
569 581 </div>
570 582
571 583 ## render general comments
572 584 <div id="comment-tr-show">
573 585 % if general_outdated_comm_count_ver:
574 586 <div class="info-box">
575 587 % if general_outdated_comm_count_ver == 1:
576 588 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
577 589 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
578 590 % else:
579 591 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
580 592 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
581 593 % endif
582 594 </div>
583 595 % endif
584 596 </div>
585 597
586 598 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
587 599
588 600 % if not c.pull_request.is_closed():
589 601 ## main comment form and it status
590 602 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
591 603 pull_request_id=c.pull_request.pull_request_id),
592 604 c.pull_request_review_status,
593 605 is_pull_request=True, change_status=c.allowed_to_change_status)}
594 606
595 607 ## merge status, and merge action
596 608 <div class="pull-request-merge">
597 609 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
598 610 </div>
599 611
600 612 %endif
601 613
602 <script type="text/javascript">
614 % endif
615 </div>
616
617 <script type="text/javascript">
603 618
604 619 versionController = new VersionController();
605 620 versionController.init();
606 621
607 622 reviewersController = new ReviewersController();
608 623 commitsController = new CommitsController();
609 624
610 625 updateController = new UpdatePrController();
611 626
612 627 $(function(){
613 628
614 629 // custom code mirror
615 630 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
616 631
617 632 var PRDetails = {
618 633 editButton: $('#open_edit_pullrequest'),
619 634 closeButton: $('#close_edit_pullrequest'),
620 635 deleteButton: $('#delete_pullrequest'),
621 636 viewFields: $('#pr-desc, #pr-title'),
622 637 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
623 638
624 639 init: function() {
625 640 var that = this;
626 641 this.editButton.on('click', function(e) { that.edit(); });
627 642 this.closeButton.on('click', function(e) { that.view(); });
628 643 },
629 644
630 645 edit: function(event) {
631 646 this.viewFields.hide();
632 647 this.editButton.hide();
633 648 this.deleteButton.hide();
634 649 this.closeButton.show();
635 650 this.editFields.show();
636 651 codeMirrorInstance.refresh();
637 652 },
638 653
639 654 view: function(event) {
640 655 this.editButton.show();
641 656 this.deleteButton.show();
642 657 this.editFields.hide();
643 658 this.closeButton.hide();
644 659 this.viewFields.show();
645 660 }
646 661 };
647 662
648 663 var ReviewersPanel = {
649 664 editButton: $('#open_edit_reviewers'),
650 665 closeButton: $('#close_edit_reviewers'),
651 666 addButton: $('#add_reviewer'),
652 667 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
653 668
654 669 init: function() {
655 670 var self = this;
656 671 this.editButton.on('click', function(e) { self.edit(); });
657 672 this.closeButton.on('click', function(e) { self.close(); });
658 673 },
659 674
660 675 edit: function(event) {
661 676 this.editButton.hide();
662 677 this.closeButton.show();
663 678 this.addButton.show();
664 679 this.removeButtons.css('visibility', 'visible');
665 680 // review rules
666 681 reviewersController.loadReviewRules(
667 682 ${c.pull_request.reviewer_data_json | n});
668 683 },
669 684
670 685 close: function(event) {
671 686 this.editButton.show();
672 687 this.closeButton.hide();
673 688 this.addButton.hide();
674 689 this.removeButtons.css('visibility', 'hidden');
675 690 // hide review rules
676 691 reviewersController.hideReviewRules()
677 692 }
678 693 };
679 694
680 695 PRDetails.init();
681 696 ReviewersPanel.init();
682 697
683 698 showOutdated = function(self){
684 699 $('.comment-inline.comment-outdated').show();
685 700 $('.filediff-outdated').show();
686 701 $('.showOutdatedComments').hide();
687 702 $('.hideOutdatedComments').show();
688 703 };
689 704
690 705 hideOutdated = function(self){
691 706 $('.comment-inline.comment-outdated').hide();
692 707 $('.filediff-outdated').hide();
693 708 $('.hideOutdatedComments').hide();
694 709 $('.showOutdatedComments').show();
695 710 };
696 711
697 712 refreshMergeChecks = function(){
698 713 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
699 714 $('.pull-request-merge').css('opacity', 0.3);
700 715 $('.action-buttons-extra').css('opacity', 0.3);
701 716
702 717 $('.pull-request-merge').load(
703 718 loadUrl, function() {
704 719 $('.pull-request-merge').css('opacity', 1);
705 720
706 721 $('.action-buttons-extra').css('opacity', 1);
707 722 }
708 723 );
709 724 };
710 725
711 726 closePullRequest = function (status) {
712 727 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
713 728 return false;
714 729 }
715 730 // inject closing flag
716 731 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
717 732 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
718 733 $(generalCommentForm.submitForm).submit();
719 734 };
720 735
721 736 $('#show-outdated-comments').on('click', function(e){
722 737 var button = $(this);
723 738 var outdated = $('.comment-outdated');
724 739
725 740 if (button.html() === "(Show)") {
726 741 button.html("(Hide)");
727 742 outdated.show();
728 743 } else {
729 744 button.html("(Show)");
730 745 outdated.hide();
731 746 }
732 747 });
733 748
734 749 $('.show-inline-comments').on('change', function(e){
735 750 var show = 'none';
736 751 var target = e.currentTarget;
737 752 if(target.checked){
738 753 show = ''
739 754 }
740 755 var boxid = $(target).attr('id_for');
741 756 var comments = $('#{0} .inline-comments'.format(boxid));
742 757 var fn_display = function(idx){
743 758 $(this).css('display', show);
744 759 };
745 760 $(comments).each(fn_display);
746 761 var btns = $('#{0} .inline-comments-button'.format(boxid));
747 762 $(btns).each(fn_display);
748 763 });
749 764
750 765 $('#merge_pull_request_form').submit(function() {
751 766 if (!$('#merge_pull_request').attr('disabled')) {
752 767 $('#merge_pull_request').attr('disabled', 'disabled');
753 768 }
754 769 return true;
755 770 });
756 771
757 772 $('#edit_pull_request').on('click', function(e){
758 773 var title = $('#pr-title-input').val();
759 774 var description = codeMirrorInstance.getValue();
760 775 var renderer = $('#pr-renderer-input').val();
761 776 editPullRequest(
762 777 "${c.repo_name}", "${c.pull_request.pull_request_id}",
763 778 title, description, renderer);
764 779 });
765 780
766 781 $('#update_pull_request').on('click', function(e){
767 782 $(this).attr('disabled', 'disabled');
768 783 $(this).addClass('disabled');
769 784 $(this).html(_gettext('Saving...'));
770 785 reviewersController.updateReviewers(
771 786 "${c.repo_name}", "${c.pull_request.pull_request_id}");
772 787 });
773 788
774 789
775 790 // fixing issue with caches on firefox
776 791 $('#update_commits').removeAttr("disabled");
777 792
778 793 $('.show-inline-comments').on('click', function(e){
779 794 var boxid = $(this).attr('data-comment-id');
780 795 var button = $(this);
781 796
782 797 if(button.hasClass("comments-visible")) {
783 798 $('#{0} .inline-comments'.format(boxid)).each(function(index){
784 799 $(this).hide();
785 800 });
786 801 button.removeClass("comments-visible");
787 802 } else {
788 803 $('#{0} .inline-comments'.format(boxid)).each(function(index){
789 804 $(this).show();
790 805 });
791 806 button.addClass("comments-visible");
792 807 }
793 808 });
794 809
795 810 // register submit callback on commentForm form to track TODOs
796 811 window.commentFormGlobalSubmitSuccessCallback = function(){
797 812 refreshMergeChecks();
798 813 };
799 814
800 815 ReviewerAutoComplete('#user');
801 816
802 817 })
803 818
804 819 </script>
805
806 </div>
807 820 </div>
808 821
809 822 </%def>
@@ -1,143 +1,140 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('{} Pull Requests').format(c.repo_name)}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()"></%def>
11 11
12 12 <%def name="menu_bar_nav()">
13 13 ${self.menu_items(active='repositories')}
14 14 </%def>
15 15
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.repo_menu(active='showpullrequest')}
19 19 </%def>
20 20
21 21
22 22 <%def name="main()">
23 23
24 24 <div class="box">
25 25 <div class="title">
26 26 <ul class="button-links">
27 27 <li class="btn ${h.is_active('open', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0})}">${_('Opened')}</a></li>
28 28 <li class="btn ${h.is_active('my', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'my':1})}">${_('Opened by me')}</a></li>
29 29 <li class="btn ${h.is_active('awaiting', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_review':1})}">${_('Awaiting review')}</a></li>
30 30 <li class="btn ${h.is_active('awaiting_my', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'awaiting_my_review':1})}">${_('Awaiting my review')}</a></li>
31 31 <li class="btn ${h.is_active('closed', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':0,'closed':1})}">${_('Closed')}</a></li>
32 32 <li class="btn ${h.is_active('source', c.active)}"><a href="${h.route_path('pullrequest_show_all',repo_name=c.repo_name, _query={'source':1})}">${_('From this repo')}</a></li>
33 33 </ul>
34 34
35 35 <ul class="links">
36 36 % if c.rhodecode_user.username != h.DEFAULT_USER:
37 37 <li>
38 38 <span>
39 39 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">
40 40 ${_('Open new Pull Request')}
41 41 </a>
42 42 </span>
43 43 </li>
44 44 % endif
45 45
46 46 <li>
47 47 <div class="grid-quick-filter">
48 48 <ul class="grid-filter-box">
49 49 <li class="grid-filter-box-icon">
50 50 <i class="icon-search"></i>
51 51 </li>
52 52 <li class="grid-filter-box-input">
53 53 <input class="q_filter_box" id="q_filter" size="15" type="text" name="filter" placeholder="${_('quick filter...')}" value=""/>
54 54 </li>
55 55 </ul>
56 56 </div>
57 57 </li>
58 58
59 59 </ul>
60 60
61 61 </div>
62 62
63 63 <div class="main-content-full-width">
64 64 <table id="pull_request_list_table" class="display"></table>
65 65 </div>
66 66
67 67 </div>
68 68
69 69 <script type="text/javascript">
70 70 $(document).ready(function() {
71 71 var $pullRequestListTable = $('#pull_request_list_table');
72 72
73 73 // object list
74 74 $pullRequestListTable.DataTable({
75 75 processing: true,
76 76 serverSide: true,
77 77 ajax: {
78 78 "url": "${h.route_path('pullrequest_show_all_data', repo_name=c.repo_name)}",
79 79 "data": function (d) {
80 80 d.source = "${c.source}";
81 81 d.closed = "${c.closed}";
82 82 d.my = "${c.my}";
83 83 d.awaiting_review = "${c.awaiting_review}";
84 84 d.awaiting_my_review = "${c.awaiting_my_review}";
85 85 }
86 86 },
87 87 dom: 'rtp',
88 88 pageLength: ${c.visual.dashboard_items},
89 89 order: [[ 1, "desc" ]],
90 90 columns: [
91 91 { data: {"_": "status",
92 92 "sort": "status"}, title: "", className: "td-status", orderable: false},
93 93 { data: {"_": "name",
94 94 "sort": "name_raw"}, title: "${_('Id')}", className: "td-componentname", "type": "num" },
95 95 { data: {"_": "title",
96 96 "sort": "title"}, title: "${_('Title')}", className: "td-description" },
97 97 { data: {"_": "author",
98 98 "sort": "author_raw"}, title: "${_('Author')}", className: "td-user", orderable: false },
99 99 { data: {"_": "comments",
100 100 "sort": "comments_raw"}, title: "", className: "td-comments", orderable: false},
101 101 { data: {"_": "updated_on",
102 102 "sort": "updated_on_raw"}, title: "${_('Last Update')}", className: "td-time" }
103 103 ],
104 104 language: {
105 105 paginate: DEFAULT_GRID_PAGINATION,
106 106 sProcessing: _gettext('loading...'),
107 107 emptyTable: _gettext("No pull requests available yet.")
108 108 },
109 109 "drawCallback": function( settings, json ) {
110 110 timeagoActivate();
111 111 tooltipActivate();
112 112 },
113 113 "createdRow": function ( row, data, index ) {
114 114 if (data['closed']) {
115 115 $(row).addClass('closed');
116 116 }
117 if (data['state'] !== 'created') {
118 $(row).addClass('state-' + data['state']);
119 }
120 117 }
121 118 });
122 119
123 120 $pullRequestListTable.on('xhr.dt', function(e, settings, json, xhr){
124 121 $pullRequestListTable.css('opacity', 1);
125 122 });
126 123
127 124 $pullRequestListTable.on('preXhr.dt', function(e, settings, data){
128 125 $pullRequestListTable.css('opacity', 0.3);
129 126 });
130 127
131 128 // filter
132 129 $('#q_filter').on('keyup',
133 130 $.debounce(250, function() {
134 131 $pullRequestListTable.DataTable().search(
135 132 $('#q_filter').val()
136 133 ).draw();
137 134 })
138 135 );
139 136
140 137 });
141 138
142 139 </script>
143 140 </%def>
General Comments 0
You need to be logged in to leave comments. Login now