##// END OF EJS Templates
commits/pr pages various fixes....
marcink -
r4485:ac1b264f default
parent child Browse files
Show More
@@ -0,0 +1,134 b''
1 ## snippet for sidebar elements
2 ## usage:
3 ## <%namespace name="sidebar" file="/base/sidebar.mako"/>
4 ## ${sidebar.comments_table()}
5 <%namespace name="base" file="/base/base.mako"/>
6
7 <%def name="comments_table(comments, counter_num, todo_comments=False, existing_ids=None, is_pr=True)">
8 <%
9 if todo_comments:
10 cls_ = 'todos-content-table'
11 def sorter(entry):
12 user_id = entry.author.user_id
13 resolved = '1' if entry.resolved else '0'
14 if user_id == c.rhodecode_user.user_id:
15 # own comments first
16 user_id = 0
17 return '{}'.format(str(entry.comment_id).zfill(10000))
18 else:
19 cls_ = 'comments-content-table'
20 def sorter(entry):
21 user_id = entry.author.user_id
22 return '{}'.format(str(entry.comment_id).zfill(10000))
23
24 existing_ids = existing_ids or []
25
26 %>
27
28 <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}">
29
30 % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))):
31 <%
32 display = ''
33 _cls = ''
34 %>
35
36 <%
37 comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', []))
38 prev_comment_ver_index = 0
39 if loop_obj.previous:
40 prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', []))
41
42 ver_info = None
43 if getattr(c, 'versions', []):
44 ver_info = c.versions[comment_ver_index-1] if comment_ver_index else None
45 %>
46 <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %>
47 <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %>
48 <%
49 if (prev_comment_ver_index > comment_ver_index):
50 comments_ver_divider = comment_ver_index
51 else:
52 comments_ver_divider = None
53 %>
54
55 % if todo_comments:
56 % if comment_obj.resolved:
57 <% _cls = 'resolved-todo' %>
58 <% display = 'none' %>
59 % endif
60 % else:
61 ## SKIP TODOs we display them in other area
62 % if comment_obj.is_todo:
63 <% display = 'none' %>
64 % endif
65 ## Skip outdated comments
66 % if comment_obj.outdated:
67 <% display = 'none' %>
68 <% _cls = 'hidden-comment' %>
69 % endif
70 % endif
71
72 % if not todo_comments and comments_ver_divider:
73 <tr class="old-comments-marker">
74 <td colspan="3">
75 % if ver_info:
76 <code>v${comments_ver_divider} ${h.age_component(ver_info.created_on, time_is_local=True, tooltip=False)}</code>
77 % else:
78 <code>v${comments_ver_divider}</code>
79 % endif
80 </td>
81 </tr>
82
83 % endif
84
85 <tr class="${_cls}" style="display: ${display};" data-sidebar-comment-id="${comment_obj.comment_id}">
86 <td class="td-todo-number">
87
88 <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink"
89 href="#comment-${comment_obj.comment_id}"
90 onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})">
91
92 <%
93 version_info = ''
94 if is_pr:
95 version_info = (' made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else ' made in this version')
96 %>
97
98 % if todo_comments:
99 % if comment_obj.is_inline:
100 <i class="tooltip icon-code" title="Inline TODO comment${version_info}."></i>
101 % else:
102 <i class="tooltip icon-comment" title="General TODO comment${version_info}."></i>
103 % endif
104 % else:
105 % if comment_obj.outdated:
106 <i class="tooltip icon-comment-toggle" title="Inline Outdated made in v${comment_ver_index}."></i>
107 % elif comment_obj.is_inline:
108 <i class="tooltip icon-code" title="Inline comment${version_info}."></i>
109 % else:
110 <i class="tooltip icon-comment" title="General comment${version_info}."></i>
111 % endif
112 % endif
113
114 </a>
115 ## NEW, since refresh
116 % if existing_ids and comment_obj.comment_id not in existing_ids:
117 <span class="tag">NEW</span>
118 % endif
119 </td>
120
121 <td class="td-todo-gravatar">
122 ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
123 </td>
124 <td class="todo-comment-text-wrapper">
125 <div class="tooltip todo-comment-text timeago ${('todo-resolved' if comment_obj.resolved else '')} " title="${h.format_date(comment_obj.created_on)}" datetime="${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}">
126 <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code>
127 </div>
128 </td>
129 </tr>
130 % endfor
131
132 </table>
133
134 </%def> No newline at end of file
@@ -485,23 +485,10 b' class TestRepoCommitCommentsView(TestCon'
485
485
486
486
487 def assert_comment_links(response, comments, inline_comments):
487 def assert_comment_links(response, comments, inline_comments):
488 if comments == 1:
488 response.mustcontain(
489 comments_text = "%d General" % comments
489 '<span class="display-none" id="general-comments-count">{}</span>'.format(comments))
490 else:
490 response.mustcontain(
491 comments_text = "%d General" % comments
491 '<span class="display-none" id="inline-comments-count">{}</span>'.format(inline_comments))
492
493 if inline_comments == 1:
494 inline_comments_text = "%d Inline" % inline_comments
495 else:
496 inline_comments_text = "%d Inline" % inline_comments
497
492
498 if comments:
499 response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
500 else:
501 response.mustcontain(comments_text)
502
493
503 if inline_comments:
494
504 response.mustcontain(
505 'id="inline-comments-counter">%s' % inline_comments_text)
506 else:
507 response.mustcontain(inline_comments_text)
@@ -18,8 +18,8 b''
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
22 import logging
21 import logging
22 import collections
23
23
24 from pyramid.httpexceptions import (
24 from pyramid.httpexceptions import (
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
@@ -34,14 +34,14 b' from rhodecode.apps.file_store.exception'
34 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.diffs import (
39 from rhodecode.lib.diffs import (
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 get_diff_whitespace_flag)
41 get_diff_whitespace_flag)
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 import rhodecode.lib.helpers as h
43 import rhodecode.lib.helpers as h
44 from rhodecode.lib.utils2 import safe_unicode, str2bool
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 RepositoryError, CommitDoesNotExistError)
47 RepositoryError, CommitDoesNotExistError)
@@ -87,7 +87,6 b' class RepoCommitsView(RepoAppView):'
87 diff_limit = c.visual.cut_off_limit_diff
87 diff_limit = c.visual.cut_off_limit_diff
88 file_limit = c.visual.cut_off_limit_file
88 file_limit = c.visual.cut_off_limit_file
89
89
90
91 # get ranges of commit ids if preset
90 # get ranges of commit ids if preset
92 commit_range = commit_id_range.split('...')[:2]
91 commit_range = commit_id_range.split('...')[:2]
93
92
@@ -116,6 +115,7 b' class RepoCommitsView(RepoAppView):'
116 except Exception:
115 except Exception:
117 log.exception("General failure")
116 log.exception("General failure")
118 raise HTTPNotFound()
117 raise HTTPNotFound()
118 single_commit = len(c.commit_ranges) == 1
119
119
120 c.changes = OrderedDict()
120 c.changes = OrderedDict()
121 c.lines_added = 0
121 c.lines_added = 0
@@ -129,23 +129,48 b' class RepoCommitsView(RepoAppView):'
129 c.inline_comments = []
129 c.inline_comments = []
130 c.files = []
130 c.files = []
131
131
132 c.statuses = []
133 c.comments = []
132 c.comments = []
134 c.unresolved_comments = []
133 c.unresolved_comments = []
135 c.resolved_comments = []
134 c.resolved_comments = []
136 if len(c.commit_ranges) == 1:
135
136 # Single commit
137 if single_commit:
137 commit = c.commit_ranges[0]
138 commit = c.commit_ranges[0]
138 c.comments = CommentsModel().get_comments(
139 c.comments = CommentsModel().get_comments(
139 self.db_repo.repo_id,
140 self.db_repo.repo_id,
140 revision=commit.raw_id)
141 revision=commit.raw_id)
141 c.statuses.append(ChangesetStatusModel().get_status(
142
142 self.db_repo.repo_id, commit.raw_id))
143 # comments from PR
143 # comments from PR
144 statuses = ChangesetStatusModel().get_statuses(
144 statuses = ChangesetStatusModel().get_statuses(
145 self.db_repo.repo_id, commit.raw_id,
145 self.db_repo.repo_id, commit.raw_id,
146 with_revisions=True)
146 with_revisions=True)
147 prs = set(st.pull_request for st in statuses
147
148 if st.pull_request is not None)
148 prs = set()
149 reviewers = list()
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
151 for c_status in statuses:
152
153 # extract associated pull-requests from votes
154 if c_status.pull_request:
155 prs.add(c_status.pull_request)
156
157 # extract reviewers
158 _user_id = c_status.author.user_id
159 if _user_id not in reviewers_duplicates:
160 reviewers.append(
161 StrictAttributeDict({
162 'user': c_status.author,
163
164 # fake attributed for commit, page that we don't have
165 # but we share the display with PR page
166 'mandatory': False,
167 'reasons': [],
168 'rule_user_group_data': lambda: None
169 })
170 )
171 reviewers_duplicates.add(_user_id)
172
173 c.allowed_reviewers = reviewers
149 # from associated statuses, check the pull requests, and
174 # from associated statuses, check the pull requests, and
150 # show comments from them
175 # show comments from them
151 for pr in prs:
176 for pr in prs:
@@ -156,6 +181,37 b' class RepoCommitsView(RepoAppView):'
156 c.resolved_comments = CommentsModel()\
181 c.resolved_comments = CommentsModel()\
157 .get_commit_resolved_todos(commit.raw_id)
182 .get_commit_resolved_todos(commit.raw_id)
158
183
184 c.inline_comments_flat = CommentsModel()\
185 .get_commit_inline_comments(commit.raw_id)
186
187 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
188 statuses, reviewers)
189
190 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
191
192 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
193
194 for review_obj, member, reasons, mandatory, status in review_statuses:
195 member_reviewer = h.reviewer_as_json(
196 member, reasons=reasons, mandatory=mandatory,
197 user_group=None
198 )
199
200 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
201 member_reviewer['review_status'] = current_review_status
202 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
203 member_reviewer['allowed_to_update'] = False
204 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
205
206 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
207
208 # NOTE(marcink): this uses the same voting logic as in pull-requests
209 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
210 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
211 c.repo_name,
212 commit.raw_id
213 )
214
159 diff = None
215 diff = None
160 # Iterate over ranges (default commit view is always one commit)
216 # Iterate over ranges (default commit view is always one commit)
161 for commit in c.commit_ranges:
217 for commit in c.commit_ranges:
@@ -397,6 +453,7 b' class RepoCommitsView(RepoAppView):'
397 }
453 }
398 if comment:
454 if comment:
399 c.co = comment
455 c.co = comment
456 c.at_version_num = 0
400 rendered_comment = render(
457 rendered_comment = render(
401 'rhodecode:templates/changeset/changeset_comment_block.mako',
458 'rhodecode:templates/changeset/changeset_comment_block.mako',
402 self._get_template_context(c), self.request)
459 self._get_template_context(c), self.request)
@@ -39,7 +39,7 b' from rhodecode.lib.ext_json import json'
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
@@ -474,9 +474,6 b' class RepoPullRequestsView(RepoAppView, '
474
474
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
476
476
477
478
479
480 general_comments, inline_comments = \
477 general_comments, inline_comments = \
481 self.register_comments_vars(c, pull_request_latest, versions)
478 self.register_comments_vars(c, pull_request_latest, versions)
482
479
@@ -980,7 +977,7 b' class RepoPullRequestsView(RepoAppView, '
980 version = self.request.GET.get('version')
977 version = self.request.GET.get('version')
981
978
982 _render = self.request.get_partial_renderer(
979 _render = self.request.get_partial_renderer(
983 'rhodecode:templates/pullrequests/pullrequest_show.mako')
980 'rhodecode:templates/base/sidebar.mako')
984 c = _render.get_call_context()
981 c = _render.get_call_context()
985
982
986 (pull_request_latest,
983 (pull_request_latest,
@@ -999,7 +996,11 b' class RepoPullRequestsView(RepoAppView, '
999
996
1000 self.register_comments_vars(c, pull_request_latest, versions)
997 self.register_comments_vars(c, pull_request_latest, versions)
1001 all_comments = c.inline_comments_flat + c.comments
998 all_comments = c.inline_comments_flat + c.comments
1002 return _render('comments_table', all_comments, len(all_comments))
999
1000 existing_ids = filter(
1001 lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
1002 return _render('comments_table', all_comments, len(all_comments),
1003 existing_ids=existing_ids)
1003
1004
1004 @LoginRequired()
1005 @LoginRequired()
1005 @NotAnonymous()
1006 @NotAnonymous()
@@ -1017,7 +1018,7 b' class RepoPullRequestsView(RepoAppView, '
1017 version = self.request.GET.get('version')
1018 version = self.request.GET.get('version')
1018
1019
1019 _render = self.request.get_partial_renderer(
1020 _render = self.request.get_partial_renderer(
1020 'rhodecode:templates/pullrequests/pullrequest_show.mako')
1021 'rhodecode:templates/base/sidebar.mako')
1021 c = _render.get_call_context()
1022 c = _render.get_call_context()
1022 (pull_request_latest,
1023 (pull_request_latest,
1023 pull_request_at_ver,
1024 pull_request_at_ver,
@@ -1039,7 +1040,10 b' class RepoPullRequestsView(RepoAppView, '
1039 .get_pull_request_resolved_todos(pull_request)
1040 .get_pull_request_resolved_todos(pull_request)
1040
1041
1041 all_comments = c.unresolved_comments + c.resolved_comments
1042 all_comments = c.unresolved_comments + c.resolved_comments
1042 return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True)
1043 existing_ids = filter(
1044 lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
1045 return _render('comments_table', all_comments, len(c.unresolved_comments),
1046 todo_comments=True, existing_ids=existing_ids)
1043
1047
1044 @LoginRequired()
1048 @LoginRequired()
1045 @NotAnonymous()
1049 @NotAnonymous()
@@ -354,34 +354,37 b' class ChangesetStatusModel(BaseModel):'
354 Session().add(new_status)
354 Session().add(new_status)
355 return new_statuses
355 return new_statuses
356
356
357 def aggregate_votes_by_user(self, commit_statuses, reviewers_data):
358
359 commit_statuses_map = collections.defaultdict(list)
360 for st in commit_statuses:
361 commit_statuses_map[st.author.username] += [st]
362
363 reviewers = []
364
365 def version(commit_status):
366 return commit_status.version
367
368 for obj in reviewers_data:
369 if not obj.user:
370 continue
371 statuses = commit_statuses_map.get(obj.user.username, None)
372 if statuses:
373 status_groups = itertools.groupby(
374 sorted(statuses, key=version), version)
375 statuses = [(x, list(y)[0]) for x, y in status_groups]
376
377 reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses))
378
379 return reviewers
380
357 def reviewers_statuses(self, pull_request):
381 def reviewers_statuses(self, pull_request):
358 _commit_statuses = self.get_statuses(
382 _commit_statuses = self.get_statuses(
359 pull_request.source_repo,
383 pull_request.source_repo,
360 pull_request=pull_request,
384 pull_request=pull_request,
361 with_revisions=True)
385 with_revisions=True)
362
386
363 commit_statuses = collections.defaultdict(list)
387 return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers)
364 for st in _commit_statuses:
365 commit_statuses[st.author.username] += [st]
366
367 pull_request_reviewers = []
368
369 def version(commit_status):
370 return commit_status.version
371
372 for obj in pull_request.reviewers:
373 if not obj.user:
374 continue
375 statuses = commit_statuses.get(obj.user.username, None)
376 if statuses:
377 status_groups = itertools.groupby(
378 sorted(statuses, key=version), version)
379 statuses = [(x, list(y)[0]) for x, y in status_groups]
380
381 pull_request_reviewers.append(
382 (obj, obj.user, obj.reasons, obj.mandatory, statuses))
383
384 return pull_request_reviewers
385
388
386 def calculated_review_status(self, pull_request, reviewers_statuses=None):
389 def calculated_review_status(self, pull_request, reviewers_statuses=None):
387 """
390 """
@@ -228,6 +228,14 b' class CommentsModel(BaseModel):'
228
228
229 return todos
229 return todos
230
230
231 def get_commit_inline_comments(self, commit_id):
232 inline_comments = Session().query(ChangesetComment) \
233 .filter(ChangesetComment.line_no != None) \
234 .filter(ChangesetComment.f_path != None) \
235 .filter(ChangesetComment.revision == commit_id)
236 inline_comments = inline_comments.all()
237 return inline_comments
238
231 def _log_audit_action(self, action, action_data, auth_user, comment):
239 def _log_audit_action(self, action, action_data, auth_user, comment):
232 audit_logger.store(
240 audit_logger.store(
233 action=action,
241 action=action,
@@ -55,3 +55,16 b''
55 margin: 0 auto 35px auto;
55 margin: 0 auto 35px auto;
56 }
56 }
57 }
57 }
58
59 .alert-text-success {
60 color: @alert1;
61
62 }
63
64 .alert-text-error {
65 color: @alert2;
66 }
67
68 .alert-text-warning {
69 color: @alert3;
70 }
@@ -254,7 +254,7 b' input[type="button"] {'
254
254
255 .btn-group-actions {
255 .btn-group-actions {
256 position: relative;
256 position: relative;
257 z-index: 100;
257 z-index: 50;
258
258
259 &:not(.open) .btn-action-switcher-container {
259 &:not(.open) .btn-action-switcher-container {
260 display: none;
260 display: none;
@@ -1078,10 +1078,16 b' input.filediff-collapse-state {'
1078 background: @color5;
1078 background: @color5;
1079 color: white;
1079 color: white;
1080 }
1080 }
1081
1081 &[op="comments"] { /* comments on file */
1082 &[op="comments"] { /* comments on file */
1082 background: @grey4;
1083 background: @grey4;
1083 color: white;
1084 color: white;
1084 }
1085 }
1086
1087 &[op="options"] { /* context menu */
1088 background: @grey6;
1089 color: black;
1090 }
1085 }
1091 }
1086 }
1092 }
1087
1093
@@ -31,6 +31,10 b' a { cursor: pointer; }'
31 clear: both;
31 clear: both;
32 }
32 }
33
33
34 .display-none {
35 display: none;
36 }
37
34 .pull-right {
38 .pull-right {
35 float: right !important;
39 float: right !important;
36 }
40 }
@@ -83,6 +83,11 b' body {'
83 }
83 }
84 }
84 }
85
85
86 .flex-container {
87 display: flex;
88 justify-content: space-between;
89 }
90
86 .action-link{
91 .action-link{
87 margin-left: @padding;
92 margin-left: @padding;
88 padding-left: @padding;
93 padding-left: @padding;
@@ -482,6 +487,15 b' ul.auth_plugins {'
482 text-align: left;
487 text-align: left;
483 overflow: hidden;
488 overflow: hidden;
484 white-space: pre-line;
489 white-space: pre-line;
490 padding-top: 5px
491 }
492
493 #add_reviewer {
494 padding-top: 10px;
495 }
496
497 #add_reviewer_input {
498 padding-top: 10px
485 }
499 }
486
500
487 .pr-details-title-author-pref {
501 .pr-details-title-author-pref {
@@ -1169,9 +1183,12 b' label {'
1169 a {
1183 a {
1170 color: @grey5
1184 color: @grey5
1171 }
1185 }
1172 @media screen and (max-width: 1200px) {
1186
1187 // 1024px or smaller
1188 @media screen and (max-width: 1180px) {
1173 display: none;
1189 display: none;
1174 }
1190 }
1191
1175 }
1192 }
1176
1193
1177 img {
1194 img {
@@ -1553,6 +1570,7 b' table.integrations {'
1553 width: 16px;
1570 width: 16px;
1554 padding: 0;
1571 padding: 0;
1555 color: black;
1572 color: black;
1573 cursor: pointer;
1556 }
1574 }
1557
1575
1558 .reviewer_member_mandatory_remove {
1576 .reviewer_member_mandatory_remove {
@@ -1682,7 +1700,7 b' table.group_members {'
1682 }
1700 }
1683
1701
1684 .reviewer_ac .ac-input {
1702 .reviewer_ac .ac-input {
1685 width: 92%;
1703 width: 100%;
1686 margin-bottom: 1em;
1704 margin-bottom: 1em;
1687 }
1705 }
1688
1706
@@ -2756,7 +2774,7 b' table.rctable td.td-search-results div {'
2756 }
2774 }
2757
2775
2758 #help_kb .modal-content{
2776 #help_kb .modal-content{
2759 max-width: 750px;
2777 max-width: 800px;
2760 margin: 10% auto;
2778 margin: 10% auto;
2761
2779
2762 table{
2780 table{
@@ -3053,4 +3071,141 b' form.markup-form {'
3053
3071
3054 .pr-hovercard-title {
3072 .pr-hovercard-title {
3055 padding-top: 5px;
3073 padding-top: 5px;
3056 } No newline at end of file
3074 }
3075
3076 .action-divider {
3077 opacity: 0.5;
3078 }
3079
3080 .details-inline-block {
3081 display: inline-block;
3082 position: relative;
3083 }
3084
3085 .details-inline-block summary {
3086 list-style: none;
3087 }
3088
3089 details:not([open]) > :not(summary) {
3090 display: none !important;
3091 }
3092
3093 .details-reset > summary {
3094 list-style: none;
3095 }
3096
3097 .details-reset > summary::-webkit-details-marker {
3098 display: none;
3099 }
3100
3101 .details-dropdown {
3102 position: absolute;
3103 top: 100%;
3104 width: 185px;
3105 list-style: none;
3106 background-color: #fff;
3107 background-clip: padding-box;
3108 border: 1px solid @grey5;
3109 box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
3110 left: -150px;
3111 text-align: left;
3112 z-index: 90;
3113 }
3114
3115 .dropdown-divider {
3116 display: block;
3117 height: 0;
3118 margin: 8px 0;
3119 border-top: 1px solid @grey5;
3120 }
3121
3122 .dropdown-item {
3123 display: block;
3124 padding: 4px 8px 4px 16px;
3125 overflow: hidden;
3126 text-overflow: ellipsis;
3127 white-space: nowrap;
3128 font-weight: normal;
3129 }
3130
3131 .right-sidebar {
3132 position: fixed;
3133 top: 0px;
3134 bottom: 0;
3135 right: 0;
3136
3137 background: #fafafa;
3138 z-index: 50;
3139 }
3140
3141 .right-sidebar {
3142 border-left: 1px solid @grey5;
3143 }
3144
3145 .right-sidebar.right-sidebar-expanded {
3146 width: 300px;
3147 overflow: scroll;
3148 }
3149
3150 .right-sidebar.right-sidebar-collapsed {
3151 width: 40px;
3152 padding: 0;
3153 display: block;
3154 overflow: hidden;
3155 }
3156
3157 .sidenav {
3158 float: right;
3159 will-change: min-height;
3160 background: #fafafa;
3161 width: 100%;
3162 }
3163
3164 .sidebar-toggle {
3165 height: 30px;
3166 text-align: center;
3167 margin: 15px 0px 0 0;
3168 }
3169
3170 .sidebar-toggle a {
3171
3172 }
3173
3174 .sidebar-content {
3175 margin-left: 15px;
3176 margin-right: 15px;
3177 }
3178
3179 .sidebar-heading {
3180 font-size: 1.2em;
3181 font-weight: 700;
3182 margin-top: 10px;
3183 }
3184
3185 .sidebar-element {
3186 margin-top: 20px;
3187 }
3188
3189 .right-sidebar-collapsed-state {
3190 display: flex;
3191 flex-direction: column;
3192 justify-content: center;
3193 align-items: center;
3194 padding: 0 10px;
3195 cursor: pointer;
3196 font-size: 1.3em;
3197 margin: 0 -15px;
3198 }
3199
3200 .right-sidebar-collapsed-state:hover {
3201 background-color: @grey5;
3202 }
3203
3204 .old-comments-marker {
3205 text-align: left;
3206 }
3207
3208 .old-comments-marker td {
3209 padding-top: 15px;
3210 border-bottom: 1px solid @grey5;
3211 }
@@ -790,7 +790,7 b' input {'
790
790
791 &.main_filter_input {
791 &.main_filter_input {
792 padding: 5px 10px;
792 padding: 5px 10px;
793 min-width: 340px;
793
794 color: @grey7;
794 color: @grey7;
795 background: @black;
795 background: @black;
796 min-height: 18px;
796 min-height: 18px;
@@ -800,11 +800,34 b' input {'
800 color: @grey2 !important;
800 color: @grey2 !important;
801 background: white !important;
801 background: white !important;
802 }
802 }
803
803 &:focus {
804 &:focus {
804 color: @grey2 !important;
805 color: @grey2 !important;
805 background: white !important;
806 background: white !important;
806 }
807 }
808
809 min-width: 360px;
810
811 @media screen and (max-width: 1600px) {
812 min-width: 300px;
813 }
814 @media screen and (max-width: 1500px) {
815 min-width: 280px;
816 }
817 @media screen and (max-width: 1400px) {
818 min-width: 260px;
819 }
820 @media screen and (max-width: 1300px) {
821 min-width: 240px;
822 }
823 @media screen and (max-width: 1200px) {
824 min-width: 220px;
825 }
826 @media screen and (max-width: 720px) {
827 min-width: 140px;
828 }
807 }
829 }
830
808 }
831 }
809
832
810
833
@@ -168,6 +168,7 b''
168 .icon-remove:before { content: '\e810'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* 'ξ ‘' */
169 .icon-fork:before { content: '\e811'; } /* 'ξ ‘' */
170 .icon-more:before { content: '\e812'; } /* 'ξ ’' */
170 .icon-more:before { content: '\e812'; } /* 'ξ ’' */
171 .icon-options:before { content: '\e812'; } /* 'ξ ’' */
171 .icon-search:before { content: '\e813'; } /* 'ξ “' */
172 .icon-search:before { content: '\e813'; } /* 'ξ “' */
172 .icon-scissors:before { content: '\e814'; } /* 'ξ ”' */
173 .icon-scissors:before { content: '\e814'; } /* 'ξ ”' */
173 .icon-download:before { content: '\e815'; } /* 'ξ •' */
174 .icon-download:before { content: '\e815'; } /* 'ξ •' */
@@ -251,6 +252,7 b''
251 // TRANSFORM
252 // TRANSFORM
252 .icon-merge:before {transform: rotate(180deg);}
253 .icon-merge:before {transform: rotate(180deg);}
253 .icon-wide-mode:before {transform: rotate(90deg);}
254 .icon-wide-mode:before {transform: rotate(90deg);}
255 .icon-options:before {transform: rotate(90deg);}
254
256
255 // -- END ICON CLASSES -- //
257 // -- END ICON CLASSES -- //
256
258
@@ -131,6 +131,11 b' function setRCMouseBindings(repoName, re'
131 window.location = pyroutes.url(
131 window.location = pyroutes.url(
132 'edit_repo_perms', {'repo_name': repoName});
132 'edit_repo_perms', {'repo_name': repoName});
133 });
133 });
134 Mousetrap.bind(['t s'], function(e) {
135 if (window.toggleSidebar !== undefined) {
136 window.toggleSidebar();
137 }
138 });
134 }
139 }
135 }
140 }
136
141
@@ -35,4 +35,75 b' var quick_repo_menu = function() {'
35 }, function() {
35 }, function() {
36 hide_quick_repo_menus();
36 hide_quick_repo_menus();
37 });
37 });
38 }; No newline at end of file
38 };
39
40
41 window.toggleElement = function (elem, target) {
42 var $elem = $(elem);
43 var $target = $(target);
44
45 if ($target.is(':visible') || $target.length === 0) {
46 $target.hide();
47 $elem.html($elem.data('toggleOn'))
48 } else {
49 $target.show();
50 $elem.html($elem.data('toggleOff'))
51 }
52
53 return false
54 }
55
56 var marginExpVal = '300' // needs a sync with `.right-sidebar.right-sidebar-expanded` value
57 var marginColVal = '40' // needs a sync with `.right-sidebar.right-sidebar-collapsed` value
58
59 var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
60 var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
61
62 var updateStickyHeader = function () {
63 if (window.updateSticky !== undefined) {
64 // potentially our comments change the active window size, so we
65 // notify sticky elements
66 updateSticky()
67 }
68 }
69
70 var expandSidebar = function () {
71 var $sideBar = $('.right-sidebar');
72 $('.outerwrapper').css(marginExpanded);
73 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
74 $('.right-sidebar-collapsed-state').hide();
75 $('.right-sidebar-expanded-state').show();
76 $('.branding').addClass('display-none');
77 $sideBar.addClass('right-sidebar-expanded')
78 $sideBar.removeClass('right-sidebar-collapsed')
79 }
80
81 var collapseSidebar = function () {
82 var $sideBar = $('.right-sidebar');
83 $('.outerwrapper').css(marginCollapsed);
84 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
85 $('.right-sidebar-collapsed-state').show();
86 $('.right-sidebar-expanded-state').hide();
87 $('.branding').removeClass('display-none');
88 $sideBar.removeClass('right-sidebar-expanded')
89 $sideBar.addClass('right-sidebar-collapsed')
90 }
91
92 window.toggleSidebar = function () {
93 var $sideBar = $('.right-sidebar');
94
95 if ($sideBar.hasClass('right-sidebar-expanded')) {
96 // expanded -> collapsed transition
97 collapseSidebar();
98 var sidebarState = 'collapsed';
99
100 } else {
101 // collapsed -> expanded
102 expandSidebar();
103 var sidebarState = 'expanded';
104 }
105
106 // update our other sticky header in same context
107 updateStickyHeader();
108 storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
109 }
@@ -279,8 +279,11 b' ReviewersController = function () {'
279 $('#user').show(); // show user autocomplete after load
279 $('#user').show(); // show user autocomplete after load
280
280
281 var commitElements = data["diff_info"]['commits'];
281 var commitElements = data["diff_info"]['commits'];
282
282 if (commitElements.length === 0) {
283 if (commitElements.length === 0) {
283 prButtonLock(true, _gettext('no commits'), 'all');
284 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
285 _gettext('There are no commits to merge.'));
286 prButtonLock(true, noCommitsMsg, 'all');
284
287
285 } else {
288 } else {
286 // un-lock PR button, so we cannot send PR before it's calculated
289 // un-lock PR button, so we cannot send PR before it's calculated
@@ -324,7 +327,6 b' ReviewersController = function () {'
324 };
327 };
325
328
326 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
329 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
327 var members = self.$reviewMembers.get(0);
328 var id = reviewer_obj.user_id;
330 var id = reviewer_obj.user_id;
329 var username = reviewer_obj.username;
331 var username = reviewer_obj.username;
330
332
@@ -333,10 +335,10 b' ReviewersController = function () {'
333
335
334 // register IDS to check if we don't have this ID already in
336 // register IDS to check if we don't have this ID already in
335 var currentIds = [];
337 var currentIds = [];
336 var _els = self.$reviewMembers.find('li').toArray();
338
337 for (el in _els) {
339 $.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) {
338 currentIds.push(_els[el].id)
340 currentIds.push($(value).data('reviewerUserId'))
339 }
341 })
340
342
341 var userAllowedReview = function (userId) {
343 var userAllowedReview = function (userId) {
342 var allowed = true;
344 var allowed = true;
@@ -354,12 +356,12 b' ReviewersController = function () {'
354 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
356 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
355 } else {
357 } else {
356 // only add if it's not there
358 // only add if it's not there
357 var alreadyReviewer = currentIds.indexOf('reviewer_' + id) != -1;
359 var alreadyReviewer = currentIds.indexOf(id) != -1;
358
360
359 if (alreadyReviewer) {
361 if (alreadyReviewer) {
360 alert(_gettext('User `{0}` already in reviewers').format(username));
362 alert(_gettext('User `{0}` already in reviewers').format(username));
361 } else {
363 } else {
362 members.innerHTML += renderTemplate('reviewMemberEntry', {
364 var reviewerEntry = renderTemplate('reviewMemberEntry', {
363 'member': reviewer_obj,
365 'member': reviewer_obj,
364 'mandatory': mandatory,
366 'mandatory': mandatory,
365 'reasons': reasons,
367 'reasons': reasons,
@@ -368,7 +370,9 b' ReviewersController = function () {'
368 'review_status_label': _gettext('Not Reviewed'),
370 'review_status_label': _gettext('Not Reviewed'),
369 'user_group': reviewer_obj.user_group,
371 'user_group': reviewer_obj.user_group,
370 'create': true,
372 'create': true,
371 });
373 'rule_show': true,
374 })
375 $(self.$reviewMembers.selector).append(reviewerEntry);
372 tooltipActivate();
376 tooltipActivate();
373 }
377 }
374 }
378 }
@@ -492,7 +496,7 b' var ReviewerAutoComplete = function(inpu'
492 };
496 };
493
497
494
498
495 VersionController = function () {
499 window.VersionController = function () {
496 var self = this;
500 var self = this;
497 this.$verSource = $('input[name=ver_source]');
501 this.$verSource = $('input[name=ver_source]');
498 this.$verTarget = $('input[name=ver_target]');
502 this.$verTarget = $('input[name=ver_target]');
@@ -612,25 +616,10 b' VersionController = function () {'
612 return false
616 return false
613 };
617 };
614
618
615 this.toggleElement = function (elem, target) {
616 var $elem = $(elem);
617 var $target = $(target);
618
619 if ($target.is(':visible') || $target.length === 0) {
620 $target.hide();
621 $elem.html($elem.data('toggleOn'))
622 } else {
623 $target.show();
624 $elem.html($elem.data('toggleOff'))
625 }
626
627 return false
628 }
629
630 };
619 };
631
620
632
621
633 UpdatePrController = function () {
622 window.UpdatePrController = function () {
634 var self = this;
623 var self = this;
635 this.$updateCommits = $('#update_commits');
624 this.$updateCommits = $('#update_commits');
636 this.$updateCommitsSwitcher = $('#update_commits_switcher');
625 this.$updateCommitsSwitcher = $('#update_commits_switcher');
@@ -672,4 +661,230 b' UpdatePrController = function () {'
672 templateContext.repo_name,
661 templateContext.repo_name,
673 templateContext.pull_request_data.pull_request_id, force);
662 templateContext.pull_request_data.pull_request_id, force);
674 };
663 };
675 }; No newline at end of file
664 };
665
666 /**
667 * Reviewer display panel
668 */
669 window.ReviewersPanel = {
670 editButton: null,
671 closeButton: null,
672 addButton: null,
673 removeButtons: null,
674 reviewRules: null,
675 setReviewers: null,
676
677 setSelectors: function () {
678 var self = this;
679 self.editButton = $('#open_edit_reviewers');
680 self.closeButton =$('#close_edit_reviewers');
681 self.addButton = $('#add_reviewer');
682 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
683 },
684
685 init: function (reviewRules, setReviewers) {
686 var self = this;
687 self.setSelectors();
688
689 this.reviewRules = reviewRules;
690 this.setReviewers = setReviewers;
691
692 this.editButton.on('click', function (e) {
693 self.edit();
694 });
695 this.closeButton.on('click', function (e) {
696 self.close();
697 self.renderReviewers();
698 });
699
700 self.renderReviewers();
701
702 },
703
704 renderReviewers: function () {
705
706 $('#review_members').html('')
707 $.each(this.setReviewers.reviewers, function (key, val) {
708 var member = val;
709
710 var entry = renderTemplate('reviewMemberEntry', {
711 'member': member,
712 'mandatory': member.mandatory,
713 'reasons': member.reasons,
714 'allowed_to_update': member.allowed_to_update,
715 'review_status': member.review_status,
716 'review_status_label': member.review_status_label,
717 'user_group': member.user_group,
718 'create': false
719 });
720
721 $('#review_members').append(entry)
722 });
723 tooltipActivate();
724
725 },
726
727 edit: function (event) {
728 this.editButton.hide();
729 this.closeButton.show();
730 this.addButton.show();
731 $(this.removeButtons.selector).css('visibility', 'visible');
732 // review rules
733 reviewersController.loadReviewRules(this.reviewRules);
734 },
735
736 close: function (event) {
737 this.editButton.show();
738 this.closeButton.hide();
739 this.addButton.hide();
740 $(this.removeButtons.selector).css('visibility', 'hidden');
741 // hide review rules
742 reviewersController.hideReviewRules()
743 }
744 };
745
746
747 /**
748 * OnLine presence using channelstream
749 */
750 window.ReviewerPresenceController = function (channel) {
751 var self = this;
752 this.channel = channel;
753 this.users = {};
754
755 this.storeUsers = function (users) {
756 self.users = {}
757 $.each(users, function (index, value) {
758 var userId = value.state.id;
759 self.users[userId] = value.state;
760 })
761 }
762
763 this.render = function () {
764 $.each($('.reviewer_entry'), function (index, value) {
765 var userData = $(value).data();
766 if (self.users[userData.reviewerUserId] !== undefined) {
767 $(value).find('.presence-state').show();
768 } else {
769 $(value).find('.presence-state').hide();
770 }
771 })
772 };
773
774 this.handlePresence = function (data) {
775 if (data.type == 'presence' && data.channel === self.channel) {
776 this.storeUsers(data.users);
777 this.render()
778 }
779 };
780
781 this.handleChannelUpdate = function (data) {
782 if (data.channel === this.channel) {
783 this.storeUsers(data.state.users);
784 this.render()
785 }
786
787 };
788
789 /* subscribe to the current presence */
790 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
791 /* subscribe to updates e.g connect/disconnect */
792 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
793
794 };
795
796 window.refreshComments = function (version) {
797 version = version || templateContext.pull_request_data.pull_request_version || '';
798
799 // Pull request case
800 if (templateContext.pull_request_data.pull_request_id !== null) {
801 var params = {
802 'pull_request_id': templateContext.pull_request_data.pull_request_id,
803 'repo_name': templateContext.repo_name,
804 'version': version,
805 };
806 var loadUrl = pyroutes.url('pullrequest_comments', params);
807 } // commit case
808 else {
809 return
810 }
811
812 var currentIDs = []
813 $.each($('.comment'), function (idx, element) {
814 currentIDs.push($(element).data('commentId'));
815 });
816 var data = {"comments[]": currentIDs};
817
818 var $targetElem = $('.comments-content-table');
819 $targetElem.css('opacity', 0.3);
820 $targetElem.load(
821 loadUrl, data, function (responseText, textStatus, jqXHR) {
822 if (jqXHR.status !== 200) {
823 return false;
824 }
825 var $counterElem = $('#comments-count');
826 var newCount = $(responseText).data('counter');
827 if (newCount !== undefined) {
828 var callback = function () {
829 $counterElem.animate({'opacity': 1.00}, 200)
830 $counterElem.html(newCount);
831 };
832 $counterElem.animate({'opacity': 0.15}, 200, callback);
833 }
834
835 $targetElem.css('opacity', 1);
836 tooltipActivate();
837 }
838 );
839 }
840
841 window.refreshTODOs = function (version) {
842 version = version || templateContext.pull_request_data.pull_request_version || '';
843 // Pull request case
844 if (templateContext.pull_request_data.pull_request_id !== null) {
845 var params = {
846 'pull_request_id': templateContext.pull_request_data.pull_request_id,
847 'repo_name': templateContext.repo_name,
848 'version': version,
849 };
850 var loadUrl = pyroutes.url('pullrequest_comments', params);
851 } // commit case
852 else {
853 return
854 }
855
856 var currentIDs = []
857 $.each($('.comment'), function (idx, element) {
858 currentIDs.push($(element).data('commentId'));
859 });
860
861 var data = {"comments[]": currentIDs};
862 var $targetElem = $('.todos-content-table');
863 $targetElem.css('opacity', 0.3);
864 $targetElem.load(
865 loadUrl, data, function (responseText, textStatus, jqXHR) {
866 if (jqXHR.status !== 200) {
867 return false;
868 }
869 var $counterElem = $('#todos-count')
870 var newCount = $(responseText).data('counter');
871 if (newCount !== undefined) {
872 var callback = function () {
873 $counterElem.animate({'opacity': 1.00}, 200)
874 $counterElem.html(newCount);
875 };
876 $counterElem.animate({'opacity': 0.15}, 200, callback);
877 }
878
879 $targetElem.css('opacity', 1);
880 tooltipActivate();
881 }
882 );
883 }
884
885 window.refreshAllComments = function (version) {
886 version = version || templateContext.pull_request_data.pull_request_version || '';
887
888 refreshComments(version);
889 refreshTODOs(version);
890 };
@@ -701,9 +701,6 b''
701 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
701 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
702 notice_display = 'none' if len(notice_messages) == 0 else ''
702 notice_display = 'none' if len(notice_messages) == 0 else ''
703 %>
703 %>
704 <style>
705
706 </style>
707
704
708 <ul id="quick" class="main_nav navigation horizontal-list">
705 <ul id="quick" class="main_nav navigation horizontal-list">
709 ## notice box for important system messages
706 ## notice box for important system messages
@@ -1202,6 +1199,7 b''
1202 ('g p', 'Goto pull requests page'),
1199 ('g p', 'Goto pull requests page'),
1203 ('g o', 'Goto repository settings'),
1200 ('g o', 'Goto repository settings'),
1204 ('g O', 'Goto repository access permissions settings'),
1201 ('g O', 'Goto repository access permissions settings'),
1202 ('t s', 'Toggle sidebar on some pages'),
1205 ]
1203 ]
1206 %>
1204 %>
1207 %for key, desc in elems:
1205 %for key, desc in elems:
@@ -1221,3 +1219,36 b''
1221 </div><!-- /.modal-content -->
1219 </div><!-- /.modal-content -->
1222 </div><!-- /.modal-dialog -->
1220 </div><!-- /.modal-dialog -->
1223 </div><!-- /.modal -->
1221 </div><!-- /.modal -->
1222
1223
1224 <script type="text/javascript">
1225 (function () {
1226 "use sctrict";
1227
1228 var $sideBar = $('.right-sidebar');
1229 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1230 var sidebarState = templateContext.session_attrs.sidebarState;
1231 var sidebarEnabled = $('aside.right-sidebar').get(0);
1232
1233 if (sidebarState === 'expanded') {
1234 expanded = true
1235 } else if (sidebarState === 'collapsed') {
1236 expanded = false
1237 }
1238 if (sidebarEnabled) {
1239 // show sidebar since it's hidden on load
1240 $('.right-sidebar').show();
1241
1242 // init based on set initial class, or if defined user session attrs
1243 if (expanded) {
1244 window.expandSidebar();
1245 window.updateStickyHeader();
1246
1247 } else {
1248 window.collapseSidebar();
1249 window.updateStickyHeader();
1250 }
1251 }
1252 })()
1253
1254 </script>
@@ -4,6 +4,8 b''
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
8
7
9
8 <%def name="title()">
10 <%def name="title()">
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
@@ -100,22 +102,6 b''
100 % endif
102 % endif
101 </div>
103 </div>
102
104
103 %if c.statuses:
104 <div class="tag status-tag-${c.statuses[0]} pull-right">
105 <i class="icon-circle review-status-${c.statuses[0]}"></i>
106 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
107 </div>
108 %endif
109
110 </div>
111
112 </div>
113 </div>
114
115 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
116 <div class="left-label-summary">
117 <p>${_('Commit navigation')}:</p>
118 <div class="right-label-summary">
119 <span id="parent_link" class="tag tagtag">
105 <span id="parent_link" class="tag tagtag">
120 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
121 </span>
107 </span>
@@ -123,7 +109,9 b''
123 <span id="child_link" class="tag tagtag">
109 <span id="child_link" class="tag tagtag">
124 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
125 </span>
111 </span>
112
126 </div>
113 </div>
114
127 </div>
115 </div>
128 </div>
116 </div>
129
117
@@ -160,7 +148,9 b''
160 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
161 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
162 ${cbdiffs.render_diffset(
150 ${cbdiffs.render_diffset(
163 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
151 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
152 inline_comments=c.inline_comments,
153 show_todos=False)}
164 </div>
154 </div>
165
155
166 ## template for inline comment form
156 ## template for inline comment form
@@ -169,7 +159,7 b''
169 ## comments heading with count
159 ## comments heading with count
170 <div class="comments-heading">
160 <div class="comments-heading">
171 <i class="icon-comment"></i>
161 <i class="icon-comment"></i>
172 ${_('Comments')} ${len(c.comments)}
162 ${_('General Comments')} ${len(c.comments)}
173 </div>
163 </div>
174
164
175 ## render comments
165 ## render comments
@@ -180,123 +170,262 b''
180 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
181 </div>
171 </div>
182
172
183 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
173 ### NAV SIDEBAR
184 <script type="text/javascript">
174 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
175 <div class="sidenav navbar__inner" >
176 ## TOGGLE
177 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
178 <a href="#toggleSidebar" class="grey-link-action">
179
180 </a>
181 </div>
182
183 ## CONTENT
184 <div class="sidebar-content">
185
185
186 $(document).ready(function() {
186 ## RULES SUMMARY/RULES
187 <div class="sidebar-element clear-both">
188 <% vote_title = _ungettext(
189 'Status calculated based on votes from {} reviewer',
190 'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
191 %>
192
193 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
194 <i class="icon-circle review-status-${c.commit_review_status}"></i>
195 ${len(c.allowed_reviewers)}
196 </div>
197 </div>
187
198
188 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
199 ## REVIEWERS
189 if($('#trimmed_message_box').height() === boxmax){
200 <div class="right-sidebar-expanded-state pr-details-title">
190 $('#message_expand').show();
201 <span class="tooltip sidebar-heading" title="${vote_title}">
191 }
202 <i class="icon-circle review-status-${c.commit_review_status}"></i>
203 ${_('Reviewers')}
204 </span>
205 </div>
206
207 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
208
209 <table id="review_members" class="group_members">
210 ## This content is loaded via JS and ReviewersPanel
211 </table>
212
213 </div>
214
215 ## TODOs
216 <div class="sidebar-element clear-both">
217 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
218 <i class="icon-flag-filled"></i>
219 <span id="todos-count">${len(c.unresolved_comments)}</span>
220 </div>
221
222 <div class="right-sidebar-expanded-state pr-details-title">
223 ## Only show unresolved, that is only what matters
224 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
225 <i class="icon-flag-filled"></i>
226 TODOs
227 </span>
228
229 % if c.resolved_comments:
230 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
231 % else:
232 <span class="block-right last-item noselect">Show resolved</span>
233 % endif
234
235 </div>
192
236
193 $('#message_expand').on('click', function(e){
237 <div class="right-sidebar-expanded-state pr-details-content">
194 $('#trimmed_message_box').css('max-height', 'none');
238 % if c.unresolved_comments + c.resolved_comments:
195 $(this).hide();
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
196 });
240 % else:
241 <table>
242 <tr>
243 <td>
244 ${_('No TODOs yet')}
245 </td>
246 </tr>
247 </table>
248 % endif
249 </div>
250 </div>
251
252 ## COMMENTS
253 <div class="sidebar-element clear-both">
254 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
255 <i class="icon-comment" style="color: #949494"></i>
256 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
257 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
258 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
259 </div>
260
261 <div class="right-sidebar-expanded-state pr-details-title">
262 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
263 <i class="icon-comment" style="color: #949494"></i>
264 ${_('Comments')}
265 </span>
266
267 </div>
197
268
198 $('.show-inline-comments').on('click', function(e){
269 <div class="right-sidebar-expanded-state pr-details-content">
199 var boxid = $(this).attr('data-comment-id');
270 % if c.inline_comments_flat + c.comments:
200 var button = $(this);
271 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
272 % else:
273 <table>
274 <tr>
275 <td>
276 ${_('No Comments yet')}
277 </td>
278 </tr>
279 </table>
280 % endif
281 </div>
282
283 </div>
284
285 </div>
286
287 </div>
288 </aside>
201
289
202 if(button.hasClass("comments-visible")) {
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
203 $('#{0} .inline-comments'.format(boxid)).each(function(index){
291 <script type="text/javascript">
204 $(this).hide();
292 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
293
294 $(document).ready(function () {
295 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
296
297 if ($('#trimmed_message_box').height() === boxmax) {
298 $('#message_expand').show();
299 }
300
301 $('#message_expand').on('click', function (e) {
302 $('#trimmed_message_box').css('max-height', 'none');
303 $(this).hide();
304 });
305
306 $('.show-inline-comments').on('click', function (e) {
307 var boxid = $(this).attr('data-comment-id');
308 var button = $(this);
309
310 if (button.hasClass("comments-visible")) {
311 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
312 $(this).hide();
205 });
313 });
206 button.removeClass("comments-visible");
314 button.removeClass("comments-visible");
207 } else {
315 } else {
208 $('#{0} .inline-comments'.format(boxid)).each(function(index){
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
209 $(this).show();
317 $(this).show();
210 });
318 });
211 button.addClass("comments-visible");
319 button.addClass("comments-visible");
212 }
320 }
213 });
321 });
214
322
215 // next links
323 // next links
216 $('#child_link').on('click', function(e){
324 $('#child_link').on('click', function (e) {
217 // fetch via ajax what is going to be the next link, if we have
325 // fetch via ajax what is going to be the next link, if we have
218 // >1 links show them to user to choose
326 // >1 links show them to user to choose
219 if(!$('#child_link').hasClass('disabled')){
327 if (!$('#child_link').hasClass('disabled')) {
220 $.ajax({
328 $.ajax({
221 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
222 success: function(data) {
330 success: function (data) {
223 if(data.results.length === 0){
331 if (data.results.length === 0) {
224 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
225 }
333 }
226 if(data.results.length === 1){
334 if (data.results.length === 1) {
227 var commit = data.results[0];
335 var commit = data.results[0];
228 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
336 window.location = pyroutes.url('repo_commit', {
229 }
337 'repo_name': '${c.repo_name}',
230 else if(data.results.length === 2){
338 'commit_id': commit.raw_id
231 $('#child_link').addClass('disabled');
339 });
232 $('#child_link').addClass('double');
340 } else if (data.results.length === 2) {
341 $('#child_link').addClass('disabled');
342 $('#child_link').addClass('double');
233
343
234 var _html = '';
344 var _html = '';
235 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
236 .replace('__branch__', data.results[0].branch)
346 .replace('__branch__', data.results[0].branch)
237 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
238 .replace('__title__', data.results[0].message)
348 .replace('__title__', data.results[0].message)
239 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
349 .replace('__url__', pyroutes.url('repo_commit', {
240 _html +=' | ';
350 'repo_name': '${c.repo_name}',
241 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
351 'commit_id': data.results[0].raw_id
242 .replace('__branch__', data.results[1].branch)
352 }));
243 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
353 _html += ' | ';
244 .replace('__title__', data.results[1].message)
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
245 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
355 .replace('__branch__', data.results[1].branch)
246 $('#child_link').html(_html);
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
247 }
357 .replace('__title__', data.results[1].message)
358 .replace('__url__', pyroutes.url('repo_commit', {
359 'repo_name': '${c.repo_name}',
360 'commit_id': data.results[1].raw_id
361 }));
362 $('#child_link').html(_html);
363 }
248 }
364 }
249 });
365 });
250 e.preventDefault();
366 e.preventDefault();
251 }
367 }
252 });
368 });
253
369
254 // prev links
370 // prev links
255 $('#parent_link').on('click', function(e){
371 $('#parent_link').on('click', function (e) {
256 // fetch via ajax what is going to be the next link, if we have
372 // fetch via ajax what is going to be the next link, if we have
257 // >1 links show them to user to choose
373 // >1 links show them to user to choose
258 if(!$('#parent_link').hasClass('disabled')){
374 if (!$('#parent_link').hasClass('disabled')) {
259 $.ajax({
375 $.ajax({
260 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
261 success: function(data) {
377 success: function (data) {
262 if(data.results.length === 0){
378 if (data.results.length === 0) {
263 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
264 }
380 }
265 if(data.results.length === 1){
381 if (data.results.length === 1) {
266 var commit = data.results[0];
382 var commit = data.results[0];
267 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
383 window.location = pyroutes.url('repo_commit', {
268 }
384 'repo_name': '${c.repo_name}',
269 else if(data.results.length === 2){
385 'commit_id': commit.raw_id
270 $('#parent_link').addClass('disabled');
386 });
271 $('#parent_link').addClass('double');
387 } else if (data.results.length === 2) {
388 $('#parent_link').addClass('disabled');
389 $('#parent_link').addClass('double');
272
390
273 var _html = '';
391 var _html = '';
274 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
275 .replace('__branch__', data.results[0].branch)
393 .replace('__branch__', data.results[0].branch)
276 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
277 .replace('__title__', data.results[0].message)
395 .replace('__title__', data.results[0].message)
278 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
396 .replace('__url__', pyroutes.url('repo_commit', {
279 _html +=' | ';
397 'repo_name': '${c.repo_name}',
280 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
398 'commit_id': data.results[0].raw_id
281 .replace('__branch__', data.results[1].branch)
399 }));
282 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
400 _html += ' | ';
283 .replace('__title__', data.results[1].message)
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
284 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
402 .replace('__branch__', data.results[1].branch)
285 $('#parent_link').html(_html);
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
286 }
404 .replace('__title__', data.results[1].message)
405 .replace('__url__', pyroutes.url('repo_commit', {
406 'repo_name': '${c.repo_name}',
407 'commit_id': data.results[1].raw_id
408 }));
409 $('#parent_link').html(_html);
410 }
287 }
411 }
288 });
412 });
289 e.preventDefault();
413 e.preventDefault();
290 }
414 }
291 });
415 });
292
416
293 // browse tree @ revision
417 // browse tree @ revision
294 $('#files_link').on('click', function(e){
418 $('#files_link').on('click', function (e) {
295 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
296 e.preventDefault();
420 e.preventDefault();
297 });
421 });
298
422
299 })
423 ReviewersPanel.init(null, setReviewersData);
300 </script>
424
425 var channel = '${c.commit_broadcast_channel}';
426 new ReviewerPresenceController(channel)
427
428 })
429 </script>
301
430
302 </%def>
431 </%def>
@@ -11,6 +11,10 b''
11 <%namespace name="base" file="/base/base.mako"/>
11 <%namespace name="base" file="/base/base.mako"/>
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13
13
14 <%
15 from rhodecode.model.comment import CommentsModel
16 comment_model = CommentsModel()
17 %>
14 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
18 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
15 <% latest_ver = len(getattr(c, 'versions', [])) %>
19 <% latest_ver = len(getattr(c, 'versions', [])) %>
16
20
@@ -155,20 +159,16 b''
155 </div>
159 </div>
156 %endif
160 %endif
157
161
158 <a class="permalink" href="#comment-${comment.comment_id}">&para; #${comment.comment_id}</a>
159
160 <div class="comment-links-block">
162 <div class="comment-links-block">
161
163
162 % if inline:
164 % if inline:
163 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
165 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
164 % if outdated_at_ver:
166 % if outdated_at_ver:
165 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
167 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">outdated ${'v{}'.format(comment_ver)}</code>
166 outdated ${'v{}'.format(comment_ver)} |
168 <code class="action-divider">|</code>
167 </code>
168 % elif comment_ver:
169 % elif comment_ver:
169 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
170 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
170 ${'v{}'.format(comment_ver)} |
171 <code class="action-divider">|</code>
171 </code>
172 % endif
172 % endif
173 </a>
173 </a>
174 % else:
174 % else:
@@ -179,45 +179,70 b''
179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
180 >
180 >
181 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
181 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
182 </a> |
182 </a>
183 <code class="action-divider">|</code>
183 % else:
184 % else:
184 <a class="tooltip pr-version"
185 <a class="tooltip pr-version"
185 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
186 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
186 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)}"
187 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)}"
187 >
188 >
188 <code class="pr-version-num">
189 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
189 ${'v{}'.format(comment_ver)}
190 </a>
190 </code>
191 <code class="action-divider">|</code>
191 </a> |
192 % endif
192 % endif
193
193
194 % endif
194 % endif
195 % endif
195 % endif
196
196
197 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
197 <details class="details-reset details-inline-block">
198 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
198 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
199 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
199 <details-menu class="details-dropdown">
200 ## permissions to delete
200
201 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
201 <div class="dropdown-item">
202 <a onclick="return Rhodecode.comments.editComment(this);"
202 ${_('Comment')} #${comment.comment_id}
203 class="edit-comment">${_('Edit')}</a>
203 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
204 | <a onclick="return Rhodecode.comments.deleteComment(this);"
204 </div>
205 class="delete-comment">${_('Delete')}</a>
206 %else:
207 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
208 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
209 %endif
210 %else:
211 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
212 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
213 %endif
214
205
206 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
207 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
208 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
209 ## permissions to delete
210 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
211 <div class="dropdown-divider"></div>
212 <div class="dropdown-item">
213 <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
214 </div>
215 <div class="dropdown-item">
216 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
217 </div>
218 %else:
219 <div class="dropdown-divider"></div>
220 <div class="dropdown-item">
221 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
222 </div>
223 <div class="dropdown-item">
224 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
225 </div>
226 %endif
227 %else:
228 <div class="dropdown-divider"></div>
229 <div class="dropdown-item">
230 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
231 </div>
232 <div class="dropdown-item">
233 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
234 </div>
235 %endif
236 </details-menu>
237 </details>
238
239 <code class="action-divider">|</code>
215 % if outdated_at_ver:
240 % if outdated_at_ver:
216 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
241 <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
217 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
242 <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
218 % else:
243 % else:
219 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
244 <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
220 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
245 <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
221 % endif
246 % endif
222
247
223 </div>
248 </div>
@@ -102,6 +102,11 b''
102 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
102 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
103
103
104 %for commit in c.commit_ranges:
104 %for commit in c.commit_ranges:
105 ## commit range header for each individual diff
106 <h3>
107 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
108 </h3>
109
105 ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
110 ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
106 ${cbdiffs.render_diffset(
111 ${cbdiffs.render_diffset(
107 diffset=c.changes[commit.raw_id],
112 diffset=c.changes[commit.raw_id],
@@ -61,6 +61,8 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
61 diffset_container_id = h.md5(diffset.target_ref)
61 diffset_container_id = h.md5(diffset.target_ref)
62 collapse_all = len(diffset.files) > collapse_when_files_over
62 collapse_all = len(diffset.files) > collapse_when_files_over
63 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
63 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
64 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
65 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
64 %>
66 %>
65
67
66 %if use_comments:
68 %if use_comments:
@@ -208,13 +210,6 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
208 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
210 <a href="${h.current_route_path(request, fulldiff=1)}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a>
209 </h2>
211 </h2>
210 </div>
212 </div>
211 ## commit range header for each individual diff
212 % elif commit and hasattr(c, 'commit_ranges') and len(c.commit_ranges) > 1:
213 <div class="diffset-heading ${(diffset.limited_diff and 'diffset-heading-warning' or '')}">
214 <div class="clearinner">
215 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=diffset.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
216 </div>
217 </div>
218 % endif
213 % endif
219
214
220 <div id="todo-box">
215 <div id="todo-box">
@@ -239,6 +234,43 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
239 <% over_lines_changed_limit = False %>
234 <% over_lines_changed_limit = False %>
240 %for i, filediff in enumerate(diffset.files):
235 %for i, filediff in enumerate(diffset.files):
241
236
237 %if filediff.source_file_path and filediff.target_file_path:
238 %if filediff.source_file_path != filediff.target_file_path:
239 ## file was renamed, or copied
240 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
241 <%
242 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> <del>{}</del>'.format(filediff.target_file_path, filediff.source_file_path))
243 final_path = filediff.target_file_path
244 %>
245 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
246 <%
247 final_file_name = h.literal(u'{} <i class="icon-angle-left"></i> {}'.format(filediff.target_file_path, filediff.source_file_path))
248 final_path = filediff.target_file_path
249 %>
250 %endif
251 %else:
252 ## file was modified
253 <%
254 final_file_name = filediff.source_file_path
255 final_path = final_file_name
256 %>
257 %endif
258 %else:
259 %if filediff.source_file_path:
260 ## file was deleted
261 <%
262 final_file_name = filediff.source_file_path
263 final_path = final_file_name
264 %>
265 %else:
266 ## file was added
267 <%
268 final_file_name = filediff.target_file_path
269 final_path = final_file_name
270 %>
271 %endif
272 %endif
273
242 <%
274 <%
243 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
275 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
244 over_lines_changed_limit = lines_changed > lines_changed_limit
276 over_lines_changed_limit = lines_changed > lines_changed_limit
@@ -258,13 +290,39 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
258 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
290 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
259 %>
291 %>
260 <div class="filediff-collapse-indicator icon-"></div>
292 <div class="filediff-collapse-indicator icon-"></div>
261 <span class="pill-group pull-right" >
293
294 ## Comments/Options PILL
295 <span class="pill-group pull-right">
262 <span class="pill" op="comments">
296 <span class="pill" op="comments">
263
264 <i class="icon-comment"></i> ${len(total_file_comments)}
297 <i class="icon-comment"></i> ${len(total_file_comments)}
265 </span>
298 </span>
299
300 <details class="details-reset details-inline-block">
301 <summary class="noselect">
302 <i class="pill icon-options cursor-pointer" op="options"></i>
303 </summary>
304 <details-menu class="details-dropdown">
305
306 <div class="dropdown-item">
307 <span>${final_path}</span>
308 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="Copy file path"></span>
309 </div>
310
311 <div class="dropdown-divider"></div>
312
313 <div class="dropdown-item">
314 <% permalink = request.current_route_url(_anchor='a_{}'.format(h.FID(filediff.raw_id, filediff.patch['filename']))) %>
315 <a href="${permalink}">ΒΆ permalink</a>
316 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${permalink}" title="Copy permalink"></span>
317 </div>
318
319
320 </details-menu>
321 </details>
322
266 </span>
323 </span>
267 ${diff_ops(filediff)}
324
325 ${diff_ops(final_file_name, filediff)}
268
326
269 </label>
327 </label>
270
328
@@ -463,43 +521,15 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
463 </div>
521 </div>
464 </%def>
522 </%def>
465
523
466 <%def name="diff_ops(filediff)">
524 <%def name="diff_ops(file_name, filediff)">
467 <%
525 <%
468 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
526 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
469 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
527 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
470 %>
528 %>
471 <span class="pill">
529 <span class="pill">
472 <i class="icon-file-text"></i>
530 <i class="icon-file-text"></i>
473 %if filediff.source_file_path and filediff.target_file_path:
531 ${file_name}
474 %if filediff.source_file_path != filediff.target_file_path:
475 ## file was renamed, or copied
476 %if RENAMED_FILENODE in filediff.patch['stats']['ops']:
477 ${filediff.target_file_path} β¬… <del>${filediff.source_file_path}</del>
478 <% final_path = filediff.target_file_path %>
479 %elif COPIED_FILENODE in filediff.patch['stats']['ops']:
480 ${filediff.target_file_path} β¬… ${filediff.source_file_path}
481 <% final_path = filediff.target_file_path %>
482 %endif
483 %else:
484 ## file was modified
485 ${filediff.source_file_path}
486 <% final_path = filediff.source_file_path %>
487 %endif
488 %else:
489 %if filediff.source_file_path:
490 ## file was deleted
491 ${filediff.source_file_path}
492 <% final_path = filediff.source_file_path %>
493 %else:
494 ## file was added
495 ${filediff.target_file_path}
496 <% final_path = filediff.target_file_path %>
497 %endif
498 %endif
499 <i style="color: #aaa" class="on-hover-icon icon-clipboard clipboard-action" data-clipboard-text="${final_path}" title="${_('Copy file path')}" onclick="return false;"></i>
500 </span>
532 </span>
501 ## anchor link
502 <a class="pill filediff-anchor" href="#a_${h.FID(filediff.raw_id, filediff.patch['filename'])}">ΒΆ</a>
503
533
504 <span class="pill-group pull-right">
534 <span class="pill-group pull-right">
505
535
@@ -53,6 +53,14 b" var data_hovercard_url = pyroutes.url('h"
53 var reviewGroup = null;
53 var reviewGroup = null;
54 var reviewGroupColor = 'transparent';
54 var reviewGroupColor = 'transparent';
55 }
55 }
56 var rule_show = rule_show || false;
57
58 if (rule_show) {
59 var rule_visibility = 'table-cell';
60 } else {
61 var rule_visibility = 'none';
62 }
63
56 %>
64 %>
57
65
58 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
66 <tr id="reviewer_<%= member.user_id %>" class="reviewer_entry" tooltip="Review Group" data-reviewer-user-id="<%= member.user_id %>">
@@ -98,9 +106,9 b" var data_hovercard_url = pyroutes.url('h"
98 </td>
106 </td>
99
107
100 <% } else { %>
108 <% } else { %>
101 <td>
109 <td style="text-align: right;width: 10px;">
102 <% if (allowed_to_update) { %>
110 <% if (allowed_to_update) { %>
103 <div class="reviewer_member_remove action_button" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
111 <div class="reviewer_member_remove" onclick="reviewersController.removeReviewMember(<%= member.user_id %>, true)" style="visibility: <%= edit_visibility %>;">
104 <i class="icon-remove"></i>
112 <i class="icon-remove"></i>
105 </div>
113 </div>
106 <% } %>
114 <% } %>
@@ -110,7 +118,7 b" var data_hovercard_url = pyroutes.url('h"
110 </tr>
118 </tr>
111
119
112 <tr>
120 <tr>
113 <td colspan="4" style="display: none" class="pr-user-rule-container">
121 <td colspan="4" style="display: <%= rule_visibility %>" class="pr-user-rule-container">
114 <input type="hidden" name="__start__" value="reviewer:mapping">
122 <input type="hidden" name="__start__" value="reviewer:mapping">
115
123
116 <%if (member.user_group && member.user_group.vote_rule) { %>
124 <%if (member.user_group && member.user_group.vote_rule) { %>
@@ -19,21 +19,74 b''
19 <div class="box">
19 <div class="box">
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
20 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
21
21
22 <div class="box pr-summary">
22 <div class="box">
23
23
24 <div class="summary-details block-left">
24 <div class="summary-details block-left">
25
25
26
27 <div class="pr-details-title">
28 ${_('New pull request')}
29 </div>
30
31 <div class="form" style="padding-top: 10px">
26 <div class="form" style="padding-top: 10px">
32 <!-- fields -->
33
27
34 <div class="fields" >
28 <div class="fields" >
35
29
36 <div class="field">
30 ## COMMIT FLOW
31 <div class="field">
32 <div class="label label-textarea">
33 <label for="commit_flow">${_('Commit flow')}:</label>
34 </div>
35
36 <div class="content">
37 <div class="flex-container">
38 <div style="width: 45%;">
39 <div class="panel panel-default source-panel">
40 <div class="panel-heading">
41 <h3 class="panel-title">${_('Source repository')}</h3>
42 </div>
43 <div class="panel-body">
44 <div style="display:none">${c.rhodecode_db_repo.description}</div>
45 ${h.hidden('source_repo')}
46 ${h.hidden('source_ref')}
47
48 <div id="pr_open_message"></div>
49 </div>
50 </div>
51 </div>
52
53 <div style="width: 90px; text-align: center; padding-top: 30px">
54 <div>
55 <i class="icon-right" style="font-size: 2.2em"></i>
56 </div>
57 <div style="position: relative; top: 10px">
58 <span class="tag tag">
59 <span id="switch_base"></span>
60 </span>
61 </div>
62
63 </div>
64
65 <div style="width: 45%;">
66
67 <div class="panel panel-default target-panel">
68 <div class="panel-heading">
69 <h3 class="panel-title">${_('Target repository')}</h3>
70 </div>
71 <div class="panel-body">
72 <div style="display:none" id="target_repo_desc"></div>
73 ${h.hidden('target_repo')}
74 ${h.hidden('target_ref')}
75 <span id="target_ref_loading" style="display: none">
76 ${_('Loading refs...')}
77 </span>
78 </div>
79 </div>
80
81 </div>
82 </div>
83
84 </div>
85
86 </div>
87
88 ## TITLE
89 <div class="field">
37 <div class="label">
90 <div class="label">
38 <label for="pullrequest_title">${_('Title')}:</label>
91 <label for="pullrequest_title">${_('Title')}:</label>
39 </div>
92 </div>
@@ -43,8 +96,9 b''
43 <p class="help-block">
96 <p class="help-block">
44 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
45 </p>
98 </p>
46 </div>
99 </div>
47
100
101 ## DESC
48 <div class="field">
102 <div class="field">
49 <div class="label label-textarea">
103 <div class="label label-textarea">
50 <label for="pullrequest_desc">${_('Description')}:</label>
104 <label for="pullrequest_desc">${_('Description')}:</label>
@@ -55,39 +109,49 b''
55 </div>
109 </div>
56 </div>
110 </div>
57
111
112 ## REVIEWERS
58 <div class="field">
113 <div class="field">
59 <div class="label label-textarea">
114 <div class="label label-textarea">
60 <label for="commit_flow">${_('Commit flow')}:</label>
115 <label for="pullrequest_reviewers">${_('Reviewers')}:</label>
61 </div>
62
63 ## TODO: johbo: Abusing the "content" class here to get the
64 ## desired effect. Should be replaced by a proper solution.
65
66 ##ORG
67 <div class="content">
68 <strong>${_('Source repository')}:</strong>
69 ${c.rhodecode_db_repo.description}
70 </div>
116 </div>
71 <div class="content">
117 <div class="content">
72 ${h.hidden('source_repo')}
118 ## REVIEW RULES
73 ${h.hidden('source_ref')}
119 <div id="review_rules" style="display: none" class="reviewers-title">
74 </div>
120 <div class="pr-details-title">
121 ${_('Reviewer rules')}
122 </div>
123 <div class="pr-reviewer-rules">
124 ## review rules will be appended here, by default reviewers logic
125 </div>
126 </div>
75
127
76 ##OTHER, most Probably the PARENT OF THIS FORK
128 ## REVIEWERS
77 <div class="content">
129 <div class="reviewers-title">
78 ## filled with JS
130 <div class="pr-details-title">
79 <div id="target_repo_desc"></div>
131 ${_('Pull request reviewers')}
80 </div>
132 <span class="calculate-reviewers"> - ${_('loading...')}</span>
133 </div>
134 </div>
135 <div id="reviewers" class="pr-details-content reviewers">
136 ## members goes here, filled via JS based on initial selection !
137 <input type="hidden" name="__start__" value="review_members:sequence">
138 <table id="review_members" class="group_members">
139 ## This content is loaded via JS and ReviewersPanel
140 </table>
141 <input type="hidden" name="__end__" value="review_members:sequence">
81
142
82 <div class="content">
143 <div id="add_reviewer_input" class='ac'>
83 ${h.hidden('target_repo')}
144 <div class="reviewer_ac">
84 ${h.hidden('target_ref')}
145 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
85 <span id="target_ref_loading" style="display: none">
146 <div id="reviewers_container"></div>
86 ${_('Loading refs...')}
147 </div>
87 </span>
148 </div>
149
150 </div>
88 </div>
151 </div>
89 </div>
152 </div>
90
153
154 ## SUBMIT
91 <div class="field">
155 <div class="field">
92 <div class="label label-textarea">
156 <div class="label label-textarea">
93 <label for="pullrequest_submit"></label>
157 <label for="pullrequest_submit"></label>
@@ -96,66 +160,14 b''
96 <div class="pr-submit-button">
160 <div class="pr-submit-button">
97 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
161 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
98 </div>
162 </div>
99 <div id="pr_open_message"></div>
100 </div>
163 </div>
101 </div>
164 </div>
102
103 <div class="pr-spacing-container"></div>
104 </div>
105 </div>
106 </div>
107 <div>
108 ## AUTHOR
109 <div class="reviewers-title block-right">
110 <div class="pr-details-title">
111 ${_('Author of this pull request')}
112 </div>
113 </div>
114 <div class="block-right pr-details-content reviewers">
115 <ul class="group_members">
116 <li>
117 ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)}
118 </li>
119 </ul>
120 </div>
121
122 ## REVIEW RULES
123 <div id="review_rules" style="display: none" class="reviewers-title block-right">
124 <div class="pr-details-title">
125 ${_('Reviewer rules')}
126 </div>
127 <div class="pr-reviewer-rules">
128 ## review rules will be appended here, by default reviewers logic
129 </div>
130 </div>
131
132 ## REVIEWERS
133 <div class="reviewers-title block-right">
134 <div class="pr-details-title">
135 ${_('Pull request reviewers')}
136 <span class="calculate-reviewers"> - ${_('loading...')}</span>
137 </div>
138 </div>
139 <div id="reviewers" class="block-right pr-details-content reviewers">
140 ## members goes here, filled via JS based on initial selection !
141 <input type="hidden" name="__start__" value="review_members:sequence">
142 <ul id="review_members" class="group_members"></ul>
143 <input type="hidden" name="__end__" value="review_members:sequence">
144 <div id="add_reviewer_input" class='ac'>
145 <div class="reviewer_ac">
146 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
147 <div id="reviewers_container"></div>
148 </div>
149 </div>
165 </div>
150 </div>
166 </div>
151 </div>
167 </div>
168
152 </div>
169 </div>
153 <div class="box">
170
154 <div>
155 ## overview pulled by ajax
156 <div id="pull_request_overview"></div>
157 </div>
158 </div>
159 ${h.end_form()}
171 ${h.end_form()}
160 </div>
172 </div>
161
173
@@ -243,8 +255,6 b''
243
255
244 var diffDataHandler = function(data) {
256 var diffDataHandler = function(data) {
245
257
246 $('#pull_request_overview').html(data);
247
248 var commitElements = data['commits'];
258 var commitElements = data['commits'];
249 var files = data['files'];
259 var files = data['files'];
250 var added = data['stats'][0]
260 var added = data['stats'][0]
@@ -303,27 +313,33 b''
303
313
304 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
314 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
305 msg += _ngettext(
315 msg += _ngettext(
306 'This pull requests will consist of <strong>{0} commit</strong>.',
316 'Compare summary: <strong>{0} commit</strong>',
307 'This pull requests will consist of <strong>{0} commits</strong>.',
317 'Compare summary: <strong>{0} commits</strong>',
308 commitElements.length).format(commitElements.length)
318 commitElements.length).format(commitElements.length)
309
319
310 msg += '\n';
320 msg += '';
311 msg += _ngettext(
321 msg += _ngettext(
312 '<strong>{0} file</strong> changed, ',
322 '<strong>, and {0} file</strong> changed.',
313 '<strong>{0} files</strong> changed, ',
323 '<strong>, and {0} files</strong> changed.',
314 files.length).format(files.length)
324 files.length).format(files.length)
315 msg += '<span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted</span>.'.format(added, deleted)
316
325
317 msg += '\n\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
326 msg += '\n Diff: <span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted </span>.'.format(added, deleted)
327
328 msg += '\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
318
329
319 if (commitElements.length) {
330 if (commitElements.length) {
320 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
331 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
321 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
332 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
322 }
333 }
323 else {
334 else {
324 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
335 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
336 _gettext('There are no commits to merge.'));
337 prButtonLock(true, noCommitsMsg, 'compare');
325 }
338 }
326
339
340 //make both panels equal
341 $('.target-panel').height($('.source-panel').height())
342
327 };
343 };
328
344
329 reviewersController = new ReviewersController();
345 reviewersController = new ReviewersController();
@@ -429,10 +445,12 b''
429
445
430 var targetRepoChanged = function(repoData) {
446 var targetRepoChanged = function(repoData) {
431 // generate new DESC of target repo displayed next to select
447 // generate new DESC of target repo displayed next to select
448
449 $('#target_repo_desc').html(repoData['description']);
450
432 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
451 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
433 $('#target_repo_desc').html(
452 var title = _gettext('Switch target repository with the source.')
434 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
453 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
435 );
436
454
437 // generate dynamic select2 for refs.
455 // generate dynamic select2 for refs.
438 initTargetRefs(repoData['refs']['select2_refs'],
456 initTargetRefs(repoData['refs']['select2_refs'],
This diff has been collapsed as it changes many lines, (821 lines changed) Show them Hide them
@@ -1,6 +1,8 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
4
6
5 <%def name="title()">
7 <%def name="title()">
6 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
@@ -21,113 +23,6 b''
21 ${self.repo_menu(active='showpullrequest')}
23 ${self.repo_menu(active='showpullrequest')}
22 </%def>
24 </%def>
23
25
24 <%def name="comments_table(comments, counter_num, todo_comments=False)">
25 <%
26 old_comments = False
27 if todo_comments:
28 cls_ = 'todos-content-table'
29 def sorter(entry):
30 user_id = entry.author.user_id
31 resolved = '1' if entry.resolved else '0'
32 if user_id == c.rhodecode_user.user_id:
33 # own comments first
34 user_id = 0
35 return '{}'.format(str(entry.comment_id).zfill(10000))
36 else:
37 cls_ = 'comments-content-table'
38 def sorter(entry):
39 user_id = entry.author.user_id
40 return '{}'.format(str(entry.comment_id).zfill(10000))
41
42
43
44 %>
45 <table class="todo-table ${cls_}" data-total-count="${len(comments)}" data-counter="${counter_num}">
46
47 % for loop_obj, comment_obj in h.looper(reversed(sorted(comments, key=sorter))):
48 <%
49 display = ''
50 _cls = ''
51 %>
52 <% comment_ver_index = comment_obj.get_index_version(getattr(c, 'versions', [])) %>
53 <%
54 prev_comment_ver_index = 0
55 if loop_obj.previous:
56 prev_comment_ver_index = loop_obj.previous.get_index_version(getattr(c, 'versions', []))
57 %>
58 <% hidden_at_ver = comment_obj.outdated_at_version_js(c.at_version_num) %>
59 <% is_from_old_ver = comment_obj.older_than_version_js(c.at_version_num) %>
60 <%
61 if (prev_comment_ver_index > comment_ver_index) and old_comments is False:
62 old_comments = True
63 %>
64 % if todo_comments:
65 % if comment_obj.resolved:
66 <% _cls = 'resolved-todo' %>
67 <% display = 'none' %>
68 % endif
69 % else:
70 ## SKIP TODOs we display them in other area
71 % if comment_obj.is_todo:
72 <% display = 'none' %>
73 % endif
74 ## Skip outdated comments
75 % if comment_obj.outdated:
76 <% display = 'none' %>
77 <% _cls = 'hidden-comment' %>
78 % endif
79 % endif
80
81 % if not todo_comments and old_comments:
82 <tr class="old-comments-marker">
83 <td colspan="3"> <code>comments from older versions</code> </td>
84 </tr>
85 ## reset markers so we only show this marker once
86 <% old_comments = None %>
87 % endif
88
89 <tr class="${_cls}" style="display: ${display};">
90 <td class="td-todo-number">
91
92 <a class="${('todo-resolved' if comment_obj.resolved else '')} permalink"
93 href="#comment-${comment_obj.comment_id}"
94 onclick="return Rhodecode.comments.scrollToComment($('#comment-${comment_obj.comment_id}'), 0, ${hidden_at_ver})">
95
96 % if todo_comments:
97 % if comment_obj.is_inline:
98 <i class="tooltip icon-code" title="Inline TODO comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
99 % else:
100 <i class="tooltip icon-comment" title="General TODO comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
101 % endif
102 % else:
103 % if comment_obj.outdated:
104 <i class="tooltip icon-comment-toggle" title="Inline Outdated made in v${comment_ver_index}."></i>
105 % elif comment_obj.is_inline:
106 <i class="tooltip icon-code" title="Inline comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
107 % else:
108 <i class="tooltip icon-comment" title="General comment ${('made in older version (v{})'.format(comment_ver_index) if is_from_old_ver == 'true' else 'made in this version')}."></i>
109 % endif
110 % endif
111
112 #${comment_obj.comment_id}
113 </a>
114 </td>
115
116 <td class="td-todo-gravatar">
117 ${base.gravatar(comment_obj.author.email, 16, user=comment_obj.author, tooltip=True, extra_class=['no-margin'])}
118 </td>
119 <td class="todo-comment-text-wrapper">
120 <div class="tooltip todo-comment-text timeago" title="${h.format_date(comment_obj.created_on)}" datetime="${comment_obj.created_on}${h.get_timezone(comment_obj.created_on, time_is_local=True)}">
121 <code>${h.chop_at_smart(comment_obj.text, '\n', suffix_if_chopped='...')}</code>
122 </div>
123 </td>
124 </tr>
125 % endfor
126
127 </table>
128
129 </%def>
130
131
26
132 <%def name="main()">
27 <%def name="main()">
133 ## Container to gather extracted Tickets
28 ## Container to gather extracted Tickets
@@ -140,6 +35,7 b''
140 // TODO: marcink switch this to pyroutes
35 // TODO: marcink switch this to pyroutes
141 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__')}";
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__')}";
142 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
38 templateContext.pull_request_data.pull_request_version = '${request.GET.get('version', '')}';
143 </script>
39 </script>
144
40
145 <div class="box">
41 <div class="box">
@@ -226,7 +122,7 b''
226
122
227 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
123 ${_('of')} <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.repo_name}</a>
228
124
229 <a class="source-details-action" href="#expand-source-details" onclick="return versionController.toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
125 <a class="source-details-action" href="#expand-source-details" onclick="return toggleElement(this, '.source-details')" data-toggle-on='<i class="icon-angle-down">more details</i>' data-toggle-off='<i class="icon-angle-up">less details</i>'>
230 <i class="icon-angle-down">more details</i>
126 <i class="icon-angle-down">more details</i>
231 </a>
127 </a>
232
128
@@ -643,101 +539,12 b''
643 </div>
539 </div>
644
540
645
541
646 ### NAVBOG RIGHT
542 ### NAV SIDEBAR
647 <style>
648
649 .right-sidebar {
650 position: fixed;
651 top: 0px;
652 bottom: 0;
653 right: 0;
654
655 background: #fafafa;
656 z-index: 50;
657 }
658
659 .right-sidebar {
660 border-left: 1px solid #dbdbdb;
661 }
662
663 .right-sidebar.right-sidebar-expanded {
664 width: 320px;
665 overflow: scroll;
666 }
667
668 .right-sidebar.right-sidebar-collapsed {
669 width: 50px;
670 padding: 0;
671 display: block;
672 overflow: hidden;
673 }
674
675 .sidenav {
676 float: right;
677 will-change: min-height;
678 background: #fafafa;
679 width: 100%;
680 padding-top: 50px;
681 }
682
683 .sidebar-toggle {
684 height: 30px;
685 text-align: center;
686 margin: 15px 0px 0 0;
687 }
688 .sidebar-toggle a {
689
690 }
691
692 .sidebar-content {
693 margin-left: 15px;
694 margin-right: 15px;
695 }
696
697 .sidebar-heading {
698 font-size: 1.2em;
699 font-weight: 700;
700 margin-top: 10px;
701 }
702
703 .sidebar-element {
704 margin-top: 20px;
705 }
706 .right-sidebar-collapsed-state {
707 display: flex;
708 flex-direction: column;
709 justify-content: center;
710 align-items: center;
711 padding: 0 10px;
712 cursor: pointer;
713 font-size: 1.3em;
714 margin: 0 -15px;
715 }
716
717 .right-sidebar-collapsed-state:hover {
718 background-color: #dbd9da;
719 }
720
721 .old-comments-marker {
722 text-align: center;
723 }
724
725 .old-comments-marker td {
726 padding-top: 15px;
727 border-bottom: 1px solid #dbd9da;
728 }
729
730 #add_reviewer {
731 padding-top: 10px;
732 }
733
734 </style>
735
736 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
737 <div class="sidenav navbar__inner" >
544 <div class="sidenav navbar__inner" >
738 ## TOGGLE
545 ## TOGGLE
739 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
740 <a href="#toggleSidebar">
547 <a href="#toggleSidebar" class="grey-link-action">
741
548
742 </a>
549 </a>
743 </div>
550 </div>
@@ -747,8 +554,12 b''
747
554
748 ## RULES SUMMARY/RULES
555 ## RULES SUMMARY/RULES
749 <div class="sidebar-element clear-both">
556 <div class="sidebar-element clear-both">
557 <% vote_title = _ungettext(
558 'Status calculated based on votes from {} reviewer',
559 'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
560 %>
750
561
751 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Reviewers')}">
562 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
752 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
753 ${len(c.allowed_reviewers)}
564 ${len(c.allowed_reviewers)}
754 </div>
565 </div>
@@ -769,7 +580,7 b''
769
580
770 ## REVIEWERS
581 ## REVIEWERS
771 <div class="right-sidebar-expanded-state pr-details-title">
582 <div class="right-sidebar-expanded-state pr-details-title">
772 <span class="tooltip sidebar-heading" title="${_ungettext('Review status calculated based on {} reviewer vote', 'Review status calculated based on {} reviewers votes', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))}">
583 <span class="tooltip sidebar-heading" title="${vote_title}">
773 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
774 ${_('Reviewers')}
585 ${_('Reviewers')}
775 </span>
586 </span>
@@ -846,7 +657,7 b''
846
657
847 % if not c.at_version:
658 % if not c.at_version:
848 % if c.resolved_comments:
659 % if c.resolved_comments:
849 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return versionController.toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
660 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
850 % else:
661 % else:
851 <span class="block-right last-item noselect">Show resolved</span>
662 <span class="block-right last-item noselect">Show resolved</span>
852 % endif
663 % endif
@@ -863,7 +674,7 b''
863 </table>
674 </table>
864 % else:
675 % else:
865 % if c.unresolved_comments + c.resolved_comments:
676 % if c.unresolved_comments + c.resolved_comments:
866 ${comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
677 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True)}
867 % else:
678 % else:
868 <table>
679 <table>
869 <tr>
680 <tr>
@@ -882,6 +693,8 b''
882 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
693 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
883 <i class="icon-comment" style="color: #949494"></i>
694 <i class="icon-comment" style="color: #949494"></i>
884 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
695 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
696 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
697 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
885 </div>
698 </div>
886
699
887 <div class="right-sidebar-expanded-state pr-details-title">
700 <div class="right-sidebar-expanded-state pr-details-title">
@@ -903,7 +716,7 b''
903 </span>
716 </span>
904
717
905 % if outdated_comm_count_ver:
718 % if outdated_comm_count_ver:
906 <span class="block-right action_button last-item noselect" onclick="return versionController.toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
719 <span class="block-right action_button last-item noselect" onclick="return toggleElement(this, '.hidden-comment');" data-toggle-on="Show outdated" data-toggle-off="Hide outdated">Show outdated</span>
907 % else:
720 % else:
908 <span class="block-right last-item noselect">Show hidden</span>
721 <span class="block-right last-item noselect">Show hidden</span>
909 % endif
722 % endif
@@ -912,7 +725,7 b''
912
725
913 <div class="right-sidebar-expanded-state pr-details-content">
726 <div class="right-sidebar-expanded-state pr-details-content">
914 % if c.inline_comments_flat + c.comments:
727 % if c.inline_comments_flat + c.comments:
915 ${comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
728 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments))}
916 % else:
729 % else:
917 <table>
730 <table>
918 <tr>
731 <tr>
@@ -942,7 +755,7 b''
942 <div class="right-sidebar-expanded-state pr-details-content">
755 <div class="right-sidebar-expanded-state pr-details-content">
943 <table>
756 <table>
944
757
945 <tr><td><code>${_('Pull Request Description')}</code></td></tr>
758 <tr><td><code>${_('In pull request description')}:</code></td></tr>
946 % if c.referenced_desc_issues:
759 % if c.referenced_desc_issues:
947 % for ticket_dict in c.referenced_desc_issues:
760 % for ticket_dict in c.referenced_desc_issues:
948 <tr>
761 <tr>
@@ -961,7 +774,7 b''
961 </tr>
774 </tr>
962 % endif
775 % endif
963
776
964 <tr><td style="padding-top: 10px"><code>${_('Commit Messages')}</code></td></tr>
777 <tr><td style="padding-top: 10px"><code>${_('In commit messages')}:</code></td></tr>
965 % if c.referenced_commit_issues:
778 % if c.referenced_commit_issues:
966 % for ticket_dict in c.referenced_commit_issues:
779 % for ticket_dict in c.referenced_commit_issues:
967 <tr>
780 <tr>
@@ -992,451 +805,189 b''
992 ## This JS needs to be at the end
805 ## This JS needs to be at the end
993 <script type="text/javascript">
806 <script type="text/javascript">
994
807
995 versionController = new VersionController();
808 versionController = new VersionController();
996 versionController.init();
809 versionController.init();
997
810
998 reviewersController = new ReviewersController();
811 reviewersController = new ReviewersController();
999 commitsController = new CommitsController();
812 commitsController = new CommitsController();
1000
813
1001 updateController = new UpdatePrController();
814 updateController = new UpdatePrController();
1002
815
1003 /** leak object to top level scope **/
816 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
1004 window.PullRequestPresenceController;
817 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
1005
818
1006 (function () {
819 (function () {
1007 "use strict";
820 "use strict";
1008
821
1009 window.PullRequestPresenceController = function (channel) {
822 // custom code mirror
1010 var self = this;
823 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1011 this.channel = channel;
824
1012 this.users = {};
825 var PRDetails = {
826 editButton: $('#open_edit_pullrequest'),
827 closeButton: $('#close_edit_pullrequest'),
828 deleteButton: $('#delete_pullrequest'),
829 viewFields: $('#pr-desc, #pr-title'),
830 editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
831
832 init: function () {
833 var that = this;
834 this.editButton.on('click', function (e) {
835 that.edit();
836 });
837 this.closeButton.on('click', function (e) {
838 that.view();
839 });
840 },
841
842 edit: function (event) {
843 var cmInstance = $('#pr-description-input').get(0).MarkupForm.cm;
844 this.viewFields.hide();
845 this.editButton.hide();
846 this.deleteButton.hide();
847 this.closeButton.show();
848 this.editFields.show();
849 cmInstance.refresh();
850 },
851
852 view: function (event) {
853 this.editButton.show();
854 this.deleteButton.show();
855 this.editFields.hide();
856 this.closeButton.hide();
857 this.viewFields.show();
858 }
859 };
860
861 PRDetails.init();
862 ReviewersPanel.init(reviewerRulesData, setReviewersData);
863
864 window.showOutdated = function (self) {
865 $('.comment-inline.comment-outdated').show();
866 $('.filediff-outdated').show();
867 $('.showOutdatedComments').hide();
868 $('.hideOutdatedComments').show();
869 };
870
871 window.hideOutdated = function (self) {
872 $('.comment-inline.comment-outdated').hide();
873 $('.filediff-outdated').hide();
874 $('.hideOutdatedComments').hide();
875 $('.showOutdatedComments').show();
876 };
877
878 window.refreshMergeChecks = function () {
879 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
880 $('.pull-request-merge').css('opacity', 0.3);
881 $('.action-buttons-extra').css('opacity', 0.3);
882
883 $('.pull-request-merge').load(
884 loadUrl, function () {
885 $('.pull-request-merge').css('opacity', 1);
886
887 $('.action-buttons-extra').css('opacity', 1);
888 }
889 );
890 };
1013
891
1014 this.storeUsers = function (users) {
892 window.closePullRequest = function (status) {
1015 self.users = {}
893 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
1016 $.each(users, function(index, value) {
894 return false;
1017 var userId = value.state.id;
1018 self.users[userId] = value.state;
1019 })
1020 }
895 }
896 // inject closing flag
897 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
898 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
899 $(generalCommentForm.submitForm).submit();
900 };
901
902 //TODO this functionality is now missing
903 $('#show-outdated-comments').on('click', function (e) {
904 var button = $(this);
905 var outdated = $('.comment-outdated');
906
907 if (button.html() === "(Show)") {
908 button.html("(Hide)");
909 outdated.show();
910 } else {
911 button.html("(Show)");
912 outdated.hide();
913 }
914 });
915
916 $('#merge_pull_request_form').submit(function () {
917 if (!$('#merge_pull_request').attr('disabled')) {
918 $('#merge_pull_request').attr('disabled', 'disabled');
919 }
920 return true;
921 });
1021
922
1022 this.render = function () {
923 $('#edit_pull_request').on('click', function (e) {
1023 $.each($('.reviewer_entry'), function(index, value) {
924 var title = $('#pr-title-input').val();
1024 var userData = $(value).data();
925 var description = codeMirrorInstance.getValue();
1025 if(self.users[userData.reviewerUserId] !== undefined){
926 var renderer = $('#pr-renderer-input').val();
1026 $(value).find('.presence-state').show();
927 editPullRequest(
1027 } else {
928 "${c.repo_name}", "${c.pull_request.pull_request_id}",
1028 $(value).find('.presence-state').hide();
929 title, description, renderer);
1029 }
930 });
1030 })
931
932 $('#update_pull_request').on('click', function (e) {
933 $(this).attr('disabled', 'disabled');
934 $(this).addClass('disabled');
935 $(this).html(_gettext('Saving...'));
936 reviewersController.updateReviewers(
937 "${c.repo_name}", "${c.pull_request.pull_request_id}");
938 });
939
940 // fixing issue with caches on firefox
941 $('#update_commits').removeAttr("disabled");
942
943 $('.show-inline-comments').on('click', function (e) {
944 var boxid = $(this).attr('data-comment-id');
945 var button = $(this);
946
947 if (button.hasClass("comments-visible")) {
948 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
949 $(this).hide();
950 });
951 button.removeClass("comments-visible");
952 } else {
953 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
954 $(this).show();
955 });
956 button.addClass("comments-visible");
957 }
958 });
959
960 $('.show-inline-comments').on('change', function (e) {
961 var show = 'none';
962 var target = e.currentTarget;
963 if (target.checked) {
964 show = ''
965 }
966 var boxid = $(target).attr('id_for');
967 var comments = $('#{0} .inline-comments'.format(boxid));
968 var fn_display = function (idx) {
969 $(this).css('display', show);
1031 };
970 };
1032
971 $(comments).each(fn_display);
1033 this.handlePresence = function (data) {
972 var btns = $('#{0} .inline-comments-button'.format(boxid));
1034
973 $(btns).each(fn_display);
1035 if (data.type == 'presence' && data.channel === self.channel) {
974 });
1036 this.storeUsers(data.users);
1037 this.render()
1038 }
1039 };
1040
1041 this.handleChannelUpdate = function (data) {
1042
975
1043 if (data.channel === this.channel) {
976 // register submit callback on commentForm form to track TODOs
1044 this.storeUsers(data.state.users);
977 window.commentFormGlobalSubmitSuccessCallback = function () {
1045 this.render()
978 refreshMergeChecks();
1046 }
979 };
1047
980
1048 };
981 ReviewerAutoComplete('#user');
1049
1050 /* subscribe our chat to topics that are interesting to it */
1051 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
1052 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
1053 };
1054
982
1055 })();
983 })();
1056
984
1057
985 $(document).ready(function () {
1058 $(function () {
1059
1060 // custom code mirror
1061 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
1062
1063 var PRDetails = {
1064 editButton: $('#open_edit_pullrequest'),
1065 closeButton: $('#close_edit_pullrequest'),
1066 deleteButton: $('#delete_pullrequest'),
1067 viewFields: $('#pr-desc, #pr-title'),
1068 editFields: $('#pr-desc-edit, #pr-title-edit, .pr-save'),
1069
1070 init: function () {
1071 var that = this;
1072 this.editButton.on('click', function (e) {
1073 that.edit();
1074 });
1075 this.closeButton.on('click', function (e) {
1076 that.view();
1077 });
1078 },
1079
1080 edit: function (event) {
1081 this.viewFields.hide();
1082 this.editButton.hide();
1083 this.deleteButton.hide();
1084 this.closeButton.show();
1085 this.editFields.show();
1086 codeMirrorInstance.refresh();
1087 },
1088
1089 view: function (event) {
1090 this.editButton.show();
1091 this.deleteButton.show();
1092 this.editFields.hide();
1093 this.closeButton.hide();
1094 this.viewFields.show();
1095 }
1096 };
1097
1098 var ReviewersPanel = {
1099 editButton: $('#open_edit_reviewers'),
1100 closeButton: $('#close_edit_reviewers'),
1101 addButton: $('#add_reviewer'),
1102 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
1103 reviewRules: ${c.pull_request_default_reviewers_data_json | n},
1104 setReviewers: ${c.pull_request_set_reviewers_data_json | n},
1105
1106 init: function () {
1107 var self = this;
1108 this.editButton.on('click', function (e) {
1109 self.edit();
1110 });
1111 this.closeButton.on('click', function (e) {
1112 self.close();
1113 self.renderReviewers();
1114 });
1115
1116 self.renderReviewers();
1117
1118 },
1119
1120 renderReviewers: function () {
1121
1122 $('#review_members').html('')
1123 $.each(this.setReviewers.reviewers, function (key, val) {
1124 var member = val;
1125
1126 var entry = renderTemplate('reviewMemberEntry', {
1127 'member': member,
1128 'mandatory': member.mandatory,
1129 'reasons': member.reasons,
1130 'allowed_to_update': member.allowed_to_update,
1131 'review_status': member.review_status,
1132 'review_status_label': member.review_status_label,
1133 'user_group': member.user_group,
1134 'create': false
1135 });
1136
1137 $('#review_members').append(entry)
1138 });
1139 tooltipActivate();
1140
1141 },
1142
1143 edit: function (event) {
1144 this.editButton.hide();
1145 this.closeButton.show();
1146 this.addButton.show();
1147 $(this.removeButtons.selector).css('visibility', 'visible');
1148 // review rules
1149 reviewersController.loadReviewRules(this.reviewRules);
1150 },
1151
1152 close: function (event) {
1153 this.editButton.show();
1154 this.closeButton.hide();
1155 this.addButton.hide();
1156 $(this.removeButtons.selector).css('visibility', 'hidden');
1157 // hide review rules
1158 reviewersController.hideReviewRules()
1159 }
1160 };
1161
1162 PRDetails.init();
1163 ReviewersPanel.init();
1164
1165 showOutdated = function (self) {
1166 $('.comment-inline.comment-outdated').show();
1167 $('.filediff-outdated').show();
1168 $('.showOutdatedComments').hide();
1169 $('.hideOutdatedComments').show();
1170 };
1171
1172 hideOutdated = function (self) {
1173 $('.comment-inline.comment-outdated').hide();
1174 $('.filediff-outdated').hide();
1175 $('.hideOutdatedComments').hide();
1176 $('.showOutdatedComments').show();
1177 };
1178
1179 refreshMergeChecks = function () {
1180 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
1181 $('.pull-request-merge').css('opacity', 0.3);
1182 $('.action-buttons-extra').css('opacity', 0.3);
1183
1184 $('.pull-request-merge').load(
1185 loadUrl, function () {
1186 $('.pull-request-merge').css('opacity', 1);
1187
1188 $('.action-buttons-extra').css('opacity', 1);
1189 }
1190 );
1191 };
1192
1193 refreshComments = function () {
1194 var params = {
1195 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1196 'repo_name': templateContext.repo_name,
1197 'version': '${request.GET.get('version', '')}',
1198 };
1199 var data = {"comments[]": ["1"]};
1200 var loadUrl = pyroutes.url('pullrequest_comments', params);
1201 var $targetElem = $('.comments-content-table');
1202 $targetElem.css('opacity', 0.3);
1203 $targetElem.load(
1204 loadUrl, data, function (responseText, textStatus, jqXHR) {
1205 if (jqXHR.status !== 200) {
1206 return false;
1207 }
1208 var $counterElem = $('#comments-count');
1209 var newCount = $(responseText).data('counter');
1210 if (newCount !== undefined) {
1211 var callback = function () {
1212 $counterElem.animate({'opacity': 1.00}, 200)
1213 $counterElem.html(newCount);
1214 };
1215 $counterElem.animate({'opacity': 0.15}, 200, callback);
1216 }
1217
1218
1219 $targetElem.css('opacity', 1);
1220 tooltipActivate();
1221 }
1222 );
1223 }
1224
986
1225 refreshTODOs = function () {
987 var channel = '${c.pr_broadcast_channel}';
1226 var params = {
988 new ReviewerPresenceController(channel)
1227 'pull_request_id': templateContext.pull_request_data.pull_request_id,
1228 'repo_name': templateContext.repo_name,
1229 'version': '${request.GET.get('version', '')}',
1230 };
1231 var data = {"comments[]": ["1"]};
1232 var loadUrl = pyroutes.url('pullrequest_todos', params);
1233 var $targetElem = $('.todos-content-table');
1234 $targetElem.css('opacity', 0.3);
1235 $targetElem.load(
1236 loadUrl, data, function (responseText, textStatus, jqXHR) {
1237 if (jqXHR.status !== 200) {
1238 return false;
1239 }
1240 var $counterElem = $('#todos-count')
1241 var newCount = $(responseText).data('counter');
1242 if (newCount !== undefined) {
1243 var callback = function () {
1244 $counterElem.animate({'opacity': 1.00}, 200)
1245 $counterElem.html(newCount);
1246 };
1247 $counterElem.animate({'opacity': 0.15}, 200, callback);
1248 }
1249
1250 $targetElem.css('opacity', 1);
1251 tooltipActivate();
1252 }
1253 );
1254
1255 }
1256
1257 refreshAllComments = function() {
1258 refreshComments();
1259 refreshTODOs();
1260 }
1261
1262 closePullRequest = function (status) {
1263 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
1264 return false;
1265 }
1266 // inject closing flag
1267 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
1268 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
1269 $(generalCommentForm.submitForm).submit();
1270 };
1271
1272 $('#show-outdated-comments').on('click', function (e) {
1273 var button = $(this);
1274 var outdated = $('.comment-outdated');
1275
1276 if (button.html() === "(Show)") {
1277 button.html("(Hide)");
1278 outdated.show();
1279 } else {
1280 button.html("(Show)");
1281 outdated.hide();
1282 }
1283 });
1284
1285 $('.show-inline-comments').on('change', function (e) {
1286 var show = 'none';
1287 var target = e.currentTarget;
1288 if (target.checked) {
1289 show = ''
1290 }
1291 var boxid = $(target).attr('id_for');
1292 var comments = $('#{0} .inline-comments'.format(boxid));
1293 var fn_display = function (idx) {
1294 $(this).css('display', show);
1295 };
1296 $(comments).each(fn_display);
1297 var btns = $('#{0} .inline-comments-button'.format(boxid));
1298 $(btns).each(fn_display);
1299 });
1300
1301 $('#merge_pull_request_form').submit(function () {
1302 if (!$('#merge_pull_request').attr('disabled')) {
1303 $('#merge_pull_request').attr('disabled', 'disabled');
1304 }
1305 return true;
1306 });
1307
1308 $('#edit_pull_request').on('click', function (e) {
1309 var title = $('#pr-title-input').val();
1310 var description = codeMirrorInstance.getValue();
1311 var renderer = $('#pr-renderer-input').val();
1312 editPullRequest(
1313 "${c.repo_name}", "${c.pull_request.pull_request_id}",
1314 title, description, renderer);
1315 });
1316
1317 $('#update_pull_request').on('click', function (e) {
1318 $(this).attr('disabled', 'disabled');
1319 $(this).addClass('disabled');
1320 $(this).html(_gettext('Saving...'));
1321 reviewersController.updateReviewers(
1322 "${c.repo_name}", "${c.pull_request.pull_request_id}");
1323 });
1324
1325
1326 // fixing issue with caches on firefox
1327 $('#update_commits').removeAttr("disabled");
1328
1329 $('.show-inline-comments').on('click', function (e) {
1330 var boxid = $(this).attr('data-comment-id');
1331 var button = $(this);
1332
989
1333 if (button.hasClass("comments-visible")) {
990 })
1334 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
991 </script>
1335 $(this).hide();
1336 });
1337 button.removeClass("comments-visible");
1338 } else {
1339 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
1340 $(this).show();
1341 });
1342 button.addClass("comments-visible");
1343 }
1344 });
1345
1346 // register submit callback on commentForm form to track TODOs
1347 window.commentFormGlobalSubmitSuccessCallback = function () {
1348 refreshMergeChecks();
1349 };
1350
1351 ReviewerAutoComplete('#user');
1352
1353 })
1354
1355 $(document).ready(function () {
1356
1357 var $sideBar = $('.right-sidebar');
1358 var marginExpVal = '320'
1359 var marginColVal = '50'
1360 var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
1361 var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
1362 var marginExpandedHeader = {'margin': '0 -{0}px 0 0'.format(marginExpVal), 'z-index': 10000};
1363 var marginCollapsedHeader = {'margin': '0 -{0}px 0 0'.format(marginColVal), 'z-index': 10000};
1364
1365 var updateStickyHeader = function() {
1366 if (window.updateSticky !== undefined) {
1367 // potentially our comments change the active window size, so we
1368 // notify sticky elements
1369 updateSticky()
1370 }
1371 }
1372
1373 var expandSidebar = function() {
1374 var $sideBar = $('.right-sidebar');
1375 $('.outerwrapper').css(marginExpanded);
1376 $('.header').css(marginExpandedHeader);
1377 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
1378 $('.right-sidebar-collapsed-state').hide();
1379 $('.right-sidebar-expanded-state').show();
1380
1381 $sideBar.addClass('right-sidebar-expanded')
1382 $sideBar.removeClass('right-sidebar-collapsed')
1383 }
1384
1385 var collapseSidebar = function() {
1386 var $sideBar = $('.right-sidebar');
1387 $('.outerwrapper').css(marginCollapsed);
1388 $('.header').css(marginCollapsedHeader);
1389 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
1390 $('.right-sidebar-collapsed-state').show();
1391 $('.right-sidebar-expanded-state').hide();
1392
1393 $sideBar.removeClass('right-sidebar-expanded')
1394 $sideBar.addClass('right-sidebar-collapsed')
1395 }
1396
1397 toggleSidebar = function () {
1398 var $sideBar = $('.right-sidebar');
1399
1400 if ($sideBar.hasClass('right-sidebar-expanded')) {
1401 // expanded -> collapsed transition
1402 collapseSidebar();
1403 var sidebarState = 'collapsed';
1404
1405 } else {
1406 // collapsed -> expanded
1407 expandSidebar();
1408 var sidebarState = 'expanded';
1409 }
1410
1411 // update our other sticky header in same context
1412 updateStickyHeader();
1413 storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
1414 }
1415
1416 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1417
1418 if (templateContext.session_attrs.sidebarState === 'expanded') {
1419 expanded = true
1420 } else if (templateContext.session_attrs.sidebarState === 'collapsed') {
1421 expanded = false
1422 }
1423
1424 // show sidebar since it's hidden on load
1425 $('.right-sidebar').show();
1426
1427 // init based on set initial class, or if defined user session attrs
1428 if (expanded) {
1429 expandSidebar();
1430 updateStickyHeader();
1431
1432 } else {
1433 collapseSidebar();
1434 updateStickyHeader();
1435 }
1436 var channel = '${c.pr_broadcast_channel}';
1437 new PullRequestPresenceController(channel)
1438
1439 })
1440 </script>
1441
992
1442 </%def>
993 </%def>
General Comments 0
You need to be logged in to leave comments. Login now