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