##// END OF EJS Templates
comments: enable urlify functions on comments in repo/pr scopes....
dan -
r4042:34d7ca19 default
parent child Browse files
Show More
@@ -1,600 +1,601 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.httpexceptions import HTTPNotFound, HTTPBadRequest, HTTPFound
26 26 from pyramid.view import view_config
27 27 from pyramid.renderers import render
28 28 from pyramid.response import Response
29 29
30 30 from rhodecode.apps._base import RepoAppView
31 31 from rhodecode.apps.file_store import utils as store_utils
32 32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33 33
34 34 from rhodecode.lib import diffs, codeblocks
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37 37
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.lib.diffs import (
40 40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 41 get_diff_whitespace_flag)
42 42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
43 43 import rhodecode.lib.helpers as h
44 44 from rhodecode.lib.utils2 import safe_unicode, str2bool
45 45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 46 from rhodecode.lib.vcs.exceptions import (
47 47 RepositoryError, CommitDoesNotExistError)
48 48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore
49 49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 50 from rhodecode.model.comment import CommentsModel
51 51 from rhodecode.model.meta import Session
52 52 from rhodecode.model.settings import VcsSettingsModel
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 def _update_with_GET(params, request):
58 58 for k in ['diff1', 'diff2', 'diff']:
59 59 params[k] += request.GET.getall(k)
60 60
61 61
62 62 class RepoCommitsView(RepoAppView):
63 63 def load_default_context(self):
64 64 c = self._get_local_tmpl_context(include_app_defaults=True)
65 65 c.rhodecode_repo = self.rhodecode_vcs_repo
66 66
67 67 return c
68 68
69 69 def _is_diff_cache_enabled(self, target_repo):
70 70 caching_enabled = self._get_general_setting(
71 71 target_repo, 'rhodecode_diff_cache')
72 72 log.debug('Diff caching enabled: %s', caching_enabled)
73 73 return caching_enabled
74 74
75 75 def _commit(self, commit_id_range, method):
76 76 _ = self.request.translate
77 77 c = self.load_default_context()
78 78 c.fulldiff = self.request.GET.get('fulldiff')
79 79
80 80 # fetch global flags of ignore ws or context lines
81 81 diff_context = get_diff_context(self.request)
82 82 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83 83
84 84 # diff_limit will cut off the whole diff if the limit is applied
85 85 # otherwise it will just hide the big files from the front-end
86 86 diff_limit = c.visual.cut_off_limit_diff
87 87 file_limit = c.visual.cut_off_limit_file
88 88
89 89 # get ranges of commit ids if preset
90 90 commit_range = commit_id_range.split('...')[:2]
91 91
92 92 try:
93 93 pre_load = ['affected_files', 'author', 'branch', 'date',
94 94 'message', 'parents']
95 95 if self.rhodecode_vcs_repo.alias == 'hg':
96 96 pre_load += ['hidden', 'obsolete', 'phase']
97 97
98 98 if len(commit_range) == 2:
99 99 commits = self.rhodecode_vcs_repo.get_commits(
100 100 start_id=commit_range[0], end_id=commit_range[1],
101 101 pre_load=pre_load, translate_tags=False)
102 102 commits = list(commits)
103 103 else:
104 104 commits = [self.rhodecode_vcs_repo.get_commit(
105 105 commit_id=commit_id_range, pre_load=pre_load)]
106 106
107 107 c.commit_ranges = commits
108 108 if not c.commit_ranges:
109 109 raise RepositoryError('The commit range returned an empty result')
110 110 except CommitDoesNotExistError as e:
111 111 msg = _('No such commit exists. Org exception: `{}`').format(e)
112 112 h.flash(msg, category='error')
113 113 raise HTTPNotFound()
114 114 except Exception:
115 115 log.exception("General failure")
116 116 raise HTTPNotFound()
117 117
118 118 c.changes = OrderedDict()
119 119 c.lines_added = 0
120 120 c.lines_deleted = 0
121 121
122 122 # auto collapse if we have more than limit
123 123 collapse_limit = diffs.DiffProcessor._collapse_commits_over
124 124 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
125 125
126 126 c.commit_statuses = ChangesetStatus.STATUSES
127 127 c.inline_comments = []
128 128 c.files = []
129 129
130 130 c.statuses = []
131 131 c.comments = []
132 132 c.unresolved_comments = []
133 133 c.resolved_comments = []
134 134 if len(c.commit_ranges) == 1:
135 135 commit = c.commit_ranges[0]
136 136 c.comments = CommentsModel().get_comments(
137 137 self.db_repo.repo_id,
138 138 revision=commit.raw_id)
139 139 c.statuses.append(ChangesetStatusModel().get_status(
140 140 self.db_repo.repo_id, commit.raw_id))
141 141 # comments from PR
142 142 statuses = ChangesetStatusModel().get_statuses(
143 143 self.db_repo.repo_id, commit.raw_id,
144 144 with_revisions=True)
145 145 prs = set(st.pull_request for st in statuses
146 146 if st.pull_request is not None)
147 147 # from associated statuses, check the pull requests, and
148 148 # show comments from them
149 149 for pr in prs:
150 150 c.comments.extend(pr.comments)
151 151
152 152 c.unresolved_comments = CommentsModel()\
153 153 .get_commit_unresolved_todos(commit.raw_id)
154 154 c.resolved_comments = CommentsModel()\
155 155 .get_commit_resolved_todos(commit.raw_id)
156 156
157 157 diff = None
158 158 # Iterate over ranges (default commit view is always one commit)
159 159 for commit in c.commit_ranges:
160 160 c.changes[commit.raw_id] = []
161 161
162 162 commit2 = commit
163 163 commit1 = commit.first_parent
164 164
165 165 if method == 'show':
166 166 inline_comments = CommentsModel().get_inline_comments(
167 167 self.db_repo.repo_id, revision=commit.raw_id)
168 168 c.inline_cnt = CommentsModel().get_inline_comments_count(
169 169 inline_comments)
170 170 c.inline_comments = inline_comments
171 171
172 172 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
173 173 self.db_repo)
174 174 cache_file_path = diff_cache_exist(
175 175 cache_path, 'diff', commit.raw_id,
176 176 hide_whitespace_changes, diff_context, c.fulldiff)
177 177
178 178 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
179 179 force_recache = str2bool(self.request.GET.get('force_recache'))
180 180
181 181 cached_diff = None
182 182 if caching_enabled:
183 183 cached_diff = load_cached_diff(cache_file_path)
184 184
185 185 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
186 186 if not force_recache and has_proper_diff_cache:
187 187 diffset = cached_diff['diff']
188 188 else:
189 189 vcs_diff = self.rhodecode_vcs_repo.get_diff(
190 190 commit1, commit2,
191 191 ignore_whitespace=hide_whitespace_changes,
192 192 context=diff_context)
193 193
194 194 diff_processor = diffs.DiffProcessor(
195 195 vcs_diff, format='newdiff', diff_limit=diff_limit,
196 196 file_limit=file_limit, show_full_diff=c.fulldiff)
197 197
198 198 _parsed = diff_processor.prepare()
199 199
200 200 diffset = codeblocks.DiffSet(
201 201 repo_name=self.db_repo_name,
202 202 source_node_getter=codeblocks.diffset_node_getter(commit1),
203 203 target_node_getter=codeblocks.diffset_node_getter(commit2))
204 204
205 205 diffset = self.path_filter.render_patchset_filtered(
206 206 diffset, _parsed, commit1.raw_id, commit2.raw_id)
207 207
208 208 # save cached diff
209 209 if caching_enabled:
210 210 cache_diff(cache_file_path, diffset, None)
211 211
212 212 c.limited_diff = diffset.limited_diff
213 213 c.changes[commit.raw_id] = diffset
214 214 else:
215 215 # TODO(marcink): no cache usage here...
216 216 _diff = self.rhodecode_vcs_repo.get_diff(
217 217 commit1, commit2,
218 218 ignore_whitespace=hide_whitespace_changes, context=diff_context)
219 219 diff_processor = diffs.DiffProcessor(
220 220 _diff, format='newdiff', diff_limit=diff_limit,
221 221 file_limit=file_limit, show_full_diff=c.fulldiff)
222 222 # downloads/raw we only need RAW diff nothing else
223 223 diff = self.path_filter.get_raw_patch(diff_processor)
224 224 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
225 225
226 226 # sort comments by how they were generated
227 227 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
228 228
229 229 if len(c.commit_ranges) == 1:
230 230 c.commit = c.commit_ranges[0]
231 231 c.parent_tmpl = ''.join(
232 232 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
233 233
234 234 if method == 'download':
235 235 response = Response(diff)
236 236 response.content_type = 'text/plain'
237 237 response.content_disposition = (
238 238 'attachment; filename=%s.diff' % commit_id_range[:12])
239 239 return response
240 240 elif method == 'patch':
241 241 c.diff = safe_unicode(diff)
242 242 patch = render(
243 243 'rhodecode:templates/changeset/patch_changeset.mako',
244 244 self._get_template_context(c), self.request)
245 245 response = Response(patch)
246 246 response.content_type = 'text/plain'
247 247 return response
248 248 elif method == 'raw':
249 249 response = Response(diff)
250 250 response.content_type = 'text/plain'
251 251 return response
252 252 elif method == 'show':
253 253 if len(c.commit_ranges) == 1:
254 254 html = render(
255 255 'rhodecode:templates/changeset/changeset.mako',
256 256 self._get_template_context(c), self.request)
257 257 return Response(html)
258 258 else:
259 259 c.ancestor = None
260 260 c.target_repo = self.db_repo
261 261 html = render(
262 262 'rhodecode:templates/changeset/changeset_range.mako',
263 263 self._get_template_context(c), self.request)
264 264 return Response(html)
265 265
266 266 raise HTTPBadRequest()
267 267
268 268 @LoginRequired()
269 269 @HasRepoPermissionAnyDecorator(
270 270 'repository.read', 'repository.write', 'repository.admin')
271 271 @view_config(
272 272 route_name='repo_commit', request_method='GET',
273 273 renderer=None)
274 274 def repo_commit_show(self):
275 275 commit_id = self.request.matchdict['commit_id']
276 276 return self._commit(commit_id, method='show')
277 277
278 278 @LoginRequired()
279 279 @HasRepoPermissionAnyDecorator(
280 280 'repository.read', 'repository.write', 'repository.admin')
281 281 @view_config(
282 282 route_name='repo_commit_raw', request_method='GET',
283 283 renderer=None)
284 284 @view_config(
285 285 route_name='repo_commit_raw_deprecated', request_method='GET',
286 286 renderer=None)
287 287 def repo_commit_raw(self):
288 288 commit_id = self.request.matchdict['commit_id']
289 289 return self._commit(commit_id, method='raw')
290 290
291 291 @LoginRequired()
292 292 @HasRepoPermissionAnyDecorator(
293 293 'repository.read', 'repository.write', 'repository.admin')
294 294 @view_config(
295 295 route_name='repo_commit_patch', request_method='GET',
296 296 renderer=None)
297 297 def repo_commit_patch(self):
298 298 commit_id = self.request.matchdict['commit_id']
299 299 return self._commit(commit_id, method='patch')
300 300
301 301 @LoginRequired()
302 302 @HasRepoPermissionAnyDecorator(
303 303 'repository.read', 'repository.write', 'repository.admin')
304 304 @view_config(
305 305 route_name='repo_commit_download', request_method='GET',
306 306 renderer=None)
307 307 def repo_commit_download(self):
308 308 commit_id = self.request.matchdict['commit_id']
309 309 return self._commit(commit_id, method='download')
310 310
311 311 @LoginRequired()
312 312 @NotAnonymous()
313 313 @HasRepoPermissionAnyDecorator(
314 314 'repository.read', 'repository.write', 'repository.admin')
315 315 @CSRFRequired()
316 316 @view_config(
317 317 route_name='repo_commit_comment_create', request_method='POST',
318 318 renderer='json_ext')
319 319 def repo_commit_comment_create(self):
320 320 _ = self.request.translate
321 321 commit_id = self.request.matchdict['commit_id']
322 322
323 323 c = self.load_default_context()
324 324 status = self.request.POST.get('changeset_status', None)
325 325 text = self.request.POST.get('text')
326 326 comment_type = self.request.POST.get('comment_type')
327 327 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
328 328
329 329 if status:
330 330 text = text or (_('Status change %(transition_icon)s %(status)s')
331 331 % {'transition_icon': '>',
332 332 'status': ChangesetStatus.get_status_lbl(status)})
333 333
334 334 multi_commit_ids = []
335 335 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
336 336 if _commit_id not in ['', None, EmptyCommit.raw_id]:
337 337 if _commit_id not in multi_commit_ids:
338 338 multi_commit_ids.append(_commit_id)
339 339
340 340 commit_ids = multi_commit_ids or [commit_id]
341 341
342 342 comment = None
343 343 for current_id in filter(None, commit_ids):
344 344 comment = CommentsModel().create(
345 345 text=text,
346 346 repo=self.db_repo.repo_id,
347 347 user=self._rhodecode_db_user.user_id,
348 348 commit_id=current_id,
349 349 f_path=self.request.POST.get('f_path'),
350 350 line_no=self.request.POST.get('line'),
351 351 status_change=(ChangesetStatus.get_status_lbl(status)
352 352 if status else None),
353 353 status_change_type=status,
354 354 comment_type=comment_type,
355 355 resolves_comment_id=resolves_comment_id,
356 356 auth_user=self._rhodecode_user
357 357 )
358 358
359 359 # get status if set !
360 360 if status:
361 361 # if latest status was from pull request and it's closed
362 362 # disallow changing status !
363 363 # dont_allow_on_closed_pull_request = True !
364 364
365 365 try:
366 366 ChangesetStatusModel().set_status(
367 367 self.db_repo.repo_id,
368 368 status,
369 369 self._rhodecode_db_user.user_id,
370 370 comment,
371 371 revision=current_id,
372 372 dont_allow_on_closed_pull_request=True
373 373 )
374 374 except StatusChangeOnClosedPullRequestError:
375 375 msg = _('Changing the status of a commit associated with '
376 376 'a closed pull request is not allowed')
377 377 log.exception(msg)
378 378 h.flash(msg, category='warning')
379 379 raise HTTPFound(h.route_path(
380 380 'repo_commit', repo_name=self.db_repo_name,
381 381 commit_id=current_id))
382 382
383 383 # finalize, commit and redirect
384 384 Session().commit()
385 385
386 386 data = {
387 387 'target_id': h.safeid(h.safe_unicode(
388 388 self.request.POST.get('f_path'))),
389 389 }
390 390 if comment:
391 391 c.co = comment
392 392 rendered_comment = render(
393 393 'rhodecode:templates/changeset/changeset_comment_block.mako',
394 394 self._get_template_context(c), self.request)
395 395
396 396 data.update(comment.get_dict())
397 397 data.update({'rendered_text': rendered_comment})
398 398
399 399 return data
400 400
401 401 @LoginRequired()
402 402 @NotAnonymous()
403 403 @HasRepoPermissionAnyDecorator(
404 404 'repository.read', 'repository.write', 'repository.admin')
405 405 @CSRFRequired()
406 406 @view_config(
407 407 route_name='repo_commit_comment_preview', request_method='POST',
408 408 renderer='string', xhr=True)
409 409 def repo_commit_comment_preview(self):
410 410 # Technically a CSRF token is not needed as no state changes with this
411 411 # call. However, as this is a POST is better to have it, so automated
412 412 # tools don't flag it as potential CSRF.
413 413 # Post is required because the payload could be bigger than the maximum
414 414 # allowed by GET.
415 415
416 416 text = self.request.POST.get('text')
417 417 renderer = self.request.POST.get('renderer') or 'rst'
418 418 if text:
419 return h.render(text, renderer=renderer, mentions=True)
419 return h.render(text, renderer=renderer, mentions=True,
420 repo_name=self.db_repo_name)
420 421 return ''
421 422
422 423 @LoginRequired()
423 424 @NotAnonymous()
424 425 @HasRepoPermissionAnyDecorator(
425 426 'repository.read', 'repository.write', 'repository.admin')
426 427 @CSRFRequired()
427 428 @view_config(
428 429 route_name='repo_commit_comment_attachment_upload', request_method='POST',
429 430 renderer='json_ext', xhr=True)
430 431 def repo_commit_comment_attachment_upload(self):
431 432 c = self.load_default_context()
432 433 upload_key = 'attachment'
433 434
434 435 file_obj = self.request.POST.get(upload_key)
435 436
436 437 if file_obj is None:
437 438 self.request.response.status = 400
438 439 return {'store_fid': None,
439 440 'access_path': None,
440 441 'error': '{} data field is missing'.format(upload_key)}
441 442
442 443 if not hasattr(file_obj, 'filename'):
443 444 self.request.response.status = 400
444 445 return {'store_fid': None,
445 446 'access_path': None,
446 447 'error': 'filename cannot be read from the data field'}
447 448
448 449 filename = file_obj.filename
449 450 file_display_name = filename
450 451
451 452 metadata = {
452 453 'user_uploaded': {'username': self._rhodecode_user.username,
453 454 'user_id': self._rhodecode_user.user_id,
454 455 'ip': self._rhodecode_user.ip_addr}}
455 456
456 457 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
457 458 allowed_extensions = [
458 459 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
459 460 '.pptx', '.txt', '.xlsx', '.zip']
460 461 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
461 462
462 463 try:
463 464 storage = store_utils.get_file_storage(self.request.registry.settings)
464 465 store_uid, metadata = storage.save_file(
465 466 file_obj.file, filename, extra_metadata=metadata,
466 467 extensions=allowed_extensions, max_filesize=max_file_size)
467 468 except FileNotAllowedException:
468 469 self.request.response.status = 400
469 470 permitted_extensions = ', '.join(allowed_extensions)
470 471 error_msg = 'File `{}` is not allowed. ' \
471 472 'Only following extensions are permitted: {}'.format(
472 473 filename, permitted_extensions)
473 474 return {'store_fid': None,
474 475 'access_path': None,
475 476 'error': error_msg}
476 477 except FileOverSizeException:
477 478 self.request.response.status = 400
478 479 limit_mb = h.format_byte_size_binary(max_file_size)
479 480 return {'store_fid': None,
480 481 'access_path': None,
481 482 'error': 'File {} is exceeding allowed limit of {}.'.format(
482 483 filename, limit_mb)}
483 484
484 485 try:
485 486 entry = FileStore.create(
486 487 file_uid=store_uid, filename=metadata["filename"],
487 488 file_hash=metadata["sha256"], file_size=metadata["size"],
488 489 file_display_name=file_display_name,
489 490 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
490 491 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
491 492 scope_repo_id=self.db_repo.repo_id
492 493 )
493 494 Session().add(entry)
494 495 Session().commit()
495 496 log.debug('Stored upload in DB as %s', entry)
496 497 except Exception:
497 498 log.exception('Failed to store file %s', filename)
498 499 self.request.response.status = 400
499 500 return {'store_fid': None,
500 501 'access_path': None,
501 502 'error': 'File {} failed to store in DB.'.format(filename)}
502 503
503 504 Session().commit()
504 505
505 506 return {
506 507 'store_fid': store_uid,
507 508 'access_path': h.route_path(
508 509 'download_file', fid=store_uid),
509 510 'fqn_access_path': h.route_url(
510 511 'download_file', fid=store_uid),
511 512 'repo_access_path': h.route_path(
512 513 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
513 514 'repo_fqn_access_path': h.route_url(
514 515 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
515 516 }
516 517
517 518 @LoginRequired()
518 519 @NotAnonymous()
519 520 @HasRepoPermissionAnyDecorator(
520 521 'repository.read', 'repository.write', 'repository.admin')
521 522 @CSRFRequired()
522 523 @view_config(
523 524 route_name='repo_commit_comment_delete', request_method='POST',
524 525 renderer='json_ext')
525 526 def repo_commit_comment_delete(self):
526 527 commit_id = self.request.matchdict['commit_id']
527 528 comment_id = self.request.matchdict['comment_id']
528 529
529 530 comment = ChangesetComment.get_or_404(comment_id)
530 531 if not comment:
531 532 log.debug('Comment with id:%s not found, skipping', comment_id)
532 533 # comment already deleted in another call probably
533 534 return True
534 535
535 536 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
536 537 super_admin = h.HasPermissionAny('hg.admin')()
537 538 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
538 539 is_repo_comment = comment.repo.repo_name == self.db_repo_name
539 540 comment_repo_admin = is_repo_admin and is_repo_comment
540 541
541 542 if super_admin or comment_owner or comment_repo_admin:
542 543 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
543 544 Session().commit()
544 545 return True
545 546 else:
546 547 log.warning('No permissions for user %s to delete comment_id: %s',
547 548 self._rhodecode_db_user, comment_id)
548 549 raise HTTPNotFound()
549 550
550 551 @LoginRequired()
551 552 @HasRepoPermissionAnyDecorator(
552 553 'repository.read', 'repository.write', 'repository.admin')
553 554 @view_config(
554 555 route_name='repo_commit_data', request_method='GET',
555 556 renderer='json_ext', xhr=True)
556 557 def repo_commit_data(self):
557 558 commit_id = self.request.matchdict['commit_id']
558 559 self.load_default_context()
559 560
560 561 try:
561 562 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
562 563 except CommitDoesNotExistError as e:
563 564 return EmptyCommit(message=str(e))
564 565
565 566 @LoginRequired()
566 567 @HasRepoPermissionAnyDecorator(
567 568 'repository.read', 'repository.write', 'repository.admin')
568 569 @view_config(
569 570 route_name='repo_commit_children', request_method='GET',
570 571 renderer='json_ext', xhr=True)
571 572 def repo_commit_children(self):
572 573 commit_id = self.request.matchdict['commit_id']
573 574 self.load_default_context()
574 575
575 576 try:
576 577 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
577 578 children = commit.children
578 579 except CommitDoesNotExistError:
579 580 children = []
580 581
581 582 result = {"results": children}
582 583 return result
583 584
584 585 @LoginRequired()
585 586 @HasRepoPermissionAnyDecorator(
586 587 'repository.read', 'repository.write', 'repository.admin')
587 588 @view_config(
588 589 route_name='repo_commit_parents', request_method='GET',
589 590 renderer='json_ext')
590 591 def repo_commit_parents(self):
591 592 commit_id = self.request.matchdict['commit_id']
592 593 self.load_default_context()
593 594
594 595 try:
595 596 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
596 597 parents = commit.parents
597 598 except CommitDoesNotExistError:
598 599 parents = []
599 600 result = {"results": parents}
600 601 return result
@@ -1,420 +1,419 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ## usage:
3 3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 4 ## ${comment.comment_block(comment)}
5 5 ##
6 6 <%namespace name="base" file="/base/base.mako"/>
7 7
8 8 <%def name="comment_block(comment, inline=False)">
9 9 <% pr_index_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
10 10 <% latest_ver = len(getattr(c, 'versions', [])) %>
11 11 % if inline:
12 12 <% outdated_at_ver = comment.outdated_at_version(getattr(c, 'at_version_num', None)) %>
13 13 % else:
14 14 <% outdated_at_ver = comment.older_than_version(getattr(c, 'at_version_num', None)) %>
15 15 % endif
16 16
17
18 17 <div class="comment
19 18 ${'comment-inline' if inline else 'comment-general'}
20 19 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
21 20 id="comment-${comment.comment_id}"
22 21 line="${comment.line_no}"
23 22 data-comment-id="${comment.comment_id}"
24 23 data-comment-type="${comment.comment_type}"
25 24 data-comment-line-no="${comment.line_no}"
26 25 data-comment-inline=${h.json.dumps(inline)}
27 26 style="${'display: none;' if outdated_at_ver else ''}">
28 27
29 28 <div class="meta">
30 29 <div class="comment-type-label">
31 30 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}" title="line: ${comment.line_no}">
32 31 % if comment.comment_type == 'todo':
33 32 % if comment.resolved:
34 33 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
35 34 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
36 35 </div>
37 36 % else:
38 37 <div class="resolved tooltip" style="display: none">
39 38 <span>${comment.comment_type}</span>
40 39 </div>
41 40 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to resolve this comment')}">
42 41 ${comment.comment_type}
43 42 </div>
44 43 % endif
45 44 % else:
46 45 % if comment.resolved_comment:
47 46 fix
48 47 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
49 48 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
50 49 </a>
51 50 % else:
52 51 ${comment.comment_type or 'note'}
53 52 % endif
54 53 % endif
55 54 </div>
56 55 </div>
57 56
58 57 <div class="author ${'author-inline' if inline else 'author-general'}">
59 58 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
60 59 </div>
61 60 <div class="date">
62 61 ${h.age_component(comment.modified_at, time_is_local=True)}
63 62 </div>
64 63 % if inline:
65 64 <span></span>
66 65 % else:
67 66 <div class="status-change">
68 67 % if comment.pull_request:
69 68 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
70 69 % if comment.status_change:
71 70 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}:
72 71 % else:
73 72 ${_('pull request !{}').format(comment.pull_request.pull_request_id)}
74 73 % endif
75 74 </a>
76 75 % else:
77 76 % if comment.status_change:
78 77 ${_('Status change on commit')}:
79 78 % endif
80 79 % endif
81 80 </div>
82 81 % endif
83 82
84 83 % if comment.status_change:
85 84 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
86 85 <div title="${_('Commit status')}" class="changeset-status-lbl">
87 86 ${comment.status_change[0].status_lbl}
88 87 </div>
89 88 % endif
90 89
91 90 <a class="permalink" href="#comment-${comment.comment_id}"> &para;</a>
92 91
93 92 <div class="comment-links-block">
94 93 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
95 94 <span class="tag authortag tooltip" title="${_('Pull request author')}">
96 95 ${_('author')}
97 96 </span>
98 97 |
99 98 % endif
100 99 % if inline:
101 100 <div class="pr-version-inline">
102 101 <a href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
103 102 % if outdated_at_ver:
104 103 <code class="pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
105 104 outdated ${'v{}'.format(pr_index_ver)} |
106 105 </code>
107 106 % elif pr_index_ver:
108 107 <code class="pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
109 108 ${'v{}'.format(pr_index_ver)} |
110 109 </code>
111 110 % endif
112 111 </a>
113 112 </div>
114 113 % else:
115 114 % if comment.pull_request_version_id and pr_index_ver:
116 115 |
117 116 <div class="pr-version">
118 117 % if comment.outdated:
119 118 <a href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}">
120 119 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}
121 120 </a>
122 121 % else:
123 122 <div title="${_('Comment from pull request version v{0}, latest v{1}').format(pr_index_ver, latest_ver)}">
124 123 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}">
125 124 <code class="pr-version-num">
126 125 ${'v{}'.format(pr_index_ver)}
127 126 </code>
128 127 </a>
129 128 </div>
130 129 % endif
131 130 </div>
132 131 % endif
133 132 % endif
134 133
135 134 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
136 135 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
137 136 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
138 137 ## permissions to delete
139 138 %if c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id:
140 139 ## TODO: dan: add edit comment here
141 140 <a onclick="return Rhodecode.comments.deleteComment(this);" class="delete-comment"> ${_('Delete')}</a>
142 141 %else:
143 142 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
144 143 %endif
145 144 %else:
146 145 <button class="btn-link" disabled="disabled"> ${_('Delete')}</button>
147 146 %endif
148 147
149 148 % if outdated_at_ver:
150 149 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="prev-comment"> ${_('Prev')}</a>
151 150 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="next-comment"> ${_('Next')}</a>
152 151 % else:
153 152 | <a onclick="return Rhodecode.comments.prevComment(this);" class="prev-comment"> ${_('Prev')}</a>
154 153 | <a onclick="return Rhodecode.comments.nextComment(this);" class="next-comment"> ${_('Next')}</a>
155 154 % endif
156 155
157 156 </div>
158 157 </div>
159 158 <div class="text">
160 ${h.render(comment.text, renderer=comment.renderer, mentions=True)}
159 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None))}
161 160 </div>
162 161
163 162 </div>
164 163 </%def>
165 164
166 165 ## generate main comments
167 166 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
168 167 <div class="general-comments" id="comments">
169 168 %for comment in comments:
170 169 <div id="comment-tr-${comment.comment_id}">
171 170 ## only render comments that are not from pull request, or from
172 171 ## pull request and a status change
173 172 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
174 173 ${comment_block(comment)}
175 174 %endif
176 175 </div>
177 176 %endfor
178 177 ## to anchor ajax comments
179 178 <div id="injected_page_comments"></div>
180 179 </div>
181 180 </%def>
182 181
183 182
184 183 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
185 184
186 185 <div class="comments">
187 186 <%
188 187 if is_pull_request:
189 188 placeholder = _('Leave a comment on this Pull Request.')
190 189 elif is_compare:
191 190 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
192 191 else:
193 192 placeholder = _('Leave a comment on this Commit.')
194 193 %>
195 194
196 195 % if c.rhodecode_user.username != h.DEFAULT_USER:
197 196 <div class="js-template" id="cb-comment-general-form-template">
198 197 ## template generated for injection
199 198 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
200 199 </div>
201 200
202 201 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
203 202 ## inject form here
204 203 </div>
205 204 <script type="text/javascript">
206 205 var lineNo = 'general';
207 206 var resolvesCommentId = null;
208 207 var generalCommentForm = Rhodecode.comments.createGeneralComment(
209 208 lineNo, "${placeholder}", resolvesCommentId);
210 209
211 210 // set custom success callback on rangeCommit
212 211 % if is_compare:
213 212 generalCommentForm.setHandleFormSubmit(function(o) {
214 213 var self = generalCommentForm;
215 214
216 215 var text = self.cm.getValue();
217 216 var status = self.getCommentStatus();
218 217 var commentType = self.getCommentType();
219 218
220 219 if (text === "" && !status) {
221 220 return;
222 221 }
223 222
224 223 // we can pick which commits we want to make the comment by
225 224 // selecting them via click on preview pane, this will alter the hidden inputs
226 225 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
227 226
228 227 var commitIds = [];
229 228 $('#changeset_compare_view_content .compare_select').each(function(el) {
230 229 var commitId = this.id.replace('row-', '');
231 230 if ($(this).hasClass('hl') || !cherryPicked) {
232 231 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
233 232 commitIds.push(commitId);
234 233 } else {
235 234 $("input[data-commit-id='{0}']".format(commitId)).val('')
236 235 }
237 236 });
238 237
239 238 self.setActionButtonsDisabled(true);
240 239 self.cm.setOption("readOnly", true);
241 240 var postData = {
242 241 'text': text,
243 242 'changeset_status': status,
244 243 'comment_type': commentType,
245 244 'commit_ids': commitIds,
246 245 'csrf_token': CSRF_TOKEN
247 246 };
248 247
249 248 var submitSuccessCallback = function(o) {
250 249 location.reload(true);
251 250 };
252 251 var submitFailCallback = function(){
253 252 self.resetCommentFormState(text)
254 253 };
255 254 self.submitAjaxPOST(
256 255 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
257 256 });
258 257 % endif
259 258
260 259 </script>
261 260 % else:
262 261 ## form state when not logged in
263 262 <div class="comment-form ac">
264 263
265 264 <div class="comment-area">
266 265 <div class="comment-area-header">
267 266 <ul class="nav-links clearfix">
268 267 <li class="active">
269 268 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
270 269 </li>
271 270 <li class="">
272 271 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
273 272 </li>
274 273 </ul>
275 274 </div>
276 275
277 276 <div class="comment-area-write" style="display: block;">
278 277 <div id="edit-container">
279 278 <div style="padding: 40px 0">
280 279 ${_('You need to be logged in to leave comments.')}
281 280 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
282 281 </div>
283 282 </div>
284 283 <div id="preview-container" class="clearfix" style="display: none;">
285 284 <div id="preview-box" class="preview-box"></div>
286 285 </div>
287 286 </div>
288 287
289 288 <div class="comment-area-footer">
290 289 <div class="toolbar">
291 290 <div class="toolbar-text">
292 291 </div>
293 292 </div>
294 293 </div>
295 294 </div>
296 295
297 296 <div class="comment-footer">
298 297 </div>
299 298
300 299 </div>
301 300 % endif
302 301
303 302 <script type="text/javascript">
304 303 bindToggleButtons();
305 304 </script>
306 305 </div>
307 306 </%def>
308 307
309 308
310 309 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
311 310
312 311 ## comment injected based on assumption that user is logged in
313 312 <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET">
314 313
315 314 <div class="comment-area">
316 315 <div class="comment-area-header">
317 316 <ul class="nav-links clearfix">
318 317 <li class="active">
319 318 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
320 319 </li>
321 320 <li class="">
322 321 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
323 322 </li>
324 323 <li class="pull-right">
325 324 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
326 325 % for val in c.visual.comment_types:
327 326 <option value="${val}">${val.upper()}</option>
328 327 % endfor
329 328 </select>
330 329 </li>
331 330 </ul>
332 331 </div>
333 332
334 333 <div class="comment-area-write" style="display: block;">
335 334 <div id="edit-container_${lineno_id}">
336 335 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
337 336 </div>
338 337 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
339 338 <div id="preview-box_${lineno_id}" class="preview-box"></div>
340 339 </div>
341 340 </div>
342 341
343 342 <div class="comment-area-footer comment-attachment-uploader">
344 343 <div class="toolbar">
345 344 <div class="toolbar-text">
346 345 ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % (
347 346 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
348 347 ('<span class="tooltip" title="%s">@mention</span>' % _('Use @username inside this text to send notification to this RhodeCode user')),
349 348 ('<span class="tooltip" title="%s">`/`</span>' % _('Start typing with / for certain actions to be triggered via text box.'))
350 349 )
351 350 )|n}
352 351 </div>
353 352
354 353 <div class="comment-attachment-text">
355 354 <div class="dropzone-text">
356 355 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
357 356 </div>
358 357 <div class="dropzone-upload" style="display:none">
359 358 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
360 359 </div>
361 360 </div>
362 361
363 362 ## comments dropzone template, empty on purpose
364 363 <div style="display: none" class="comment-attachment-uploader-template">
365 364 <div class="dz-file-preview" style="margin: 0">
366 365 <div class="dz-error-message"></div>
367 366 </div>
368 367 </div>
369 368
370 369 </div>
371 370 </div>
372 371 </div>
373 372
374 373 <div class="comment-footer">
375 374
376 375 % if review_statuses:
377 376 <div class="status_box">
378 377 <select id="change_status_${lineno_id}" name="changeset_status">
379 378 <option></option> ## Placeholder
380 379 % for status, lbl in review_statuses:
381 380 <option value="${status}" data-status="${status}">${lbl}</option>
382 381 %if is_pull_request and change_status and status in ('approved', 'rejected'):
383 382 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
384 383 %endif
385 384 % endfor
386 385 </select>
387 386 </div>
388 387 % endif
389 388
390 389 ## inject extra inputs into the form
391 390 % if form_extras and isinstance(form_extras, (list, tuple)):
392 391 <div id="comment_form_extras">
393 392 % for form_ex_el in form_extras:
394 393 ${form_ex_el|n}
395 394 % endfor
396 395 </div>
397 396 % endif
398 397
399 398 <div class="action-buttons">
400 399 ## inline for has a file, and line-number together with cancel hide button.
401 400 % if form_type == 'inline':
402 401 <input type="hidden" name="f_path" value="{0}">
403 402 <input type="hidden" name="line" value="${lineno_id}">
404 403 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
405 404 ${_('Cancel')}
406 405 </button>
407 406 % endif
408 407
409 408 % if form_type != 'inline':
410 409 <div class="action-buttons-extra"></div>
411 410 % endif
412 411
413 412 ${h.submit('save', _('Comment'), class_='btn btn-success comment-button-input')}
414 413
415 414 </div>
416 415 </div>
417 416
418 417 </form>
419 418
420 419 </%def> No newline at end of file
@@ -1,804 +1,804 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4
5 5 <%def name="title()">
6 6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
7 7 %if c.rhodecode_name:
8 8 &middot; ${h.branding(c.rhodecode_name)}
9 9 %endif
10 10 </%def>
11 11
12 12 <%def name="breadcrumbs_links()">
13 13 <span id="pr-title">
14 14 ${c.pull_request.title}
15 15 %if c.pull_request.is_closed():
16 16 (${_('Closed')})
17 17 %endif
18 18 </span>
19 19 <div id="pr-title-edit" class="input" style="display: none;">
20 20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
21 21 </div>
22 22 </%def>
23 23
24 24 <%def name="menu_bar_nav()">
25 25 ${self.menu_items(active='repositories')}
26 26 </%def>
27 27
28 28 <%def name="menu_bar_subnav()">
29 29 ${self.repo_menu(active='showpullrequest')}
30 30 </%def>
31 31
32 32 <%def name="main()">
33 33
34 34 <script type="text/javascript">
35 35 // TODO: marcink switch this to pyroutes
36 36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
37 37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 38 </script>
39 39 <div class="box">
40 40
41 41 ${self.breadcrumbs()}
42 42
43 43 <div class="box pr-summary">
44 44
45 45 <div class="summary-details block-left">
46 46 <% summary = lambda n:{False:'summary-short'}.get(n) %>
47 47 <div class="pr-details-title">
48 48 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request !{}').format(c.pull_request.pull_request_id)}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
49 49 %if c.allowed_to_update:
50 50 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
51 51 % if c.allowed_to_delete:
52 52 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
53 53 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
54 54 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
55 55 ${h.end_form()}
56 56 % else:
57 57 ${_('Delete')}
58 58 % endif
59 59 </div>
60 60 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
61 61 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
62 62 %endif
63 63 </div>
64 64
65 65 <div id="summary" class="fields pr-details-content">
66 66 <div class="field">
67 67 <div class="label-summary">
68 68 <label>${_('Source')}:</label>
69 69 </div>
70 70 <div class="input">
71 71 <div class="pr-origininfo">
72 72 ## branch link is only valid if it is a branch
73 73 <span class="tag">
74 74 %if c.pull_request.source_ref_parts.type == 'branch':
75 75 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
76 76 %else:
77 77 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
78 78 %endif
79 79 </span>
80 80 <span class="clone-url">
81 81 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
82 82 </span>
83 83 <br/>
84 84 % if c.ancestor_commit:
85 85 ${_('Common ancestor')}:
86 86 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
87 87 % endif
88 88 </div>
89 89 %if h.is_hg(c.pull_request.source_repo):
90 90 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
91 91 %elif h.is_git(c.pull_request.source_repo):
92 92 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
93 93 %endif
94 94
95 95 <div class="">
96 96 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
97 97 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
98 98 </div>
99 99
100 100 </div>
101 101 </div>
102 102 <div class="field">
103 103 <div class="label-summary">
104 104 <label>${_('Target')}:</label>
105 105 </div>
106 106 <div class="input">
107 107 <div class="pr-targetinfo">
108 108 ## branch link is only valid if it is a branch
109 109 <span class="tag">
110 110 %if c.pull_request.target_ref_parts.type == 'branch':
111 111 <a href="${h.route_path('repo_commits', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
112 112 %else:
113 113 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
114 114 %endif
115 115 </span>
116 116 <span class="clone-url">
117 117 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
118 118 </span>
119 119 </div>
120 120 </div>
121 121 </div>
122 122
123 123 ## Link to the shadow repository.
124 124 <div class="field">
125 125 <div class="label-summary">
126 126 <label>${_('Merge')}:</label>
127 127 </div>
128 128 <div class="input">
129 129 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
130 130 %if h.is_hg(c.pull_request.target_repo):
131 131 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
132 132 %elif h.is_git(c.pull_request.target_repo):
133 133 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
134 134 %endif
135 135 <div class="">
136 136 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
137 137 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
138 138 </div>
139 139 % else:
140 140 <div class="">
141 141 ${_('Shadow repository data not available')}.
142 142 </div>
143 143 % endif
144 144 </div>
145 145 </div>
146 146
147 147 <div class="field">
148 148 <div class="label-summary">
149 149 <label>${_('Review')}:</label>
150 150 </div>
151 151 <div class="input">
152 152 %if c.pull_request_review_status:
153 153 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
154 154 <span class="changeset-status-lbl tooltip">
155 155 %if c.pull_request.is_closed():
156 156 ${_('Closed')},
157 157 %endif
158 158 ${h.commit_status_lbl(c.pull_request_review_status)}
159 159 </span>
160 160 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
161 161 %endif
162 162 </div>
163 163 </div>
164 164 <div class="field">
165 165 <div class="pr-description-label label-summary" title="${_('Rendered using {} renderer').format(c.renderer)}">
166 166 <label>${_('Description')}:</label>
167 167 </div>
168 168 <div id="pr-desc" class="input">
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer)}</div>
169 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.renderer, repo_name=c.repo_name)}</div>
170 170 </div>
171 171 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
172 172 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
173 173 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
174 174 </div>
175 175 </div>
176 176
177 177 <div class="field">
178 178 <div class="label-summary">
179 179 <label>${_('Versions')}:</label>
180 180 </div>
181 181
182 182 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
183 183 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
184 184
185 185 <div class="pr-versions">
186 186 % if c.show_version_changes:
187 187 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
188 188 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
189 189 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
190 190 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
191 191 data-toggle-off="${_('Hide all versions of this pull request')}">
192 192 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
193 193 </a>
194 194 <table>
195 195 ## SHOW ALL VERSIONS OF PR
196 196 <% ver_pr = None %>
197 197
198 198 % for data in reversed(list(enumerate(c.versions, 1))):
199 199 <% ver_pos = data[0] %>
200 200 <% ver = data[1] %>
201 201 <% ver_pr = ver.pull_request_version_id %>
202 202 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
203 203
204 204 <tr class="version-pr" style="display: ${display_row}">
205 205 <td>
206 206 <code>
207 207 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
208 208 </code>
209 209 </td>
210 210 <td>
211 211 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
212 212 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
213 213 </td>
214 214 <td>
215 215 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
216 216 <i class="tooltip icon-circle review-status-${review_status}" title="${_('Your review status at this version')}"></i>
217 217 </div>
218 218 </td>
219 219 <td>
220 220 % if c.at_version_num != ver_pr:
221 221 <i class="icon-comment"></i>
222 222 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
223 223 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
224 224 </code>
225 225 % endif
226 226 </td>
227 227 <td>
228 228 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
229 229 </td>
230 230 <td>
231 231 ${h.age_component(ver.updated_on, time_is_local=True)}
232 232 </td>
233 233 </tr>
234 234 % endfor
235 235
236 236 <tr>
237 237 <td colspan="6">
238 238 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
239 239 data-label-text-locked="${_('select versions to show changes')}"
240 240 data-label-text-diff="${_('show changes between versions')}"
241 241 data-label-text-show="${_('show pull request for this version')}"
242 242 >
243 243 ${_('select versions to show changes')}
244 244 </button>
245 245 </td>
246 246 </tr>
247 247 </table>
248 248 % else:
249 249 <div class="input">
250 250 ${_('Pull request versions not available')}.
251 251 </div>
252 252 % endif
253 253 </div>
254 254 </div>
255 255
256 256 <div id="pr-save" class="field" style="display: none;">
257 257 <div class="label-summary"></div>
258 258 <div class="input">
259 259 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
260 260 </div>
261 261 </div>
262 262 </div>
263 263 </div>
264 264 <div>
265 265 ## AUTHOR
266 266 <div class="reviewers-title block-right">
267 267 <div class="pr-details-title">
268 268 ${_('Author of this pull request')}
269 269 </div>
270 270 </div>
271 271 <div class="block-right pr-details-content reviewers">
272 272 <ul class="group_members">
273 273 <li>
274 274 ${self.gravatar_with_user(c.pull_request.author.email, 16, tooltip=True)}
275 275 </li>
276 276 </ul>
277 277 </div>
278 278
279 279 ## REVIEW RULES
280 280 <div id="review_rules" style="display: none" class="reviewers-title block-right">
281 281 <div class="pr-details-title">
282 282 ${_('Reviewer rules')}
283 283 %if c.allowed_to_update:
284 284 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
285 285 %endif
286 286 </div>
287 287 <div class="pr-reviewer-rules">
288 288 ## review rules will be appended here, by default reviewers logic
289 289 </div>
290 290 <input id="review_data" type="hidden" name="review_data" value="">
291 291 </div>
292 292
293 293 ## REVIEWERS
294 294 <div class="reviewers-title block-right">
295 295 <div class="pr-details-title">
296 296 ${_('Pull request reviewers')}
297 297 %if c.allowed_to_update:
298 298 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
299 299 %endif
300 300 </div>
301 301 </div>
302 302 <div id="reviewers" class="block-right pr-details-content reviewers">
303 303
304 304 ## members redering block
305 305 <input type="hidden" name="__start__" value="review_members:sequence">
306 306 <ul id="review_members" class="group_members">
307 307
308 308 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
309 309 <script>
310 310 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
311 311 var status = "${(status[0][1].status if status else 'not_reviewed')}";
312 312 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
313 313 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
314 314
315 315 var entry = renderTemplate('reviewMemberEntry', {
316 316 'member': member,
317 317 'mandatory': member.mandatory,
318 318 'reasons': member.reasons,
319 319 'allowed_to_update': allowed_to_update,
320 320 'review_status': status,
321 321 'review_status_label': status_lbl,
322 322 'user_group': member.user_group,
323 323 'create': false
324 324 });
325 325 $('#review_members').append(entry)
326 326 </script>
327 327
328 328 % endfor
329 329
330 330 </ul>
331 331
332 332 <input type="hidden" name="__end__" value="review_members:sequence">
333 333 ## end members redering block
334 334
335 335 %if not c.pull_request.is_closed():
336 336 <div id="add_reviewer" class="ac" style="display: none;">
337 337 %if c.allowed_to_update:
338 338 % if not c.forbid_adding_reviewers:
339 339 <div id="add_reviewer_input" class="reviewer_ac">
340 340 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
341 341 <div id="reviewers_container"></div>
342 342 </div>
343 343 % endif
344 344 <div class="pull-right">
345 345 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
346 346 </div>
347 347 %endif
348 348 </div>
349 349 %endif
350 350 </div>
351 351 </div>
352 352 </div>
353 353 <div class="box">
354 354 ##DIFF
355 355 <div class="table" >
356 356 <div id="changeset_compare_view_content">
357 357 ##CS
358 358 % if c.missing_requirements:
359 359 <div class="box">
360 360 <div class="alert alert-warning">
361 361 <div>
362 362 <strong>${_('Missing requirements:')}</strong>
363 363 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
364 364 </div>
365 365 </div>
366 366 </div>
367 367 % elif c.missing_commits:
368 368 <div class="box">
369 369 <div class="alert alert-warning">
370 370 <div>
371 371 <strong>${_('Missing commits')}:</strong>
372 372 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
373 373 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
374 374 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
375 375 </div>
376 376 </div>
377 377 </div>
378 378 % endif
379 379
380 380 <div class="compare_view_commits_title">
381 381 % if not c.compare_mode:
382 382
383 383 % if c.at_version_pos:
384 384 <h4>
385 385 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
386 386 </h4>
387 387 % endif
388 388
389 389 <div class="pull-left">
390 390 <div class="btn-group">
391 391 <a
392 392 class="btn"
393 393 href="#"
394 394 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
395 395 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
396 396 </a>
397 397 <a
398 398 class="btn"
399 399 href="#"
400 400 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
401 401 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
402 402 </a>
403 403 </div>
404 404 </div>
405 405
406 406 <div class="pull-right">
407 407 % if c.allowed_to_update and not c.pull_request.is_closed():
408 408 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
409 409 % else:
410 410 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
411 411 % endif
412 412
413 413 </div>
414 414 % endif
415 415 </div>
416 416
417 417 % if not c.missing_commits:
418 418 % if c.compare_mode:
419 419 % if c.at_version:
420 420 <h4>
421 421 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
422 422 </h4>
423 423
424 424 <div class="subtitle-compare">
425 425 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
426 426 </div>
427 427
428 428 <div class="container">
429 429 <table class="rctable compare_view_commits">
430 430 <tr>
431 431 <th></th>
432 432 <th>${_('Time')}</th>
433 433 <th>${_('Author')}</th>
434 434 <th>${_('Commit')}</th>
435 435 <th></th>
436 436 <th>${_('Description')}</th>
437 437 </tr>
438 438
439 439 % for c_type, commit in c.commit_changes:
440 440 % if c_type in ['a', 'r']:
441 441 <%
442 442 if c_type == 'a':
443 443 cc_title = _('Commit added in displayed changes')
444 444 elif c_type == 'r':
445 445 cc_title = _('Commit removed in displayed changes')
446 446 else:
447 447 cc_title = ''
448 448 %>
449 449 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
450 450 <td>
451 451 <div class="commit-change-indicator color-${c_type}-border">
452 452 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
453 453 ${c_type.upper()}
454 454 </div>
455 455 </div>
456 456 </td>
457 457 <td class="td-time">
458 458 ${h.age_component(commit.date)}
459 459 </td>
460 460 <td class="td-user">
461 461 ${base.gravatar_with_user(commit.author, 16, tooltip=True)}
462 462 </td>
463 463 <td class="td-hash">
464 464 <code>
465 465 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
466 466 r${commit.idx}:${h.short_id(commit.raw_id)}
467 467 </a>
468 468 ${h.hidden('revisions', commit.raw_id)}
469 469 </code>
470 470 </td>
471 471 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
472 472 <i class="icon-expand-linked"></i>
473 473 </td>
474 474 <td class="mid td-description">
475 475 <div class="log-container truncate-wrap">
476 476 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
477 477 </div>
478 478 </td>
479 479 </tr>
480 480 % endif
481 481 % endfor
482 482 </table>
483 483 </div>
484 484
485 485 % endif
486 486
487 487 % else:
488 488 <%include file="/compare/compare_commits.mako" />
489 489 % endif
490 490
491 491 <div class="cs_files">
492 492 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
493 493 % if c.at_version:
494 494 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['display']) %>
495 495 <% c.comments = c.comment_versions[c.at_version_num]['display'] %>
496 496 % else:
497 497 <% c.inline_cnt = len(c.inline_versions[c.at_version_num]['until']) %>
498 498 <% c.comments = c.comment_versions[c.at_version_num]['until'] %>
499 499 % endif
500 500
501 501 <%
502 502 pr_menu_data = {
503 503 'outdated_comm_count_ver': outdated_comm_count_ver
504 504 }
505 505 %>
506 506
507 507 ${cbdiffs.render_diffset_menu(c.diffset, range_diff_on=c.range_diff_on)}
508 508
509 509 % if c.range_diff_on:
510 510 % for commit in c.commit_ranges:
511 511 ${cbdiffs.render_diffset(
512 512 c.changes[commit.raw_id],
513 513 commit=commit, use_comments=True,
514 514 collapse_when_files_over=5,
515 515 disable_new_comments=True,
516 516 deleted_files_comments=c.deleted_files_comments,
517 517 inline_comments=c.inline_comments,
518 518 pull_request_menu=pr_menu_data)}
519 519 % endfor
520 520 % else:
521 521 ${cbdiffs.render_diffset(
522 522 c.diffset, use_comments=True,
523 523 collapse_when_files_over=30,
524 524 disable_new_comments=not c.allowed_to_comment,
525 525 deleted_files_comments=c.deleted_files_comments,
526 526 inline_comments=c.inline_comments,
527 527 pull_request_menu=pr_menu_data)}
528 528 % endif
529 529
530 530 </div>
531 531 % else:
532 532 ## skipping commits we need to clear the view for missing commits
533 533 <div style="clear:both;"></div>
534 534 % endif
535 535
536 536 </div>
537 537 </div>
538 538
539 539 ## template for inline comment form
540 540 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
541 541
542 542 ## comments heading with count
543 543 <div class="comments-heading">
544 544 <i class="icon-comment"></i>
545 545 ${_('Comments')} ${len(c.comments)}
546 546 </div>
547 547
548 548 ## render general comments
549 549 <div id="comment-tr-show">
550 550 % if general_outdated_comm_count_ver:
551 551 <div class="info-box">
552 552 % if general_outdated_comm_count_ver == 1:
553 553 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
554 554 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
555 555 % else:
556 556 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
557 557 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
558 558 % endif
559 559 </div>
560 560 % endif
561 561 </div>
562 562
563 563 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
564 564
565 565 % if not c.pull_request.is_closed():
566 566 ## merge status, and merge action
567 567 <div class="pull-request-merge">
568 568 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
569 569 </div>
570 570
571 571 ## main comment form and it status
572 572 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
573 573 pull_request_id=c.pull_request.pull_request_id),
574 574 c.pull_request_review_status,
575 575 is_pull_request=True, change_status=c.allowed_to_change_status)}
576 576 %endif
577 577
578 578 <script type="text/javascript">
579 579 if (location.hash) {
580 580 var result = splitDelimitedHash(location.hash);
581 581 var line = $('html').find(result.loc);
582 582 // show hidden comments if we use location.hash
583 583 if (line.hasClass('comment-general')) {
584 584 $(line).show();
585 585 } else if (line.hasClass('comment-inline')) {
586 586 $(line).show();
587 587 var $cb = $(line).closest('.cb');
588 588 $cb.removeClass('cb-collapsed')
589 589 }
590 590 if (line.length > 0){
591 591 offsetScroll(line, 70);
592 592 }
593 593 }
594 594
595 595 versionController = new VersionController();
596 596 versionController.init();
597 597
598 598 reviewersController = new ReviewersController();
599 599 commitsController = new CommitsController();
600 600
601 601 $(function(){
602 602
603 603 // custom code mirror
604 604 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
605 605
606 606 var PRDetails = {
607 607 editButton: $('#open_edit_pullrequest'),
608 608 closeButton: $('#close_edit_pullrequest'),
609 609 deleteButton: $('#delete_pullrequest'),
610 610 viewFields: $('#pr-desc, #pr-title'),
611 611 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
612 612
613 613 init: function() {
614 614 var that = this;
615 615 this.editButton.on('click', function(e) { that.edit(); });
616 616 this.closeButton.on('click', function(e) { that.view(); });
617 617 },
618 618
619 619 edit: function(event) {
620 620 this.viewFields.hide();
621 621 this.editButton.hide();
622 622 this.deleteButton.hide();
623 623 this.closeButton.show();
624 624 this.editFields.show();
625 625 codeMirrorInstance.refresh();
626 626 },
627 627
628 628 view: function(event) {
629 629 this.editButton.show();
630 630 this.deleteButton.show();
631 631 this.editFields.hide();
632 632 this.closeButton.hide();
633 633 this.viewFields.show();
634 634 }
635 635 };
636 636
637 637 var ReviewersPanel = {
638 638 editButton: $('#open_edit_reviewers'),
639 639 closeButton: $('#close_edit_reviewers'),
640 640 addButton: $('#add_reviewer'),
641 641 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
642 642
643 643 init: function() {
644 644 var self = this;
645 645 this.editButton.on('click', function(e) { self.edit(); });
646 646 this.closeButton.on('click', function(e) { self.close(); });
647 647 },
648 648
649 649 edit: function(event) {
650 650 this.editButton.hide();
651 651 this.closeButton.show();
652 652 this.addButton.show();
653 653 this.removeButtons.css('visibility', 'visible');
654 654 // review rules
655 655 reviewersController.loadReviewRules(
656 656 ${c.pull_request.reviewer_data_json | n});
657 657 },
658 658
659 659 close: function(event) {
660 660 this.editButton.show();
661 661 this.closeButton.hide();
662 662 this.addButton.hide();
663 663 this.removeButtons.css('visibility', 'hidden');
664 664 // hide review rules
665 665 reviewersController.hideReviewRules()
666 666 }
667 667 };
668 668
669 669 PRDetails.init();
670 670 ReviewersPanel.init();
671 671
672 672 showOutdated = function(self){
673 673 $('.comment-inline.comment-outdated').show();
674 674 $('.filediff-outdated').show();
675 675 $('.showOutdatedComments').hide();
676 676 $('.hideOutdatedComments').show();
677 677 };
678 678
679 679 hideOutdated = function(self){
680 680 $('.comment-inline.comment-outdated').hide();
681 681 $('.filediff-outdated').hide();
682 682 $('.hideOutdatedComments').hide();
683 683 $('.showOutdatedComments').show();
684 684 };
685 685
686 686 refreshMergeChecks = function(){
687 687 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
688 688 $('.pull-request-merge').css('opacity', 0.3);
689 689 $('.action-buttons-extra').css('opacity', 0.3);
690 690
691 691 $('.pull-request-merge').load(
692 692 loadUrl, function() {
693 693 $('.pull-request-merge').css('opacity', 1);
694 694
695 695 $('.action-buttons-extra').css('opacity', 1);
696 696 }
697 697 );
698 698 };
699 699
700 700 closePullRequest = function (status) {
701 701 // inject closing flag
702 702 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
703 703 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
704 704 $(generalCommentForm.submitForm).submit();
705 705 };
706 706
707 707 $('#show-outdated-comments').on('click', function(e){
708 708 var button = $(this);
709 709 var outdated = $('.comment-outdated');
710 710
711 711 if (button.html() === "(Show)") {
712 712 button.html("(Hide)");
713 713 outdated.show();
714 714 } else {
715 715 button.html("(Show)");
716 716 outdated.hide();
717 717 }
718 718 });
719 719
720 720 $('.show-inline-comments').on('change', function(e){
721 721 var show = 'none';
722 722 var target = e.currentTarget;
723 723 if(target.checked){
724 724 show = ''
725 725 }
726 726 var boxid = $(target).attr('id_for');
727 727 var comments = $('#{0} .inline-comments'.format(boxid));
728 728 var fn_display = function(idx){
729 729 $(this).css('display', show);
730 730 };
731 731 $(comments).each(fn_display);
732 732 var btns = $('#{0} .inline-comments-button'.format(boxid));
733 733 $(btns).each(fn_display);
734 734 });
735 735
736 736 $('#merge_pull_request_form').submit(function() {
737 737 if (!$('#merge_pull_request').attr('disabled')) {
738 738 $('#merge_pull_request').attr('disabled', 'disabled');
739 739 }
740 740 return true;
741 741 });
742 742
743 743 $('#edit_pull_request').on('click', function(e){
744 744 var title = $('#pr-title-input').val();
745 745 var description = codeMirrorInstance.getValue();
746 746 var renderer = $('#pr-renderer-input').val();
747 747 editPullRequest(
748 748 "${c.repo_name}", "${c.pull_request.pull_request_id}",
749 749 title, description, renderer);
750 750 });
751 751
752 752 $('#update_pull_request').on('click', function(e){
753 753 $(this).attr('disabled', 'disabled');
754 754 $(this).addClass('disabled');
755 755 $(this).html(_gettext('Saving...'));
756 756 reviewersController.updateReviewers(
757 757 "${c.repo_name}", "${c.pull_request.pull_request_id}");
758 758 });
759 759
760 760 $('#update_commits').on('click', function(e){
761 761 var isDisabled = !$(e.currentTarget).attr('disabled');
762 762 $(e.currentTarget).attr('disabled', 'disabled');
763 763 $(e.currentTarget).addClass('disabled');
764 764 $(e.currentTarget).removeClass('btn-primary');
765 765 $(e.currentTarget).text(_gettext('Updating...'));
766 766 if(isDisabled){
767 767 updateCommits(
768 768 "${c.repo_name}", "${c.pull_request.pull_request_id}");
769 769 }
770 770 });
771 771 // fixing issue with caches on firefox
772 772 $('#update_commits').removeAttr("disabled");
773 773
774 774 $('.show-inline-comments').on('click', function(e){
775 775 var boxid = $(this).attr('data-comment-id');
776 776 var button = $(this);
777 777
778 778 if(button.hasClass("comments-visible")) {
779 779 $('#{0} .inline-comments'.format(boxid)).each(function(index){
780 780 $(this).hide();
781 781 });
782 782 button.removeClass("comments-visible");
783 783 } else {
784 784 $('#{0} .inline-comments'.format(boxid)).each(function(index){
785 785 $(this).show();
786 786 });
787 787 button.addClass("comments-visible");
788 788 }
789 789 });
790 790
791 791 // register submit callback on commentForm form to track TODOs
792 792 window.commentFormGlobalSubmitSuccessCallback = function(){
793 793 refreshMergeChecks();
794 794 };
795 795
796 796 ReviewerAutoComplete('#user');
797 797
798 798 })
799 799 </script>
800 800
801 801 </div>
802 802 </div>
803 803
804 804 </%def>
General Comments 0
You need to be logged in to leave comments. Login now