##// 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 487 def assert_comment_links(response, comments, inline_comments):
488 if comments == 1:
489 comments_text = "%d General" % comments
490 else:
491 comments_text = "%d General" % 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
488 response.mustcontain(
489 '<span class="display-none" id="general-comments-count">{}</span>'.format(comments))
490 response.mustcontain(
491 '<span class="display-none" id="inline-comments-count">{}</span>'.format(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:
504 response.mustcontain(
505 'id="inline-comments-counter">%s' % inline_comments_text)
506 else:
507 response.mustcontain(inline_comments_text)
494
@@ -18,8 +18,8 b''
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21
22 21 import logging
22 import collections
23 23
24 24 from pyramid.httpexceptions import (
25 25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
@@ -34,14 +34,14 b' from rhodecode.apps.file_store.exception'
34 34 from rhodecode.lib import diffs, codeblocks
35 35 from rhodecode.lib.auth import (
36 36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37
37 from rhodecode.lib.ext_json import json
38 38 from rhodecode.lib.compat import OrderedDict
39 39 from rhodecode.lib.diffs import (
40 40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 41 get_diff_whitespace_flag)
42 42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 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 45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 46 from rhodecode.lib.vcs.exceptions import (
47 47 RepositoryError, CommitDoesNotExistError)
@@ -87,7 +87,6 b' class RepoCommitsView(RepoAppView):'
87 87 diff_limit = c.visual.cut_off_limit_diff
88 88 file_limit = c.visual.cut_off_limit_file
89 89
90
91 90 # get ranges of commit ids if preset
92 91 commit_range = commit_id_range.split('...')[:2]
93 92
@@ -116,6 +115,7 b' class RepoCommitsView(RepoAppView):'
116 115 except Exception:
117 116 log.exception("General failure")
118 117 raise HTTPNotFound()
118 single_commit = len(c.commit_ranges) == 1
119 119
120 120 c.changes = OrderedDict()
121 121 c.lines_added = 0
@@ -129,23 +129,48 b' class RepoCommitsView(RepoAppView):'
129 129 c.inline_comments = []
130 130 c.files = []
131 131
132 c.statuses = []
133 132 c.comments = []
134 133 c.unresolved_comments = []
135 134 c.resolved_comments = []
136 if len(c.commit_ranges) == 1:
135
136 # Single commit
137 if single_commit:
137 138 commit = c.commit_ranges[0]
138 139 c.comments = CommentsModel().get_comments(
139 140 self.db_repo.repo_id,
140 141 revision=commit.raw_id)
141 c.statuses.append(ChangesetStatusModel().get_status(
142 self.db_repo.repo_id, commit.raw_id))
142
143 143 # comments from PR
144 144 statuses = ChangesetStatusModel().get_statuses(
145 145 self.db_repo.repo_id, commit.raw_id,
146 146 with_revisions=True)
147 prs = set(st.pull_request for st in statuses
148 if st.pull_request is not None)
147
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 174 # from associated statuses, check the pull requests, and
150 175 # show comments from them
151 176 for pr in prs:
@@ -156,6 +181,37 b' class RepoCommitsView(RepoAppView):'
156 181 c.resolved_comments = CommentsModel()\
157 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 215 diff = None
160 216 # Iterate over ranges (default commit view is always one commit)
161 217 for commit in c.commit_ranges:
@@ -397,6 +453,7 b' class RepoCommitsView(RepoAppView):'
397 453 }
398 454 if comment:
399 455 c.co = comment
456 c.at_version_num = 0
400 457 rendered_comment = render(
401 458 'rhodecode:templates/changeset/changeset_comment_block.mako',
402 459 self._get_template_context(c), self.request)
@@ -39,7 +39,7 b' from rhodecode.lib.ext_json import json'
39 39 from rhodecode.lib.auth import (
40 40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 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 43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 44 from rhodecode.lib.vcs.exceptions import (
45 45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
@@ -474,9 +474,6 b' class RepoPullRequestsView(RepoAppView, '
474 474
475 475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
476 476
477
478
479
480 477 general_comments, inline_comments = \
481 478 self.register_comments_vars(c, pull_request_latest, versions)
482 479
@@ -980,7 +977,7 b' class RepoPullRequestsView(RepoAppView, '
980 977 version = self.request.GET.get('version')
981 978
982 979 _render = self.request.get_partial_renderer(
983 'rhodecode:templates/pullrequests/pullrequest_show.mako')
980 'rhodecode:templates/base/sidebar.mako')
984 981 c = _render.get_call_context()
985 982
986 983 (pull_request_latest,
@@ -999,7 +996,11 b' class RepoPullRequestsView(RepoAppView, '
999 996
1000 997 self.register_comments_vars(c, pull_request_latest, versions)
1001 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 1005 @LoginRequired()
1005 1006 @NotAnonymous()
@@ -1017,7 +1018,7 b' class RepoPullRequestsView(RepoAppView, '
1017 1018 version = self.request.GET.get('version')
1018 1019
1019 1020 _render = self.request.get_partial_renderer(
1020 'rhodecode:templates/pullrequests/pullrequest_show.mako')
1021 'rhodecode:templates/base/sidebar.mako')
1021 1022 c = _render.get_call_context()
1022 1023 (pull_request_latest,
1023 1024 pull_request_at_ver,
@@ -1039,7 +1040,10 b' class RepoPullRequestsView(RepoAppView, '
1039 1040 .get_pull_request_resolved_todos(pull_request)
1040 1041
1041 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 1048 @LoginRequired()
1045 1049 @NotAnonymous()
@@ -354,34 +354,37 b' class ChangesetStatusModel(BaseModel):'
354 354 Session().add(new_status)
355 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 381 def reviewers_statuses(self, pull_request):
358 382 _commit_statuses = self.get_statuses(
359 383 pull_request.source_repo,
360 384 pull_request=pull_request,
361 385 with_revisions=True)
362 386
363 commit_statuses = collections.defaultdict(list)
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
387 return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers)
385 388
386 389 def calculated_review_status(self, pull_request, reviewers_statuses=None):
387 390 """
@@ -228,6 +228,14 b' class CommentsModel(BaseModel):'
228 228
229 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 239 def _log_audit_action(self, action, action_data, auth_user, comment):
232 240 audit_logger.store(
233 241 action=action,
@@ -55,3 +55,16 b''
55 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 255 .btn-group-actions {
256 256 position: relative;
257 z-index: 100;
257 z-index: 50;
258 258
259 259 &:not(.open) .btn-action-switcher-container {
260 260 display: none;
@@ -1078,10 +1078,16 b' input.filediff-collapse-state {'
1078 1078 background: @color5;
1079 1079 color: white;
1080 1080 }
1081
1081 1082 &[op="comments"] { /* comments on file */
1082 1083 background: @grey4;
1083 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 31 clear: both;
32 32 }
33 33
34 .display-none {
35 display: none;
36 }
37
34 38 .pull-right {
35 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 91 .action-link{
87 92 margin-left: @padding;
88 93 padding-left: @padding;
@@ -482,6 +487,15 b' ul.auth_plugins {'
482 487 text-align: left;
483 488 overflow: hidden;
484 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 501 .pr-details-title-author-pref {
@@ -1169,9 +1183,12 b' label {'
1169 1183 a {
1170 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 1189 display: none;
1174 1190 }
1191
1175 1192 }
1176 1193
1177 1194 img {
@@ -1553,6 +1570,7 b' table.integrations {'
1553 1570 width: 16px;
1554 1571 padding: 0;
1555 1572 color: black;
1573 cursor: pointer;
1556 1574 }
1557 1575
1558 1576 .reviewer_member_mandatory_remove {
@@ -1682,7 +1700,7 b' table.group_members {'
1682 1700 }
1683 1701
1684 1702 .reviewer_ac .ac-input {
1685 width: 92%;
1703 width: 100%;
1686 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 2776 #help_kb .modal-content{
2759 max-width: 750px;
2777 max-width: 800px;
2760 2778 margin: 10% auto;
2761 2779
2762 2780 table{
@@ -3053,4 +3071,141 b' form.markup-form {'
3053 3071
3054 3072 .pr-hovercard-title {
3055 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 791 &.main_filter_input {
792 792 padding: 5px 10px;
793 min-width: 340px;
793
794 794 color: @grey7;
795 795 background: @black;
796 796 min-height: 18px;
@@ -800,11 +800,34 b' input {'
800 800 color: @grey2 !important;
801 801 background: white !important;
802 802 }
803
803 804 &:focus {
804 805 color: @grey2 !important;
805 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 168 .icon-remove:before { content: '\e810'; } /* '' */
169 169 .icon-fork:before { content: '\e811'; } /* 'ξ ‘' */
170 170 .icon-more:before { content: '\e812'; } /* 'ξ ’' */
171 .icon-options:before { content: '\e812'; } /* 'ξ ’' */
171 172 .icon-search:before { content: '\e813'; } /* 'ξ “' */
172 173 .icon-scissors:before { content: '\e814'; } /* 'ξ ”' */
173 174 .icon-download:before { content: '\e815'; } /* 'ξ •' */
@@ -251,6 +252,7 b''
251 252 // TRANSFORM
252 253 .icon-merge:before {transform: rotate(180deg);}
253 254 .icon-wide-mode:before {transform: rotate(90deg);}
255 .icon-options:before {transform: rotate(90deg);}
254 256
255 257 // -- END ICON CLASSES -- //
256 258
@@ -131,6 +131,11 b' function setRCMouseBindings(repoName, re'
131 131 window.location = pyroutes.url(
132 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 35 }, function() {
36 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 279 $('#user').show(); // show user autocomplete after load
280 280
281 281 var commitElements = data["diff_info"]['commits'];
282
282 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 288 } else {
286 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 329 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
327 var members = self.$reviewMembers.get(0);
328 330 var id = reviewer_obj.user_id;
329 331 var username = reviewer_obj.username;
330 332
@@ -333,10 +335,10 b' ReviewersController = function () {'
333 335
334 336 // register IDS to check if we don't have this ID already in
335 337 var currentIds = [];
336 var _els = self.$reviewMembers.find('li').toArray();
337 for (el in _els) {
338 currentIds.push(_els[el].id)
339 }
338
339 $.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) {
340 currentIds.push($(value).data('reviewerUserId'))
341 })
340 342
341 343 var userAllowedReview = function (userId) {
342 344 var allowed = true;
@@ -354,12 +356,12 b' ReviewersController = function () {'
354 356 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
355 357 } else {
356 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 361 if (alreadyReviewer) {
360 362 alert(_gettext('User `{0}` already in reviewers').format(username));
361 363 } else {
362 members.innerHTML += renderTemplate('reviewMemberEntry', {
364 var reviewerEntry = renderTemplate('reviewMemberEntry', {
363 365 'member': reviewer_obj,
364 366 'mandatory': mandatory,
365 367 'reasons': reasons,
@@ -368,7 +370,9 b' ReviewersController = function () {'
368 370 'review_status_label': _gettext('Not Reviewed'),
369 371 'user_group': reviewer_obj.user_group,
370 372 'create': true,
371 });
373 'rule_show': true,
374 })
375 $(self.$reviewMembers.selector).append(reviewerEntry);
372 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 500 var self = this;
497 501 this.$verSource = $('input[name=ver_source]');
498 502 this.$verTarget = $('input[name=ver_target]');
@@ -612,25 +616,10 b' VersionController = function () {'
612 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 623 var self = this;
635 624 this.$updateCommits = $('#update_commits');
636 625 this.$updateCommitsSwitcher = $('#update_commits_switcher');
@@ -672,4 +661,230 b' UpdatePrController = function () {'
672 661 templateContext.repo_name,
673 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 701 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
702 702 notice_display = 'none' if len(notice_messages) == 0 else ''
703 703 %>
704 <style>
705
706 </style>
707 704
708 705 <ul id="quick" class="main_nav navigation horizontal-list">
709 706 ## notice box for important system messages
@@ -1202,6 +1199,7 b''
1202 1199 ('g p', 'Goto pull requests page'),
1203 1200 ('g o', 'Goto repository settings'),
1204 1201 ('g O', 'Goto repository access permissions settings'),
1202 ('t s', 'Toggle sidebar on some pages'),
1205 1203 ]
1206 1204 %>
1207 1205 %for key, desc in elems:
@@ -1221,3 +1219,36 b''
1221 1219 </div><!-- /.modal-content -->
1222 1220 </div><!-- /.modal-dialog -->
1223 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 4 <%namespace name="base" file="/base/base.mako"/>
5 5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 6 <%namespace name="file_base" file="/files/base.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
8
7 9
8 10 <%def name="title()">
9 11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
@@ -100,22 +102,6 b''
100 102 % endif
101 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 105 <span id="parent_link" class="tag tagtag">
120 106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
121 107 </span>
@@ -123,7 +109,9 b''
123 109 <span id="child_link" class="tag tagtag">
124 110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
125 111 </span>
112
126 113 </div>
114
127 115 </div>
128 116 </div>
129 117
@@ -160,7 +148,9 b''
160 148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
161 149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
162 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 154 </div>
165 155
166 156 ## template for inline comment form
@@ -169,7 +159,7 b''
169 159 ## comments heading with count
170 160 <div class="comments-heading">
171 161 <i class="icon-comment"></i>
172 ${_('Comments')} ${len(c.comments)}
162 ${_('General Comments')} ${len(c.comments)}
173 163 </div>
174 164
175 165 ## render comments
@@ -180,123 +170,262 b''
180 170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
181 171 </div>
182 172
183 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
184 <script type="text/javascript">
173 ### NAV SIDEBAR
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);
189 if($('#trimmed_message_box').height() === boxmax){
190 $('#message_expand').show();
191 }
199 ## REVIEWERS
200 <div class="right-sidebar-expanded-state pr-details-title">
201 <span class="tooltip sidebar-heading" title="${vote_title}">
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){
194 $('#trimmed_message_box').css('max-height', 'none');
195 $(this).hide();
196 });
237 <div class="right-sidebar-expanded-state pr-details-content">
238 % if c.unresolved_comments + c.resolved_comments:
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
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){
199 var boxid = $(this).attr('data-comment-id');
200 var button = $(this);
269 <div class="right-sidebar-expanded-state pr-details-content">
270 % if c.inline_comments_flat + c.comments:
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")) {
203 $('#{0} .inline-comments'.format(boxid)).each(function(index){
204 $(this).hide();
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
291 <script type="text/javascript">
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 314 button.removeClass("comments-visible");
207 } else {
208 $('#{0} .inline-comments'.format(boxid)).each(function(index){
209 $(this).show();
315 } else {
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
317 $(this).show();
210 318 });
211 319 button.addClass("comments-visible");
212 }
213 });
320 }
321 });
214 322
215 // next links
216 $('#child_link').on('click', function(e){
217 // fetch via ajax what is going to be the next link, if we have
218 // >1 links show them to user to choose
219 if(!$('#child_link').hasClass('disabled')){
220 $.ajax({
323 // next links
324 $('#child_link').on('click', function (e) {
325 // fetch via ajax what is going to be the next link, if we have
326 // >1 links show them to user to choose
327 if (!$('#child_link').hasClass('disabled')) {
328 $.ajax({
221 329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
222 success: function(data) {
223 if(data.results.length === 0){
224 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
225 }
226 if(data.results.length === 1){
227 var commit = data.results[0];
228 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
229 }
230 else if(data.results.length === 2){
231 $('#child_link').addClass('disabled');
232 $('#child_link').addClass('double');
330 success: function (data) {
331 if (data.results.length === 0) {
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
333 }
334 if (data.results.length === 1) {
335 var commit = data.results[0];
336 window.location = pyroutes.url('repo_commit', {
337 'repo_name': '${c.repo_name}',
338 'commit_id': commit.raw_id
339 });
340 } else if (data.results.length === 2) {
341 $('#child_link').addClass('disabled');
342 $('#child_link').addClass('double');
233 343
234 var _html = '';
235 _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)
237 .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)
239 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
240 _html +=' | ';
241 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
242 .replace('__branch__', data.results[1].branch)
243 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
244 .replace('__title__', data.results[1].message)
245 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
246 $('#child_link').html(_html);
247 }
344 var _html = '';
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
346 .replace('__branch__', data.results[0].branch)
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
348 .replace('__title__', data.results[0].message)
349 .replace('__url__', pyroutes.url('repo_commit', {
350 'repo_name': '${c.repo_name}',
351 'commit_id': data.results[0].raw_id
352 }));
353 _html += ' | ';
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
355 .replace('__branch__', data.results[1].branch)
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
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 });
250 e.preventDefault();
251 }
252 });
365 });
366 e.preventDefault();
367 }
368 });
253 369
254 // prev links
255 $('#parent_link').on('click', function(e){
256 // fetch via ajax what is going to be the next link, if we have
257 // >1 links show them to user to choose
258 if(!$('#parent_link').hasClass('disabled')){
259 $.ajax({
370 // prev links
371 $('#parent_link').on('click', function (e) {
372 // fetch via ajax what is going to be the next link, if we have
373 // >1 links show them to user to choose
374 if (!$('#parent_link').hasClass('disabled')) {
375 $.ajax({
260 376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
261 success: function(data) {
262 if(data.results.length === 0){
263 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
264 }
265 if(data.results.length === 1){
266 var commit = data.results[0];
267 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
268 }
269 else if(data.results.length === 2){
270 $('#parent_link').addClass('disabled');
271 $('#parent_link').addClass('double');
377 success: function (data) {
378 if (data.results.length === 0) {
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
380 }
381 if (data.results.length === 1) {
382 var commit = data.results[0];
383 window.location = pyroutes.url('repo_commit', {
384 'repo_name': '${c.repo_name}',
385 'commit_id': commit.raw_id
386 });
387 } else if (data.results.length === 2) {
388 $('#parent_link').addClass('disabled');
389 $('#parent_link').addClass('double');
272 390
273 var _html = '';
274 _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)
276 .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)
278 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
279 _html +=' | ';
280 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
281 .replace('__branch__', data.results[1].branch)
282 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
283 .replace('__title__', data.results[1].message)
284 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
285 $('#parent_link').html(_html);
286 }
391 var _html = '';
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
393 .replace('__branch__', data.results[0].branch)
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
395 .replace('__title__', data.results[0].message)
396 .replace('__url__', pyroutes.url('repo_commit', {
397 'repo_name': '${c.repo_name}',
398 'commit_id': data.results[0].raw_id
399 }));
400 _html += ' | ';
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
402 .replace('__branch__', data.results[1].branch)
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
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 });
289 e.preventDefault();
290 }
291 });
412 });
413 e.preventDefault();
414 }
415 });
292 416
293 // browse tree @ revision
294 $('#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)}';
296 e.preventDefault();
297 });
417 // browse tree @ revision
418 $('#files_link').on('click', function (e) {
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
420 e.preventDefault();
421 });
298 422
299 })
300 </script>
423 ReviewersPanel.init(null, setReviewersData);
424
425 var channel = '${c.commit_broadcast_channel}';
426 new ReviewerPresenceController(channel)
427
428 })
429 </script>
301 430
302 431 </%def>
@@ -11,6 +11,10 b''
11 11 <%namespace name="base" file="/base/base.mako"/>
12 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 18 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
15 19 <% latest_ver = len(getattr(c, 'versions', [])) %>
16 20
@@ -155,20 +159,16 b''
155 159 </div>
156 160 %endif
157 161
158 <a class="permalink" href="#comment-${comment.comment_id}">&para; #${comment.comment_id}</a>
159
160 162 <div class="comment-links-block">
161 163
162 164 % if inline:
163 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 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)}">
166 outdated ${'v{}'.format(comment_ver)} |
167 </code>
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>
168 <code class="action-divider">|</code>
168 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 ${'v{}'.format(comment_ver)} |
171 </code>
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>
171 <code class="action-divider">|</code>
172 172 % endif
173 173 </a>
174 174 % else:
@@ -179,45 +179,70 b''
179 179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
180 180 >
181 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 184 % else:
184 185 <a class="tooltip pr-version"
185 186 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
186 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 ${'v{}'.format(comment_ver)}
190 </code>
191 </a> |
189 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
190 </a>
191 <code class="action-divider">|</code>
192 192 % endif
193 193
194 194 % endif
195 195 % endif
196 196
197 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
198 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
199 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
200 ## permissions to delete
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):
202 <a onclick="return Rhodecode.comments.editComment(this);"
203 class="edit-comment">${_('Edit')}</a>
204 | <a onclick="return Rhodecode.comments.deleteComment(this);"
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
197 <details class="details-reset details-inline-block">
198 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
199 <details-menu class="details-dropdown">
200
201 <div class="dropdown-item">
202 ${_('Comment')} #${comment.comment_id}
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 </div>
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 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>
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>
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>
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 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>
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>
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>
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 246 % endif
222 247
223 248 </div>
@@ -102,6 +102,11 b''
102 102 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
103 103
104 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 110 ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
106 111 ${cbdiffs.render_diffset(
107 112 diffset=c.changes[commit.raw_id],
@@ -61,6 +61,8 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
61 61 diffset_container_id = h.md5(diffset.target_ref)
62 62 collapse_all = len(diffset.files) > collapse_when_files_over
63 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 68 %if use_comments:
@@ -208,13 +210,6 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
208 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 211 </h2>
210 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 213 % endif
219 214
220 215 <div id="todo-box">
@@ -239,6 +234,43 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
239 234 <% over_lines_changed_limit = False %>
240 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 275 lines_changed = filediff.patch['stats']['added'] + filediff.patch['stats']['deleted']
244 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 290 total_file_comments = [_c for _c in h.itertools.chain.from_iterable(file_comments) if not _c.outdated]
259 291 %>
260 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 296 <span class="pill" op="comments">
263
264 297 <i class="icon-comment"></i> ${len(total_file_comments)}
265 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 323 </span>
267 ${diff_ops(filediff)}
324
325 ${diff_ops(final_file_name, filediff)}
268 326
269 327 </label>
270 328
@@ -463,43 +521,15 b" return '%s_%s_%i' % (h.md5_safe(commit+f"
463 521 </div>
464 522 </%def>
465 523
466 <%def name="diff_ops(filediff)">
467 <%
468 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
469 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
470 %>
524 <%def name="diff_ops(file_name, filediff)">
525 <%
526 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
527 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE, COPIED_FILENODE
528 %>
471 529 <span class="pill">
472 530 <i class="icon-file-text"></i>
473 %if filediff.source_file_path and filediff.target_file_path:
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>
531 ${file_name}
500 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 534 <span class="pill-group pull-right">
505 535
@@ -53,6 +53,14 b" var data_hovercard_url = pyroutes.url('h"
53 53 var reviewGroup = null;
54 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 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 106 </td>
99 107
100 108 <% } else { %>
101 <td>
109 <td style="text-align: right;width: 10px;">
102 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 112 <i class="icon-remove"></i>
105 113 </div>
106 114 <% } %>
@@ -110,7 +118,7 b" var data_hovercard_url = pyroutes.url('h"
110 118 </tr>
111 119
112 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 122 <input type="hidden" name="__start__" value="reviewer:mapping">
115 123
116 124 <%if (member.user_group && member.user_group.vote_rule) { %>
@@ -19,21 +19,74 b''
19 19 <div class="box">
20 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 24 <div class="summary-details block-left">
25 25
26
27 <div class="pr-details-title">
28 ${_('New pull request')}
29 </div>
30
31 26 <div class="form" style="padding-top: 10px">
32 <!-- fields -->
33 27
34 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 90 <div class="label">
38 91 <label for="pullrequest_title">${_('Title')}:</label>
39 92 </div>
@@ -43,8 +96,9 b''
43 96 <p class="help-block">
44 97 Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready.
45 98 </p>
46 </div>
99 </div>
47 100
101 ## DESC
48 102 <div class="field">
49 103 <div class="label label-textarea">
50 104 <label for="pullrequest_desc">${_('Description')}:</label>
@@ -55,39 +109,49 b''
55 109 </div>
56 110 </div>
57 111
112 ## REVIEWERS
58 113 <div class="field">
59 114 <div class="label label-textarea">
60 <label for="commit_flow">${_('Commit flow')}:</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}
115 <label for="pullrequest_reviewers">${_('Reviewers')}:</label>
70 116 </div>
71 117 <div class="content">
72 ${h.hidden('source_repo')}
73 ${h.hidden('source_ref')}
74 </div>
118 ## REVIEW RULES
119 <div id="review_rules" style="display: none" class="reviewers-title">
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
77 <div class="content">
78 ## filled with JS
79 <div id="target_repo_desc"></div>
80 </div>
128 ## REVIEWERS
129 <div class="reviewers-title">
130 <div class="pr-details-title">
131 ${_('Pull request reviewers')}
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">
83 ${h.hidden('target_repo')}
84 ${h.hidden('target_ref')}
85 <span id="target_ref_loading" style="display: none">
86 ${_('Loading refs...')}
87 </span>
143 <div id="add_reviewer_input" class='ac'>
144 <div class="reviewer_ac">
145 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
146 <div id="reviewers_container"></div>
147 </div>
148 </div>
149
150 </div>
88 151 </div>
89 152 </div>
90 153
154 ## SUBMIT
91 155 <div class="field">
92 156 <div class="label label-textarea">
93 157 <label for="pullrequest_submit"></label>
@@ -96,66 +160,14 b''
96 160 <div class="pr-submit-button">
97 161 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
98 162 </div>
99 <div id="pr_open_message"></div>
100 163 </div>
101 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 165 </div>
150 166 </div>
151 167 </div>
168
152 169 </div>
153 <div class="box">
154 <div>
155 ## overview pulled by ajax
156 <div id="pull_request_overview"></div>
157 </div>
158 </div>
170
159 171 ${h.end_form()}
160 172 </div>
161 173
@@ -243,8 +255,6 b''
243 255
244 256 var diffDataHandler = function(data) {
245 257
246 $('#pull_request_overview').html(data);
247
248 258 var commitElements = data['commits'];
249 259 var files = data['files'];
250 260 var added = data['stats'][0]
@@ -303,27 +313,33 b''
303 313
304 314 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
305 315 msg += _ngettext(
306 'This pull requests will consist of <strong>{0} commit</strong>.',
307 'This pull requests will consist of <strong>{0} commits</strong>.',
316 'Compare summary: <strong>{0} commit</strong>',
317 'Compare summary: <strong>{0} commits</strong>',
308 318 commitElements.length).format(commitElements.length)
309 319
310 msg += '\n';
320 msg += '';
311 321 msg += _ngettext(
312 '<strong>{0} file</strong> changed, ',
313 '<strong>{0} files</strong> changed, ',
322 '<strong>, and {0} file</strong> changed.',
323 '<strong>, and {0} files</strong> changed.',
314 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 330 if (commitElements.length) {
320 331 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
321 332 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
322 333 }
323 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 345 reviewersController = new ReviewersController();
@@ -429,10 +445,12 b''
429 445
430 446 var targetRepoChanged = function(repoData) {
431 447 // generate new DESC of target repo displayed next to select
448
449 $('#target_repo_desc').html(repoData['description']);
450
432 451 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
433 $('#target_repo_desc').html(
434 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
435 );
452 var title = _gettext('Switch target repository with the source.')
453 $('#switch_base').html("<a class=\"tooltip\" title=\"{0}\" href=\"{1}\">Switch sides</a>".format(title, prLink))
436 454
437 455 // generate dynamic select2 for refs.
438 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 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 <%namespace name="sidebar" file="/base/sidebar.mako"/>
5
4 6
5 7 <%def name="title()">
6 8 ${_('{} Pull Request !{}').format(c.repo_name, c.pull_request.pull_request_id)}
@@ -21,113 +23,6 b''
21 23 ${self.repo_menu(active='showpullrequest')}
22 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 27 <%def name="main()">
133 28 ## Container to gather extracted Tickets
@@ -140,6 +35,7 b''
140 35 // TODO: marcink switch this to pyroutes
141 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 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 39 </script>
144 40
145 41 <div class="box">
@@ -226,7 +122,7 b''
226 122
227 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 126 <i class="icon-angle-down">more details</i>
231 127 </a>
232 128
@@ -643,101 +539,12 b''
643 539 </div>
644 540
645 541
646 ### NAVBOG RIGHT
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
542 ### NAV SIDEBAR
736 543 <aside class="right-sidebar right-sidebar-expanded" id="pr-nav-sticky" style="display: none">
737 544 <div class="sidenav navbar__inner" >
738 545 ## TOGGLE
739 546 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
740 <a href="#toggleSidebar">
547 <a href="#toggleSidebar" class="grey-link-action">
741 548
742 549 </a>
743 550 </div>
@@ -747,8 +554,12 b''
747 554
748 555 ## RULES SUMMARY/RULES
749 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 563 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
753 564 ${len(c.allowed_reviewers)}
754 565 </div>
@@ -769,7 +580,7 b''
769 580
770 581 ## REVIEWERS
771 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 584 <i class="icon-circle review-status-${c.pull_request_review_status}"></i>
774 585 ${_('Reviewers')}
775 586 </span>
@@ -846,7 +657,7 b''
846 657
847 658 % if not c.at_version:
848 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 661 % else:
851 662 <span class="block-right last-item noselect">Show resolved</span>
852 663 % endif
@@ -863,7 +674,7 b''
863 674 </table>
864 675 % else:
865 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 678 % else:
868 679 <table>
869 680 <tr>
@@ -882,6 +693,8 b''
882 693 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
883 694 <i class="icon-comment" style="color: #949494"></i>
884 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 698 </div>
886 699
887 700 <div class="right-sidebar-expanded-state pr-details-title">
@@ -903,7 +716,7 b''
903 716 </span>
904 717
905 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 720 % else:
908 721 <span class="block-right last-item noselect">Show hidden</span>
909 722 % endif
@@ -912,7 +725,7 b''
912 725
913 726 <div class="right-sidebar-expanded-state pr-details-content">
914 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 729 % else:
917 730 <table>
918 731 <tr>
@@ -942,7 +755,7 b''
942 755 <div class="right-sidebar-expanded-state pr-details-content">
943 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 759 % if c.referenced_desc_issues:
947 760 % for ticket_dict in c.referenced_desc_issues:
948 761 <tr>
@@ -961,7 +774,7 b''
961 774 </tr>
962 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 778 % if c.referenced_commit_issues:
966 779 % for ticket_dict in c.referenced_commit_issues:
967 780 <tr>
@@ -992,451 +805,189 b''
992 805 ## This JS needs to be at the end
993 806 <script type="text/javascript">
994 807
995 versionController = new VersionController();
996 versionController.init();
808 versionController = new VersionController();
809 versionController.init();
997 810
998 reviewersController = new ReviewersController();
999 commitsController = new CommitsController();
811 reviewersController = new ReviewersController();
812 commitsController = new CommitsController();
1000 813
1001 updateController = new UpdatePrController();
814 updateController = new UpdatePrController();
1002 815
1003 /** leak object to top level scope **/
1004 window.PullRequestPresenceController;
816 window.reviewerRulesData = ${c.pull_request_default_reviewers_data_json | n};
817 window.setReviewersData = ${c.pull_request_set_reviewers_data_json | n};
1005 818
1006 819 (function () {
1007 820 "use strict";
1008 821
1009 window.PullRequestPresenceController = function (channel) {
1010 var self = this;
1011 this.channel = channel;
1012 this.users = {};
822 // custom code mirror
823 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
824
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) {
1015 self.users = {}
1016 $.each(users, function(index, value) {
1017 var userId = value.state.id;
1018 self.users[userId] = value.state;
1019 })
892 window.closePullRequest = function (status) {
893 if (!confirm(_gettext('Are you sure to close this pull request without merging?'))) {
894 return false;
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 () {
1023 $.each($('.reviewer_entry'), function(index, value) {
1024 var userData = $(value).data();
1025 if(self.users[userData.reviewerUserId] !== undefined){
1026 $(value).find('.presence-state').show();
1027 } else {
1028 $(value).find('.presence-state').hide();
1029 }
1030 })
923 $('#edit_pull_request').on('click', function (e) {
924 var title = $('#pr-title-input').val();
925 var description = codeMirrorInstance.getValue();
926 var renderer = $('#pr-renderer-input').val();
927 editPullRequest(
928 "${c.repo_name}", "${c.pull_request.pull_request_id}",
929 title, description, renderer);
930 });
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
1033 this.handlePresence = function (data) {
1034
1035 if (data.type == 'presence' && data.channel === self.channel) {
1036 this.storeUsers(data.users);
1037 this.render()
1038 }
1039 };
1040
1041 this.handleChannelUpdate = function (data) {
971 $(comments).each(fn_display);
972 var btns = $('#{0} .inline-comments-button'.format(boxid));
973 $(btns).each(fn_display);
974 });
1042 975
1043 if (data.channel === this.channel) {
1044 this.storeUsers(data.state.users);
1045 this.render()
1046 }
976 // register submit callback on commentForm form to track TODOs
977 window.commentFormGlobalSubmitSuccessCallback = function () {
978 refreshMergeChecks();
979 };
1047 980
1048 };
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 };
981 ReviewerAutoComplete('#user');
1054 982
1055 983 })();
1056 984
1057
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 }
985 $(document).ready(function () {
1224 986
1225 refreshTODOs = function () {
1226 var params = {
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);
987 var channel = '${c.pr_broadcast_channel}';
988 new ReviewerPresenceController(channel)
1332 989
1333 if (button.hasClass("comments-visible")) {
1334 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
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>
990 })
991 </script>
1441 992
1442 993 </%def>
General Comments 0
You need to be logged in to leave comments. Login now